From b53fc964cef2d3ca167a68a39016caff056c7a43 Mon Sep 17 00:00:00 2001 From: jiwangyihao Date: Sun, 17 May 2026 02:09:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(acp):=20=E6=98=BE=E7=A4=BA=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modes/acp/acp-event-mapper.ts | 57 ++++++++++--- .../test/acp-event-mapper.test.ts | 79 +++++++++++++++++++ 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts index 593fc205a..37b702a61 100644 --- a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts +++ b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts @@ -1,6 +1,7 @@ import type { SessionNotification, SessionUpdate, + ToolCall, ToolCallContent, ToolCallLocation, ToolKind, @@ -144,18 +145,13 @@ export function mapAgentSessionEventToAcpSessionUpdates( case "message_end": return mapAssistantMessageEnd(event, sessionId, options); case "tool_execution_start": { - const update: SessionUpdate = { - sessionUpdate: "tool_call", + const update = buildToolCallStartUpdate({ toolCallId: event.toolCallId, - title: buildToolTitle(event.toolName, event.args, event.intent), - kind: mapToolKind(event.toolName), - status: "pending", - rawInput: event.args, - }; - const locations = extractToolLocations(event.args, options.cwd); - if (locations.length > 0) { - update.locations = locations; - } + toolName: event.toolName, + args: event.args, + intent: event.intent, + cwd: options.cwd, + }); return [toSessionNotification(sessionId, update)]; } case "tool_execution_update": { @@ -312,6 +308,45 @@ function mapTodoStatus(status: TodoStatus): "pending" | "in_progress" | "complet return todoStatusMap[status]; } +export function buildToolCallStartUpdate(input: { + toolCallId: string; + toolName: string; + args: unknown; + intent?: string; + cwd?: string; + status?: "pending" | "completed"; +}): SessionUpdate { + const update: ToolCall & { sessionUpdate: "tool_call" } = { + sessionUpdate: "tool_call", + toolCallId: input.toolCallId, + title: buildToolTitle(input.toolName, input.args, input.intent), + kind: mapToolKind(input.toolName), + status: input.status ?? "pending", + rawInput: input.args, + }; + const content = buildToolStartContent(input.toolName, input.args); + if (content.length > 0) { + update.content = content; + } + const locations = extractToolLocations(input.args, input.cwd); + if (locations.length > 0) { + update.locations = locations; + } + return update; +} + +function buildToolStartContent(toolName: string, args: unknown): ToolCallContent[] { + if (!isCommandToolName(toolName)) { + return []; + } + const command = extractStringProperty(args, "command"); + return command ? [textToolCallContent(`$ ${command}`)] : []; +} + +function isCommandToolName(toolName: string): boolean { + return toolName === "bash" || toolName === "shell" || toolName === "exec"; +} + function buildToolTitle(toolName: string, args: unknown, intent: string | undefined): string { const trimmedIntent = intent?.trim(); if (trimmedIntent) { diff --git a/packages/coding-agent/test/acp-event-mapper.test.ts b/packages/coding-agent/test/acp-event-mapper.test.ts index 1b259e358..646abdf59 100644 --- a/packages/coding-agent/test/acp-event-mapper.test.ts +++ b/packages/coding-agent/test/acp-event-mapper.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "bun:test"; +import path from "node:path"; import type { SessionNotification } from "@agentclientprotocol/sdk"; import { zSessionNotification } from "@agentclientprotocol/sdk/dist/schema/zod.gen.js"; import { mapAgentSessionEventToAcpSessionUpdates } from "../src/modes/acp/acp-event-mapper"; @@ -241,6 +242,84 @@ describe("ACP event mapper", () => { expect(update.sessionUpdate).toBe("tool_call_update"); expect(update.content).toEqual([{ type: "terminal", terminalId: "term-42" }]); }); + it("shows bash commands in visible tool call content", () => { + const updates = mapAgentSessionEventToAcpSessionUpdates( + { + type: "tool_execution_start", + toolCallId: "toolu_bash_1", + toolName: "bash", + args: { command: "npm run check", cwd: "/repo" }, + } as AgentSessionEvent, + "session-1", + ); + + expect(updates).toHaveLength(1); + expectAcpNotifications(updates); + const update = updates[0]!.update as { + sessionUpdate: string; + toolCallId?: string; + title?: string; + kind?: string; + status?: string; + rawInput?: unknown; + content?: unknown; + }; + expect(update.sessionUpdate).toBe("tool_call"); + expect(update.toolCallId).toBe("toolu_bash_1"); + expect(update.title).toBe("bash: npm run check"); + expect(update.kind).toBe("execute"); + expect(update.status).toBe("pending"); + expect(update.rawInput).toEqual({ command: "npm run check", cwd: "/repo" }); + expect(update.content).toEqual([{ type: "content", content: { type: "text", text: "$ npm run check" } }]); + }); + + it("does not add command text content to non-command tool starts", () => { + const updates = mapAgentSessionEventToAcpSessionUpdates( + { + type: "tool_execution_start", + toolCallId: "toolu_read_1", + toolName: "read", + args: { path: "README.md" }, + } as AgentSessionEvent, + "session-1", + ); + + expect(updates).toHaveLength(1); + expectAcpNotifications(updates); + const update = updates[0]!.update as { + sessionUpdate: string; + title?: string; + kind?: string; + rawInput?: unknown; + locations?: { path: string }[]; + content?: unknown; + }; + expect(update.sessionUpdate).toBe("tool_call"); + expect(update.title).toBe("read: README.md"); + expect(update.kind).toBe("read"); + expect(update.rawInput).toEqual({ path: "README.md" }); + expect(update.locations).toEqual([{ path: "README.md" }]); + expect("content" in update).toBe(false); + }); + it("resolves tool_execution_start locations against mapper cwd", () => { + const updates = mapAgentSessionEventToAcpSessionUpdates( + { + type: "tool_execution_start", + toolCallId: "toolu_read_cwd", + toolName: "read", + args: { path: "src/file.ts" }, + } as AgentSessionEvent, + "session-1", + { cwd: "/repo" }, + ); + + expect(updates).toHaveLength(1); + expectAcpNotifications(updates); + const update = updates[0]!.update as { sessionUpdate: string; locations?: { path: string }[]; content?: unknown }; + expect(update.sessionUpdate).toBe("tool_call"); + expect(update.locations).toEqual([{ path: path.resolve("/repo", "src/file.ts") }]); + expect("content" in update).toBe(false); + }); it("emits distinct locations for move-style path arguments", () => { const updates = mapAgentSessionEventToAcpSessionUpdates( {