From 23b8ebf8c2f2a8d22cfdbfa6b71931262b0ba91a Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 2 Oct 2026 08:20:13 -0700 Subject: [PATCH] refactor: report tool calls on the experimental ACP v2 surface (#1231) First part of step 4 of opt-in ACP v2 support (#1217, #1219, #1226, #1230): the v2 surface now sends the agent's tool calls. Nothing changes on ACP v1. ## Change `src/v2/tool-call.ts` maps the agent's v1 tool call reports to v2: - **One message.** v1 reports a new tool call with `tool_call` and changes it with `tool_call_update`. v2 has one `tool_call_update` that upserts by `toolCallId`, so both become that. - **`null` is left out.** Both versions leave an omitted field unchanged and replace `content` and `locations` as whole arrays. They differ on `null`: in v1 it leaves a field unchanged (v1's `name` docs say so explicitly), in v2 it clears it. The agent doesn't send `null` fields today; a unit test pins the rule. - **Content blocks pass through.** v2's content blocks are a superset of v1's. - **Diff and terminal content still throw**, until their own parts of step 4. ## Scenario suite Of the 30 scenarios that stopped at their first `tool_call`: | Status now | Scenarios | |---|---| | passes | 17: `read`, `grep-and-glob`, `bash-error`, `bash-background`, `skill`, `ask-user-question`, `memory-recall`, `permission-denied`, `tool-progress`, `task-output-and-stop`, four subagent scenarios, the three compaction scenarios | | stops at a permission request (step 5) | 9 | | stops at diff content (next part of step 4) | 4 | The 17 new golden files are in `src/tests/acp-scenarios/__snapshots__/v2/`. **One new check.** In v1, a `tool_call_update` for an id the client hasn't seen is an orphan, and clients drop it. In v2 the same message creates a tool call. A stray update would show up as a blank tool call, and the schema can't catch that. So every scenario recording, including those that stop, must give each tool call a title in its first report. It passes everywhere; making the first report lose its title fails it in 30 scenarios. ## Noted for later, not changed here - **`_meta` becomes stored state.** In v1, a report's `_meta` belongs to that report. In v2 it's stored on the tool call, and a concrete value replaces it. So a v2 client keeps only the last report's `_meta.claudeCode`: in `permission-denied`, the denial's `toolResponse` is replaced by the result's `{ toolName }`. No v2 client reads these keys yet. - **v2 has a `cancelled` tool status.** v1 reports an interrupted tool as `failed` with `_meta.claudeCode.nonExecutionKind`. Mapping it to `cancelled` means deciding where that fact lives. ## Verification Build, lint and all 2353 tests pass. --- src/tests/acp-scenarios-v2.test.ts | 85 ++++++++++++------- .../__snapshots__/v2/ask-user-question.jsonl | 16 ++++ .../__snapshots__/v2/bash-background.jsonl | 15 ++++ .../__snapshots__/v2/bash-error.jsonl | 14 +++ .../v2/compaction-failed-legacy.jsonl | 9 ++ .../__snapshots__/v2/compaction-legacy.jsonl | 12 +++ .../__snapshots__/v2/compaction-update.jsonl | 12 +++ .../__snapshots__/v2/grep-and-glob.jsonl | 20 +++++ .../__snapshots__/v2/memory-recall.jsonl | 9 ++ .../__snapshots__/v2/permission-denied.jsonl | 14 +++ .../acp-scenarios/__snapshots__/v2/read.jsonl | 14 +++ .../__snapshots__/v2/skill.jsonl | 14 +++ .../v2/subagent-agent-async.jsonl | 19 +++++ .../v2/subagent-late-child-update.jsonl | 19 +++++ .../__snapshots__/v2/subagent-nested.jsonl | 25 ++++++ .../v2/subagent-transcript-extension.jsonl | 22 +++++ .../v2/task-output-and-stop.jsonl | 18 ++++ .../__snapshots__/v2/tool-progress.jsonl | 16 ++++ src/tests/acp-v2.test.ts | 59 ++++++++++++- src/v2/session-update.ts | 4 + src/v2/tool-call.ts | 48 +++++++++++ 21 files changed, 431 insertions(+), 33 deletions(-) create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/read.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl create mode 100644 src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl create mode 100644 src/v2/tool-call.ts diff --git a/src/tests/acp-scenarios-v2.test.ts b/src/tests/acp-scenarios-v2.test.ts index 6a6f0852..3160737c 100644 --- a/src/tests/acp-scenarios-v2.test.ts +++ b/src/tests/acp-scenarios-v2.test.ts @@ -8,6 +8,9 @@ * stopped into passing, and updates the table. * - Schema: every message the agent sends, also in a scenario that stops, is * valid against the draft v2 schema of the SDK. + * - Tool calls: the first report of each tool call has a title. v2 has no + * `tool_call`, so a report of a tool call that the client has not seen + * creates it, where v1 drops it. * - Golden files: `acp-scenarios/__snapshots__/v2/.jsonl` for each * passing scenario. Run `npx vitest run src/tests/acp-scenarios-v2.test.ts -u` * to update them. @@ -57,9 +60,10 @@ const here = path.dirname(fileURLToPath(import.meta.url)); const PASSES = "passes"; /** Step 4 of `docs/acp-v2.md`. */ -const TOOL_CALLS = "The ACP v2 surface does not translate tool_call session updates yet"; +const DIFFS = "The ACP v2 surface does not translate diff tool call content yet"; const REPLAY = "Invalid params: The ACP v2 surface does not replay session history yet"; /** Step 5 of `docs/acp-v2.md`. */ +const PERMISSIONS = "The ACP v2 surface does not serve session/request_permission yet"; const PLANS = "The ACP v2 surface does not translate plan session updates yet"; /** @@ -69,43 +73,43 @@ const PLANS = "The ACP v2 surface does not translate plan session updates yet"; const V2_STATUS: Record = { "session-setup": PASSES, "text-and-thinking": PASSES, - "bash-foreground": TOOL_CALLS, - "bash-error": TOOL_CALLS, - "bash-background": TOOL_CALLS, + "bash-foreground": PERMISSIONS, + "bash-error": PASSES, + "bash-background": PASSES, "async-task-held-until-tool-id": PASSES, "async-task-ends-before-tool-id": PASSES, "async-task-released-at-turn-end": PASSES, - read: TOOL_CALLS, - "write-new": TOOL_CALLS, - "write-existing": TOOL_CALLS, - "edit-with-permission": TOOL_CALLS, - "edit-rejected": TOOL_CALLS, - "notebook-edit": TOOL_CALLS, - "grep-and-glob": TOOL_CALLS, - "web-fetch-and-search": TOOL_CALLS, - "subagent-task-legacy": TOOL_CALLS, - "subagent-agent-async": TOOL_CALLS, - "subagent-native-sessions": TOOL_CALLS, - "subagent-nested": TOOL_CALLS, - "subagent-late-child-update": TOOL_CALLS, - "subagent-transcript-extension": TOOL_CALLS, + read: PASSES, + "write-new": DIFFS, + "write-existing": DIFFS, + "edit-with-permission": DIFFS, + "edit-rejected": DIFFS, + "notebook-edit": PERMISSIONS, + "grep-and-glob": PASSES, + "web-fetch-and-search": PERMISSIONS, + "subagent-task-legacy": PERMISSIONS, + "subagent-agent-async": PASSES, + "subagent-native-sessions": PERMISSIONS, + "subagent-nested": PASSES, + "subagent-late-child-update": PASSES, + "subagent-transcript-extension": PASSES, "todo-write": PLANS, "task-create-update": PLANS, goal: PASSES, - "network-permission": TOOL_CALLS, - "exit-plan-approve": TOOL_CALLS, - "exit-plan-reject": TOOL_CALLS, - "ask-user-question": TOOL_CALLS, - skill: TOOL_CALLS, - "mcp-tool": TOOL_CALLS, - "task-output-and-stop": TOOL_CALLS, - "memory-recall": TOOL_CALLS, - "permission-denied": TOOL_CALLS, - "tool-progress": TOOL_CALLS, + "network-permission": PERMISSIONS, + "exit-plan-approve": PERMISSIONS, + "exit-plan-reject": PERMISSIONS, + "ask-user-question": PASSES, + skill: PASSES, + "mcp-tool": PERMISSIONS, + "task-output-and-stop": PASSES, + "memory-recall": PASSES, + "permission-denied": PASSES, + "tool-progress": PASSES, "rate-limit-and-origin": PASSES, - "compaction-legacy": TOOL_CALLS, - "compaction-update": TOOL_CALLS, - "compaction-failed-legacy": TOOL_CALLS, + "compaction-legacy": PASSES, + "compaction-update": PASSES, + "compaction-failed-legacy": PASSES, "session-load-replay": REPLAY, }; @@ -181,6 +185,25 @@ describe("ACP v2 scenarios", () => { }); }); + describe("tool calls are reported with a title first", () => { + it.each(SCENARIOS.map((scenario) => scenario.name))("%s", (scenario) => { + const reported = new Set(); + const untitled: string[] = []; + for (const message of run(scenario).raw) { + if (message.kind !== "notification" || message.method !== "session/update") continue; + const { sessionId, update } = message.payload as { + sessionId: string; + update: { sessionUpdate: string; toolCallId?: string; title?: unknown }; + }; + if (update.sessionUpdate !== "tool_call_update") continue; + const key = `${sessionId} ${update.toolCallId}`; + if (!reported.has(key) && typeof update.title !== "string") untitled.push(key); + reported.add(key); + } + expect(untitled).toEqual([]); + }); + }); + describe("v2 golden files", () => { const passing = SCENARIOS.map((scenario) => scenario.name).filter( (scenario) => V2_STATUS[scenario] === PASSES, diff --git a/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl new file mode 100644 index 00000000..5c143ec0 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/ask-user-question.jsonl @@ -0,0 +1,16 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"AskUserQuestion"}},"content":[],"kind":"other","name":"AskUserQuestion","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Asking for your input","toolCallId":"toolu_ask"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"AskUserQuestion"}},"content":[{"content":{"text":"Which database?","type":"text"},"type":"content"}],"rawInput":{"questions":[{"header":"Database","multiSelect":false,"options":[{"description":"Relational","label":"Postgres","preview":"CREATE TABLE t ();"},{"description":"Key-value","label":"Redis"}],"question":"Which database?"}]},"sessionUpdate":"tool_call_update","title":"Which database?","toolCallId":"toolu_ask"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"requires_action"}}} +{"kind":"request","method":"elicitation/create","payload":{"message":"Which database?","mode":"form","requestedSchema":{"properties":{"question_0":{"oneOf":[{"_meta":{"_claude/askUserQuestionOption":{"preview":"CREATE TABLE t ();"}},"const":"Postgres","description":"Relational","title":"Postgres"},{"const":"Redis","description":"Key-value","title":"Redis"}],"title":"Database","type":"string"},"question_0_custom":{"description":"Type your own answer, or add a note to the option you chose above (optional).","title":"Other","type":"string"}},"type":"object"},"sessionId":"","toolCallId":"toolu_ask"}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"AskUserQuestion"}},"content":[{"content":{"text":"User has answered your questions: \"Which database?\"=\"Postgres\".","type":"text"},"type":"content"}],"rawOutput":"User has answered your questions: \"Which database?\"=\"Postgres\".","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_ask"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl new file mode 100644 index 00000000..6b91e166 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/bash-background.jsonl @@ -0,0 +1,15 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bg"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"rawInput":{"command":"sleep 100"},"sessionUpdate":"tool_call_update","title":"sleep 100","toolCallId":"toolu_bg"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"rawInput":{"command":"sleep 100","description":"Wait"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_bg"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"Wait","type":"text"},"type":"content"}],"rawInput":{"command":"sleep 100","description":"Wait","run_in_background":true},"sessionUpdate":"tool_call_update","toolCallId":"toolu_bg"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"```console\nCommand running in background with ID: bash_1. Output is being written to: /tmp/tasks/bash_1.output. You will be notified when it completes.\n```","type":"text"},"type":"content"}],"rawOutput":"Command running in background with ID: bash_1. Output is being written to: /tmp/tasks/bash_1.output. You will be notified when it completes.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_bg"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl new file mode 100644 index 00000000..b048ea02 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/bash-error.jsonl @@ -0,0 +1,14 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bash"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"rawInput":{"command":"false"},"sessionUpdate":"tool_call_update","title":"false","toolCallId":"toolu_bash"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"Fail","type":"text"},"type":"content"}],"rawInput":{"command":"false","description":"Fail"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_bash"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"```\nExit code 1\nboom\n```","type":"text"},"type":"content"}],"rawOutput":"Exit code 1\nboom","sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_bash"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl new file mode 100644 index 00000000..272c3e98 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-failed-legacy.jsonl @@ -0,0 +1,9 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"kind":"think","sessionUpdate":"tool_call_update","status":"in_progress","title":"Compact conversation","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"content":[{"content":{"text":"Compaction failed: Context too small","type":"text"},"type":"content"}],"rawOutput":{"error":"Context too small"},"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl new file mode 100644 index 00000000..0a8530bd --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-legacy.jsonl @@ -0,0 +1,12 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"kind":"think","sessionUpdate":"tool_call_update","status":"in_progress","title":"Compact conversation","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"rawOutput":{"durationMs":7,"postTokens":100,"preTokens":1000,"trigger":"automatic"},"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl new file mode 100644 index 00000000..0a8530bd --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/compaction-update.jsonl @@ -0,0 +1,12 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"kind":"think","sessionUpdate":"tool_call_update","status":"in_progress","title":"Compact conversation","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"compact"}},"rawOutput":{"durationMs":7,"postTokens":100,"preTokens":1000,"trigger":"automatic"},"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl new file mode 100644 index 00000000..ef6ae241 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/grep-and-glob.jsonl @@ -0,0 +1,20 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"content":[],"kind":"search","name":"Grep","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"grep","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"rawInput":{"pattern":"value"},"sessionUpdate":"tool_call_update","title":"grep \"value\"","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"rawInput":{"path":"src","pattern":"value"},"sessionUpdate":"tool_call_update","title":"grep \"value\" src","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"rawInput":{"output_mode":"content","path":"src","pattern":"value"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"rawInput":{"-n":true,"output_mode":"content","path":"src","pattern":"value"},"sessionUpdate":"tool_call_update","title":"grep -n \"value\" src","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Grep"}},"content":[{"content":{"text":"src/app.ts:2:const value = 1;","type":"text"},"type":"content"}],"rawOutput":"src/app.ts:2:const value = 1;","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_grep"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Glob"}},"content":[],"kind":"search","locations":[],"name":"Glob","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Find","toolCallId":"toolu_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Glob"}},"rawInput":{"pattern":"**/*.ts"},"sessionUpdate":"tool_call_update","title":"Find `**/*.ts`","toolCallId":"toolu_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Glob"}},"content":[{"content":{"text":"src/app.ts\nsrc/b.ts","type":"text"},"type":"content"}],"rawOutput":"src/app.ts\nsrc/b.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl new file mode 100644 index 00000000..74886e82 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/memory-recall.jsonl @@ -0,0 +1,9 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"memory_recall","toolResponse":{"mode":"select"}}},"kind":"read","locations":[{"path":"/memory/a.md"},{"path":"/memory/b.md"}],"sessionUpdate":"tool_call_update","status":"completed","title":"Recalled 2 memories","toolCallId":"00000000-0000-4000-8000-000000000001"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"memory_recall","toolResponse":{"mode":"synthesize"}}},"content":[{"content":{"text":"The user prefers tabs.","type":"text"},"type":"content"}],"kind":"read","sessionUpdate":"tool_call_update","status":"completed","title":"Recalled synthesized memory","toolCallId":"00000000-0000-4000-8000-000000000002"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl new file mode 100644 index 00000000..98a74aa0 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/permission-denied.jsonl @@ -0,0 +1,14 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_denied"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"rawInput":{"command":"rm -rf /"},"sessionUpdate":"tool_call_update","title":"rm -rf /","toolCallId":"toolu_denied"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash","toolResponse":{"decisionReason":"Denied by rule Bash(rm:*)","decisionReasonType":"rule","message":"Permission to use Bash has been denied."}}},"content":[{"content":{"text":"Permission denied: Denied by rule Bash(rm:*)","type":"text"},"type":"content"}],"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_denied"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"```\nPermission to use Bash has been denied.\n```","type":"text"},"type":"content"}],"rawOutput":"Permission to use Bash has been denied.","sessionUpdate":"tool_call_update","toolCallId":"toolu_denied"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl new file mode 100644 index 00000000..f526961b --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/read.jsonl @@ -0,0 +1,14 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Read"}},"locations":[{"line":1,"path":"/a.ts"}],"rawInput":{"file_path":"/a.ts"},"sessionUpdate":"tool_call_update","title":"Read a.ts","toolCallId":"toolu_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Read"}},"content":[{"content":{"text":"```\n1\tconst a = 1;\n```","type":"text"},"type":"content"}],"rawOutput":"1\tconst a = 1;\nx","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Read","toolResponse":{"file":{"content":"const a = 1;\n","filePath":"/a.ts","numLines":1,"startLine":1,"totalLines":1},"type":"text"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl new file mode 100644 index 00000000..2e4e668a --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/skill.jsonl @@ -0,0 +1,14 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Skill"}},"content":[],"kind":"other","name":"Skill","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Load skill","toolCallId":"toolu_skill"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Skill"}},"rawInput":{"skill":"commits"},"sessionUpdate":"tool_call_update","title":"Load skill: commits","toolCallId":"toolu_skill"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Skill"}},"rawOutput":"Launching skill: commits","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_skill"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Skill","toolResponse":{"commandName":"commits","success":true}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_skill"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl new file mode 100644 index 00000000..bb609c90 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-agent-async.jsonl @@ -0,0 +1,19 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Background review"},"sessionUpdate":"tool_call_update","title":"Background review","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Background review","prompt":"Review the diff"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Background review","prompt":"Review the diff","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"Review the diff","type":"text"},"type":"content"}],"rawInput":{"description":"Background review","prompt":"Review the diff","run_in_background":true,"subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"Async agent launched successfully.","type":"text"},"type":"content"}],"rawOutput":"Async agent launched successfully.\nagentId: a2 (use SendMessage to continue)","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent","toolResponse":{"agentId":"a2","description":"Background review","isAsync":true,"outputFile":"/tmp/tasks/a2.output","prompt":"Review the diff","status":"async_launched"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl new file mode 100644 index 00000000..7d7ac6f4 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-late-child-update.jsonl @@ -0,0 +1,19 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_late"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Check the logs"},"sessionUpdate":"tool_call_update","title":"Check the logs","toolCallId":"toolu_late"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Check the logs","prompt":"Check logs"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"Check logs","type":"text"},"type":"content"}],"rawInput":{"description":"Check the logs","prompt":"Check logs","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolName":"Read"}},"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"The logs are clean.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The logs are clean.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Read","toolResponse":{"elapsedTimeSeconds":4}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_late_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolName":"Read"}},"content":[{"content":{"text":"```\n1\tok\n```","type":"text"},"type":"content"}],"rawOutput":"1\tok","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl new file mode 100644 index 00000000..3293365f --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-nested.jsonl @@ -0,0 +1,25 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_outer"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Plan the work"},"sessionUpdate":"tool_call_update","title":"Plan the work","toolCallId":"toolu_outer"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"rawInput":{"description":"Plan the work","prompt":"Plan it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_outer"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"Plan it","type":"text"},"type":"content"}],"rawInput":{"description":"Plan the work","prompt":"Plan it","subagent_type":"Plan"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_outer"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer","toolName":"Agent"}},"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_inner"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer","toolName":"Agent"}},"rawInput":{"description":"Read the spec"},"sessionUpdate":"tool_call_update","title":"Read the spec","toolCallId":"toolu_inner"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer","toolName":"Agent"}},"rawInput":{"description":"Read the spec","prompt":"Read spec.md"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_inner"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer","toolName":"Agent"}},"content":[{"content":{"text":"Read spec.md","type":"text"},"type":"content"}],"rawInput":{"description":"Read the spec","prompt":"Read spec.md","subagent_type":"Explore"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_inner"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner","toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_grandchild_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner","toolName":"Read"}},"locations":[{"line":1,"path":"/spec.md"}],"rawInput":{"file_path":"/spec.md"},"sessionUpdate":"tool_call_update","title":"Read /spec.md","toolCallId":"toolu_grandchild_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner","toolName":"Read"}},"content":[{"content":{"text":"```\n1\tspec\n```","type":"text"},"type":"content"}],"rawOutput":"1\tspec","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_grandchild_read"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer","toolName":"Agent"}},"content":[{"content":{"text":"The spec says X.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The spec says X.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_inner"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":"Plan:","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":" do X.","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Agent"}},"content":[{"content":{"text":"Plan: do X.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Plan: do X.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_outer"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl new file mode 100644 index 00000000..062a86fd --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/subagent-transcript-extension.jsonl @@ -0,0 +1,22 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Task"}},"content":[],"kind":"think","name":"Task","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_transcript"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Task"}},"rawInput":{"description":"Summarize"},"sessionUpdate":"tool_call_update","title":"Summarize","toolCallId":"toolu_transcript"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Task"}},"rawInput":{"description":"Summarize","prompt":"Summarize the repo"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_transcript"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Task"}},"content":[{"content":{"text":"Summarize the repo","type":"text"},"type":"content"}],"rawInput":{"description":"Summarize","prompt":"Summarize the repo","subagent_type":"Explore"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_transcript"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"Look at t","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"he layout.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"The repo has ","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"two packages.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript","toolName":"Glob"}},"content":[],"kind":"search","locations":[],"name":"Glob","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Find","toolCallId":"toolu_transcript_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript","toolName":"Glob"}},"rawInput":{"pattern":"*"},"sessionUpdate":"tool_call_update","title":"Find `*`","toolCallId":"toolu_transcript_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript","toolName":"Glob"}},"content":[{"content":{"text":"a\nb","type":"text"},"type":"content"}],"rawOutput":"a\nb","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_transcript_glob"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Task"}},"content":[{"content":{"text":"Two packages.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Two packages.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_transcript"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl new file mode 100644 index 00000000..9f157a66 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/task-output-and-stop.jsonl @@ -0,0 +1,18 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskOutput"}},"content":[],"kind":"other","name":"TaskOutput","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"TaskOutput","toolCallId":"toolu_out"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskOutput"}},"rawInput":{"task_id":"bash_1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_out"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskOutput"}},"rawInput":{"block":true,"task_id":"bash_1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_out"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskOutput"}},"content":[{"content":{"text":"success\ndone","type":"text"},"type":"content"}],"rawOutput":"success\ndone","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_out"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskStop"}},"content":[],"kind":"other","name":"TaskStop","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"TaskStop","toolCallId":"toolu_stop"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskStop"}},"rawInput":{"task_id":"bash_1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_stop"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"TaskStop"}},"content":[{"content":{"text":"Successfully stopped task: bash_1","type":"text"},"type":"content"}],"rawOutput":"Successfully stopped task: bash_1","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_stop"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl b/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl new file mode 100644 index 00000000..bf8d5c65 --- /dev/null +++ b/src/tests/acp-scenarios/__snapshots__/v2/tool-progress.jsonl @@ -0,0 +1,16 @@ +{"kind":"response","method":"initialize","payload":{"_meta":{"steering":{"supported":true}},"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":""},"protocolVersion":2}} +{"kind":"response","method":"session/new","payload":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [|all]]","type":"text"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"","sessionUpdate":"user_message"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"sessionUpdate":"state_update","state":"running"}}} +{"kind":"response","method":"session/prompt","payload":{"messageId":""}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"rawInput":{"command":"make"},"sessionUpdate":"tool_call_update","title":"make","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash","toolResponse":{"elapsedTimeSeconds":1}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash","toolResponse":{"elapsedTimeSeconds":2}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash","toolResponse":{"elapsedTimeSeconds":3}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"content":[{"content":{"text":"```console\nbuilt\n```","type":"text"},"type":"content"}],"rawOutput":"built","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_slow"}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}} +{"kind":"notification","method":"session/update","payload":{"sessionId":"","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}} diff --git a/src/tests/acp-v2.test.ts b/src/tests/acp-v2.test.ts index b6832d6d..133e00b8 100644 --- a/src/tests/acp-v2.test.ts +++ b/src/tests/acp-v2.test.ts @@ -926,9 +926,64 @@ describe("ACP v2 session translation", () => { }); it("fails on session updates that it does not translate yet", () => { + expect(() => v2SessionUpdate({ sessionUpdate: "plan", entries: [] })).toThrow( + "does not translate plan session updates yet", + ); + }); + + it("reports a new tool call with an upserting tool_call_update", () => { + expect( + v2SessionUpdate({ + sessionUpdate: "tool_call", + toolCallId: "t", + title: "Read", + name: "Read", + kind: "read", + status: "pending", + content: [{ type: "content", content: text("a.ts")[0] }], + locations: [{ path: "/p/a.ts" }], + }), + ).toEqual({ + sessionUpdate: "tool_call_update", + toolCallId: "t", + title: "Read", + name: "Read", + kind: "read", + status: "pending", + content: [{ type: "content", content: text("a.ts")[0] }], + locations: [{ path: "/p/a.ts" }], + }); + }); + + it("leaves out the null fields of a tool call update, which v2 would clear", () => { + // In v1, null leaves a tool call field unchanged; in v2 it clears it. + expect( + v2SessionUpdate({ + sessionUpdate: "tool_call_update", + toolCallId: "t", + status: "completed", + title: null, + name: null, + content: null, + locations: null, + rawOutput: null, + _meta: null, + }), + ).toEqual({ sessionUpdate: "tool_call_update", toolCallId: "t", status: "completed" }); + // An empty array still clears, in both. + expect( + v2SessionUpdate({ sessionUpdate: "tool_call_update", toolCallId: "t", content: [] }), + ).toEqual({ sessionUpdate: "tool_call_update", toolCallId: "t", content: [] }); + }); + + it("fails on tool call content that it does not translate yet", () => { expect(() => - v2SessionUpdate({ sessionUpdate: "tool_call", toolCallId: "t", title: "Read" }), - ).toThrow("does not translate tool_call session updates yet"); + v2SessionUpdate({ + sessionUpdate: "tool_call_update", + toolCallId: "t", + content: [{ type: "diff", path: "/p/a.ts", oldText: "a", newText: "b" }], + }), + ).toThrow("does not translate diff tool call content yet"); }); it("keeps the audience roles of a prompt block that v1 knows", () => { diff --git a/src/v2/session-update.ts b/src/v2/session-update.ts index 7ba1f2b5..63861721 100644 --- a/src/v2/session-update.ts +++ b/src/v2/session-update.ts @@ -5,6 +5,7 @@ import * as v2 from "@agentclientprotocol/sdk/experimental/v2"; import type { AcpSessionNotification } from "../acp-subagents.js"; import { v2AvailableCommands, v2ConfigOptions } from "./session.js"; +import { v2ToolCallUpdate } from "./tool-call.js"; /** * The v2 form of a v1 session update, or `undefined` when v2 has no @@ -35,6 +36,9 @@ export function v2SessionUpdate( } return { ...chunk, messageId }; } + case "tool_call": + case "tool_call_update": + return v2ToolCallUpdate(update); case "current_mode_update": // v2 has no modes: the mode is the `mode` config option. Every mode // change also reaches the client as a `config_option_update` or in the diff --git a/src/v2/tool-call.ts b/src/v2/tool-call.ts new file mode 100644 index 00000000..e77e95c8 --- /dev/null +++ b/src/v2/tool-call.ts @@ -0,0 +1,48 @@ +/** + * The v2 form of the tool call reports that `ClaudeAcpAgent` sends as v1 + * `tool_call` and `tool_call_update` updates. + */ +import type { ToolCall, ToolCallContent, ToolCallUpdate } from "@agentclientprotocol/sdk"; +import type * as v2 from "@agentclientprotocol/sdk/experimental/v2"; + +type V1ToolCallReport = + | (ToolCall & { sessionUpdate: "tool_call" }) + | (ToolCallUpdate & { sessionUpdate: "tool_call_update" }); + +type V2ToolCallUpdate = v2.ToolCallUpdate & { sessionUpdate: "tool_call_update" }; + +/** + * The v2 form of a v1 tool call report. + * + * v1 reports a new tool call with `tool_call` and changes it with + * `tool_call_update`; v2 has one `tool_call_update` that upserts by + * `toolCallId`. Both leave a field that a report omits unchanged, and both + * replace `content` and `locations` as whole arrays. They differ on `null`: + * in v1 it leaves the field unchanged too, in v2 it clears the field, so `null` + * fields are left out. + * + * Diff and terminal content throw until the v2 surface translates them + * (step 4 of `docs/acp-v2.md`). + */ +export function v2ToolCallUpdate(report: V1ToolCallReport): V2ToolCallUpdate { + const patch = Object.fromEntries( + Object.entries(report).filter( + ([key, value]) => value !== null && key !== "sessionUpdate" && key !== "content", + ), + ) as Omit; + return { + ...patch, + sessionUpdate: "tool_call_update", + ...(report.content != null ? { content: report.content.map(v2ToolCallContent) } : {}), + }; +} + +function v2ToolCallContent(item: ToolCallContent): v2.ToolCallContent { + switch (item.type) { + case "content": + // v2 content blocks are a superset of the v1 blocks. + return item; + default: + throw new Error(`The ACP v2 surface does not translate ${item.type} tool call content yet`); + } +}