Skip to content

Commit e2b7bf5

Browse files
committed
fix(messages): show searching state and preserve web search status
1 parent 6990158 commit e2b7bf5

7 files changed

Lines changed: 124 additions & 9 deletions

File tree

src/features/messages/components/MessageRows.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ function toolIconForSummary(
262262
if (label === "read") {
263263
return FileText;
264264
}
265-
if (label === "searched") {
265+
if (label === "searched" || label === "searching") {
266266
return Search;
267267
}
268268

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { ConversationItem } from "../../../types";
3+
import { buildToolSummary, statusToneFromText } from "./messageRenderUtils";
4+
5+
function makeToolItem(
6+
overrides: Partial<Extract<ConversationItem, { kind: "tool" }>>,
7+
): Extract<ConversationItem, { kind: "tool" }> {
8+
return {
9+
id: "tool-1",
10+
kind: "tool",
11+
toolType: "webSearch",
12+
title: "Web search",
13+
detail: "codex monitor",
14+
status: "completed",
15+
output: "",
16+
...overrides,
17+
};
18+
}
19+
20+
describe("messageRenderUtils", () => {
21+
it("renders web search as searching while in progress", () => {
22+
const summary = buildToolSummary(makeToolItem({ status: "inProgress" }), "");
23+
expect(summary.label).toBe("searching");
24+
expect(summary.value).toBe("codex monitor");
25+
});
26+
27+
it("renders mcp search calls as searching while in progress", () => {
28+
const summary = buildToolSummary(
29+
makeToolItem({
30+
toolType: "mcpToolCall",
31+
title: "Tool: web / search_query",
32+
detail: '{\n "query": "codex monitor"\n}',
33+
status: "inProgress",
34+
}),
35+
"",
36+
);
37+
expect(summary.label).toBe("searching");
38+
expect(summary.value).toBe("codex monitor");
39+
});
40+
41+
it("classifies camelCase inProgress as processing", () => {
42+
expect(statusToneFromText("inProgress")).toBe("processing");
43+
});
44+
});

src/features/messages/utils/messageRenderUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,8 @@ export function buildToolSummary(
289289

290290
if (item.toolType === "webSearch") {
291291
return {
292-
label: "searched",
293-
value: item.detail || "",
292+
label: statusToneFromText(item.status) === "processing" ? "searching" : "searched",
293+
value: item.detail || "the web",
294294
};
295295
}
296296

@@ -307,7 +307,7 @@ export function buildToolSummary(
307307
const args = parseToolArgs(item.detail);
308308
if (toolName.toLowerCase().includes("search")) {
309309
return {
310-
label: "searched",
310+
label: statusToneFromText(item.status) === "processing" ? "searching" : "searched",
311311
value:
312312
firstStringField(args, ["query", "pattern", "text"]) || item.detail,
313313
};
@@ -353,7 +353,7 @@ export function statusToneFromText(status?: string): StatusTone {
353353
if (/(fail|error)/.test(normalized)) {
354354
return "failed";
355355
}
356-
if (/(pending|running|processing|started|in_progress)/.test(normalized)) {
356+
if (/(pending|running|processing|started|in[_\s-]?progress)/.test(normalized)) {
357357
return "processing";
358358
}
359359
if (/(complete|completed|success|done)/.test(normalized)) {

src/features/threads/hooks/useThreadItemEvents.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,33 @@ describe("useThreadItemEvents", () => {
164164
);
165165
});
166166

167+
it("adds lifecycle status for web search items", () => {
168+
const { result } = makeOptions();
169+
const item: ItemPayload = { type: "webSearch", id: "search-1", query: "codex monitor" };
170+
171+
act(() => {
172+
result.current.onItemStarted("ws-1", "thread-1", item);
173+
});
174+
expect(buildConversationItem).toHaveBeenCalledWith(
175+
expect.objectContaining({
176+
type: "webSearch",
177+
id: "search-1",
178+
status: "inProgress",
179+
}),
180+
);
181+
182+
act(() => {
183+
result.current.onItemCompleted("ws-1", "thread-1", item);
184+
});
185+
expect(buildConversationItem).toHaveBeenCalledWith(
186+
expect.objectContaining({
187+
type: "webSearch",
188+
id: "search-1",
189+
status: "completed",
190+
}),
191+
);
192+
});
193+
167194
it("notifies when a user message is created", () => {
168195
const onUserMessageCreated = vi.fn();
169196
vi.mocked(buildConversationItem).mockReturnValue({

src/features/threads/hooks/useThreadItemEvents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function useThreadItemEvents({
6464
}
6565
}
6666
const itemForDisplay =
67-
itemType === "contextCompaction"
67+
itemType === "contextCompaction" || itemType === "webSearch"
6868
? ({
6969
...item,
7070
status: shouldMarkProcessing ? "inProgress" : "completed",

src/utils/threadItems.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,20 @@ describe("threadItems", () => {
458458
}
459459
});
460460

461+
it("defaults web search items to completed status", () => {
462+
const item = buildConversationItem({
463+
type: "webSearch",
464+
id: "web-1",
465+
query: "codex monitor",
466+
});
467+
expect(item).not.toBeNull();
468+
if (item && item.kind === "tool") {
469+
expect(item.toolType).toBe("webSearch");
470+
expect(item.status).toBe("completed");
471+
expect(item.detail).toBe("codex monitor");
472+
}
473+
});
474+
461475
it("merges thread items preferring non-empty remote tool output", () => {
462476
const remote: ConversationItem = {
463477
id: "tool-2",
@@ -512,6 +526,33 @@ describe("threadItems", () => {
512526
}
513527
});
514528

529+
it("keeps local tool status when remote status is empty", () => {
530+
const remote: ConversationItem = {
531+
id: "tool-remote-status",
532+
kind: "tool",
533+
toolType: "webSearch",
534+
title: "Web search",
535+
detail: "query",
536+
status: "",
537+
output: "",
538+
};
539+
const local: ConversationItem = {
540+
id: "tool-remote-status",
541+
kind: "tool",
542+
toolType: "webSearch",
543+
title: "Web search",
544+
detail: "query",
545+
status: "completed",
546+
output: "",
547+
};
548+
const merged = mergeThreadItems([remote], [local]);
549+
expect(merged).toHaveLength(1);
550+
expect(merged[0].kind).toBe("tool");
551+
if (merged[0].kind === "tool") {
552+
expect(merged[0].status).toBe("completed");
553+
}
554+
});
555+
515556
it("preserves streamed plan output when completion item has empty output", () => {
516557
const existing: ConversationItem = {
517558
id: "plan-1",

src/utils/threadItems.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,13 +755,14 @@ export function buildConversationItem(
755755
};
756756
}
757757
if (type === "webSearch") {
758+
const status = asString(item.status ?? "").trim();
758759
return {
759760
id,
760761
kind: "tool",
761762
toolType: type,
762763
title: "Web search",
763764
detail: asString(item.query ?? ""),
764-
status: "",
765+
status: status || "completed",
765766
output: "",
766767
};
767768
}
@@ -931,19 +932,21 @@ function chooseRicherItem(remote: ConversationItem, local: ConversationItem) {
931932
const remoteOutput = remote.output ?? "";
932933
const localOutput = local.output ?? "";
933934
const hasRemoteOutput = remoteOutput.trim().length > 0;
935+
const remoteStatus = remote.status?.trim();
934936
return {
935937
...remote,
936-
status: remote.status ?? local.status,
938+
status: remoteStatus ? remote.status : local.status,
937939
output: hasRemoteOutput ? remoteOutput : localOutput,
938940
changes: remote.changes ?? local.changes,
939941
};
940942
}
941943
if (remote.kind === "diff" && local.kind === "diff") {
942944
const useLocal = local.diff.length > remote.diff.length;
945+
const remoteStatus = remote.status?.trim();
943946
return {
944947
...remote,
945948
diff: useLocal ? local.diff : remote.diff,
946-
status: remote.status ?? local.status,
949+
status: remoteStatus ? remote.status : local.status,
947950
};
948951
}
949952
return remote;

0 commit comments

Comments
 (0)