diff --git a/packages/app/src/components/tool-call-details.tsx b/packages/app/src/components/tool-call-details.tsx index f66282dfe..7af089cd4 100644 --- a/packages/app/src/components/tool-call-details.tsx +++ b/packages/app/src/components/tool-call-details.tsx @@ -382,15 +382,6 @@ interface SearchDetail { function buildSearchSections(detail: SearchDetail, ds: DetailStyles): ReactNode[] { const out: ReactNode[] = []; - if (detail.query) { - out.push( - - - {detail.query} - - , - ); - } if (detail.content) { out.push( diff --git a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts index be01b7b15..432081f51 100644 --- a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts @@ -620,6 +620,56 @@ test("maps tool_result content shapes into deterministic string output", async ( } }); +test("Grep tool_result string content flows to a search detail with content", async () => { + const session = await createSession(); + const internal = session as unknown as { + buildToolOutput: ( + block: Record, + entry: Record | undefined, + ) => Record | undefined; + }; + + const grepEntry = { + id: "tool-grep-1", + name: "Grep", + server: "Grep", + classification: "search", + started: true, + input: { pattern: "MaskedView", output_mode: "files_with_matches" }, + }; + + try { + const output = internal.buildToolOutput( + { + type: "tool_result", + tool_use_id: "tool-grep-1", + tool_name: "Grep", + content: "Found 2 files\nsrc/foo.tsx\nsrc/bar.tsx", + is_error: false, + }, + grepEntry, + ); + + const { mapClaudeCompletedToolCall } = await import("./claude/tool-call-mapper.js"); + const item = mapClaudeCompletedToolCall({ + callId: "tool-grep-1", + name: "Grep", + input: grepEntry.input, + output: output ?? null, + }); + + expect(item?.detail).toEqual({ + type: "search", + query: "MaskedView", + toolName: "grep", + content: "Found 2 files\nsrc/foo.tsx\nsrc/bar.tsx", + numFiles: 0, + }); + } finally { + await session.close(); + } +}); + test("completes a foreground run when only system metadata arrives before the first assistant message", async () => { let step = 0; sdkQueryFactory.mockImplementation(() => diff --git a/packages/server/src/server/agent/providers/claude/tool-call-detail-parser.ts b/packages/server/src/server/agent/providers/claude/tool-call-detail-parser.ts index a03deac85..a63e8b715 100644 --- a/packages/server/src/server/agent/providers/claude/tool-call-detail-parser.ts +++ b/packages/server/src/server/agent/providers/claude/tool-call-detail-parser.ts @@ -25,6 +25,16 @@ import { toolDetailBranchByName, } from "../tool-call-detail-primitives.js"; +const ClaudeGrepOutputSchema = z + .union([ + ToolGrepOutputSchema, + z + .object({ output: z.string() }) + .passthrough() + .transform(({ output }) => ({ numFiles: 0, filenames: [], content: output })), + ]) + .nullable(); + const ClaudeToolEnvelopeSchema = z .object({ name: z.string().min(1), @@ -128,17 +138,11 @@ const ClaudeToolDetailPass2Schema = z.union([ toolDetailBranchByName("search", ToolSearchInputSchema, z.unknown(), (input) => toSearchToolDetail({ input, toolName: "search" }), ), - toolDetailBranchByName( - "Grep", - ToolSearchInputSchema, - ToolGrepOutputSchema.nullable(), - (input, output) => toSearchToolDetail({ input, output, toolName: "grep" }), + toolDetailBranchByName("Grep", ToolSearchInputSchema, ClaudeGrepOutputSchema, (input, output) => + toSearchToolDetail({ input, output, toolName: "grep" }), ), - toolDetailBranchByName( - "grep", - ToolSearchInputSchema, - ToolGrepOutputSchema.nullable(), - (input, output) => toSearchToolDetail({ input, output, toolName: "grep" }), + toolDetailBranchByName("grep", ToolSearchInputSchema, ClaudeGrepOutputSchema, (input, output) => + toSearchToolDetail({ input, output, toolName: "grep" }), ), toolDetailBranchByName( "Glob", diff --git a/packages/server/src/server/agent/providers/claude/tool-call-mapper.test.ts b/packages/server/src/server/agent/providers/claude/tool-call-mapper.test.ts index 1f0dd4824..531405285 100644 --- a/packages/server/src/server/agent/providers/claude/tool-call-mapper.test.ts +++ b/packages/server/src/server/agent/providers/claude/tool-call-mapper.test.ts @@ -310,6 +310,25 @@ describe("claude tool-call mapper", () => { }); }); + it("maps Grep calls when output arrives as the claude-agent string wrapper", () => { + const item = expectMapped( + mapClaudeCompletedToolCall({ + callId: "claude-grep-string-1", + name: "Grep", + input: { pattern: "MaskedView", output_mode: "files_with_matches" }, + output: { output: "Found 2 files\nsrc/foo.tsx\nsrc/bar.tsx" }, + }), + ); + + expect(item.detail).toEqual({ + type: "search", + query: "MaskedView", + toolName: "grep", + content: "Found 2 files\nsrc/foo.tsx\nsrc/bar.tsx", + numFiles: 0, + }); + }); + it("maps WebSearch calls with structured results", () => { const item = expectMapped( mapClaudeCompletedToolCall({