mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
15034659f7df5c63b3583a189c37954ca8afb1ad
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
15034659f7 |
refactor: list a session's commands in its setup responses on the experimental ACP v2 surface (#1290)
The v2 draft lets the `session/new`, `session/resume`, and `session/fork` responses carry `availableCommands` ([agent-client-protocol#2259](https://github.com/agentclientprotocol/agent-client-protocol/pull/2259)), so a client can show a session's commands without waiting for an update. The v2 surface now fills it. ## Before and after, for a v2 client - **Before:** the setup response, then an `available_commands_update` with the list, from a `setTimeout` after the response. - **Now:** the list is in the response itself, for `session/new`, `session/resume` with and without replay, and `session/fork`. The field is left out when the list is empty, as the draft asks. - **No duplicate:** on a v2 connection the agent no longer sends that post-setup update (`afterSetupResponse`). Later changes still arrive as `available_commands_update`, for example the terminal-bound filtering after Claude Code's first init frame, or `commands_changed`. - v1 and AIR are unchanged. ## Cost None. The commands come from `Query.supportedCommands()`, which in the SDK is `this.latestCommands ?? (await this.initialization).commands`: the cached initialization result that setup already awaited. So the response doesn't wait any longer. ## Code - `ClaudeAcpAgent.availableCommands(sessionId)` returns the session's command list, factored out of `sendAvailableCommandsUpdate`. - The three post-setup `setTimeout` blocks become one `afterSetupResponse`, which skips the command update on v2. - `setupCommands` in `src/v2/agent.ts` adds the list in v2 form. A failure to read the commands is logged and leaves them out, since they're optional and the session already exists. ## Title `refactor:`, like the earlier v2 surface additions (#1253, #1255, #1256), so no release note announces v2 yet. Retitle it if you'd rather. ## Checks - `npm run check`, `npm run build` and all 2494 tests pass. - The 40 v2 golden files each move the list from the post-setup notification into the setup response, and nothing else changes; I checked every diff with a script. No v1 or AIR golden file changes. - New v2 tests cover each setup response (new, resume, resume with replay, fork) and check that no post-setup update follows. - I removed each part in turn, and each removal fails tests: the agent still sending the update on v2, and the commands missing from new, from resume, or from fork. - Not covered: the empty-list case, because the adapter always adds its `/mcp` command. |
||
|
|
07c7ed3752 |
refactor: fork sessions on the experimental ACP v2 surface (#1256)
Step 6 of opt-in ACP v2 support, option (b) as decided in review. Stacked on #1255: both change the `initialize` line of every v2 golden file. Once #1255 merges, I'll merge `main` into this branch so the diff shows fork alone. Nothing changes on ACP v1. ## Why fork, then resume The agent's v1 fork copies the transcript into a new session and returns only its id. Until a client resumes or loads the fork, a prompt to it fails with "Session not found", and the request's `mcpServers` go unused. AIR, the only v1 client that forks, loads the fork right away. The fork RFD describes the response as "the same as `session/new`", with config options, and the request as taking `session/load`'s options. So on v2 the client gets a session it can prompt at once: - `v1ForkSessionRequests` (`src/v2/session.ts`) builds the v1 fork, then a v1 resume of the fork without replay, with the request's `cwd`, `additionalDirectories` and `mcpServers`. The request's `_meta` goes to the fork alone. - The response is the new `sessionId` plus the resume's `configOptions`. The available commands follow as an update, as for `session/new` and `session/resume`. - `capabilities.session.fork: {}` is advertised. Each fork starts its own Claude Code process, as `session/new` does. The draft doesn't say whether a fork must be live, so that's recorded as feedback for it. ## Verification - An end-to-end test forks a session with an HTTP MCP server, checks the forked Claude Code session resumes the copied transcript with that server, and prompts the fork without a `session/resume`. - Removing the resume, the MCP servers or the capability fails tests. - Every v2 golden file changes on its `initialize` line only, which gains `session.fork`. - Build, lint and all 2411 tests pass. |
||
|
|
3a61172e19 |
refactor: serve providers on the experimental ACP v2 surface (#1255)
Step 6 of opt-in ACP v2 support. Stacked on #1254: both change the `initialize` line of every v2 golden file. GitHub retargets this PR to `main` once #1254 merges. Nothing changes on ACP v1. ## Change - The v2 surface serves `providers/list`, `providers/set` and `providers/disable` through the agent's unstable v1 methods. Every provider type is identical in v1 and v2, so requests and responses pass through unchanged. - The v2 `initialize` response advertises `capabilities.providers: {}`, a constant like `capabilities.session`, since the agent serves the methods for every client. ## Verification - An end-to-end test lists the provider, sets a gateway and lists it, gets `invalid_params` for an unknown provider, and disables the provider back to the native routing. - The handshake test checks the capability. Removing the capability or a handler fails tests. - Every v2 golden file changes on its `initialize` line only, which gains `providers: {}`. - Build, lint and all 2410 tests pass. |
||
|
|
4ddda8f37e |
refactor: stop advertising steering on the experimental ACP v2 surface (#1254)
Found while reviewing what's left of opt-in ACP v2 support. Nothing
changes on ACP v1.
## Problem
The agent advertises `_session/steering` in the top-level `_meta` of its
v1 `initialize` response (`steering: { supported: true }`), and the v2
handshake passed that `_meta` through. The v2 surface doesn't register
`_session/steering`, so a v2 client that steered got "method not found".
## Change
The v2 `initialize` response leaves out the top-level `_meta` keys that
advertise an extension the v2 surface doesn't serve
(`UNSERVED_EXTENSION_KEYS` in `src/v2/initialize.ts`), which is
`steering` alone today. That sits next to the existing rule that keeps
the unstable `providers` capability unadvertised.
Whether v2 serves steering is left to the plan for the unstable and
extension surfaces. The protocol has no steering RFD yet, and steering
may need a different design on v2, which answers a prompt as soon as
it's accepted.
## Verification
- The handshake test checks that the v2 response has no
`_meta.steering`. The v1 handshake tests still check that v1 advertises
it.
- Every v2 golden file changes on its `initialize` line only. The line
loses its `_meta` and is otherwise identical.
- Build, lint and all 2409 tests pass.
|
||
|
|
52a92be959 |
fix: report a command's exit code only when the tool result says it (#1252)
Found while mapping commands to ACP v2 display terminals (#1240). It affects Zed and plain ACP v1 terminal clients and the v2 surface. **AIR is unchanged.** ## Problem The agent reported every command's exit with a number, even when it didn't know the code: - **any failure as 1.** A command that exited with 2, or a denied command that never ran, showed "Exited with code 1". - **a backgrounded command as 0 when it launched**, though it's still running. - **an interrupted command as 1**, and a non-zero code that Claude Code accepted as a success (grep with no matches) as 0. ## Change `CommandOutput.exitCode` is now optional. `BashReporter` sets it only when the tool result says it. I checked each source against the Claude Code 2.1.287 binary: - **a failure:** the `Exit code N` line that starts Claude Code's text of a command that exited with a failing code (`rE`/`JPr` for a `ShellError`). A failure that names none, such as a denial or a spawn error, has no code. - **a success:** 0, unless the structured `BashOutput` says otherwise. Claude Code accepts a non-zero code as a success only with a `returnCodeInterpretation`, such as grep's "No matches found" or diff's "Files differ". An interrupted command (an Esc interrupt returns a success with `interrupted: true`) and a backgrounded one have no code either. - **the code-execution block:** its `return_code`, as before. An unknown code goes out as: - **v1, Zed and plain clients:** `_meta.terminal_exit.exit_code: null`. The v1 `TerminalExitStatus` allows null. Zed (`as_u64()` → `None`) and Delta's v1 adapter (`Option<u32>`) read it as an optional code. Zed then shows a failed command as failed, without the "Exited with code N" tooltip, and every other exit as before. `compare.ts` allows the null code against the origin/main baseline, a documented difference like the `mcp` command in #1218. - **v2:** `exitStatus` without `exitCode`, which the v2 schema treats the same as null. - **AIR:** the numbers it always got (1 for a failed or interrupted command, else the code, or 0). Whether AIR accepts a null code is unchecked, so this stays out of AIR's contract. The command fact carries `interrupted` for that. The AIR golden files and `docs/air-extensions.md` don't change. ## For review - **Interrupted commands** no longer show as failed in Zed, since the tool call completes and no code is known. Their output still ends with "[Command was aborted before completion]". ## Not changed - **A backgrounded command's terminal still exits at launch**, now without a code. It could stay running until the task's `task_notification`. That needs the task's end routed to the terminal, also on replay, so it's a separate change. - **Replay has no `tool_use_result`** (the same gap as replayed diffs), so a replayed success still reports 0 when Claude Code interpreted the code or backgrounded the command. ## Verification A table test pins, for each kind of exit, the code Zed gets and the number AIR gets: a success, a failure that names its code, one that names none, an interrupted, a backgrounded, and an interpreted command. A unit test covers the v2 `exitStatus` without a code. Removing any one of the rules, including AIR's, fails tests. Golden files change for 3 v2 scenarios, each on its exit line only. Build, lint and all 2399 tests pass. |
||
|
|
3d9bae941c |
refactor: report commands as display terminals on the experimental ACP v2 surface (#1240)
Third part of step 4 of opt-in ACP v2 support (after #1231, #1232, #1237): a v2 client now sees each command the agent runs in an Agent-owned display terminal, instead of its output as a code block. Nothing changes on ACP v1. ## Why a mapping The agent already reports commands through the terminal extension of ACP v1 clients, as codex-acp does: - `_meta.terminal_info` names a terminal, which the tool call's content references; - `terminal_output` (a snapshot) or `terminal_output_delta` (a chunk) carries the output; - `terminal_exit` carries the exit. v2 standardizes exactly this as display terminals: the same `{ type: "terminal", terminalId }` content, plus `terminal_update` and `terminal_output_chunk` for the terminal's state. So the v2 surface maps the extension rather than adding new agent types. Unlike the agent's private `_meta.claudeCode` keys, these keys are a cross-agent protocol extension, so this maps one protocol form to another. ## Change - `ToolCallClientCapabilities.from` reports a v2 client as taking the extension. - `V2Terminals` (`src/v2/terminal.ts`), one per connection, turns the extension's keys into v2 updates, sent before the tool call report without those keys: - `terminal_info` → `terminal_update` naming the terminal; - `terminal_output` → `terminal_update` with an `output` snapshot, base64-encoded; - `terminal_output_delta` → `terminal_output_chunk`; - `terminal_exit` → `terminal_update` with `exitStatus`. - **The command:** the extension names none, so the terminal gets the command from the tool call's `rawInput` once a report carries it. The first report of a streamed tool use has no input yet. `V2Terminals` remembers each running command's terminal for that, and forgets it at exit or `session/close`. - **A report that carried only the output is dropped.** Its remaining `_meta` (the `parentToolUseId` stamp, for a subagent's command) would replace the tool call's stored `_meta` in v2. - Terminal content passes through the tool call translation. A v2 client has no client terminals, so these are the agent's only terminals. The 8 v2 scenarios that run a command change their golden files: `terminal_update`s in place of the code block, the terminal referenced in the permission request, and the exit before the tool call's status. ## Not changed here The agent reports a backgrounded command as exiting with 0 when it launches, and a failure whose exit code it doesn't know as 1. v1 terminal clients get the same values, since the extension requires a number. v2 can leave the exit status out, or say it's unknown, but that needs the agent's command fact to carry it, and it affects v1 too, so it's left for its own PR. ## Verification - Unit tests cover output chunks and snapshots, signals, the command arriving once, the dropped output report, a report without terminal keys passing untouched, and forgetting a closed session. - Each rule fails a test when removed. - Build, lint and all 2388 tests pass. |
||
|
|
98b17c28c8 |
refactor: advertise sessions on the experimental ACP v2 surface (#1239)
Step 4's last session piece for opt-in ACP v2 support (after #1237, #1238). The v2 surface now serves the whole v2 session baseline, so its `initialize` response advertises `capabilities.session`. Until now a conforming v2 client wouldn't call the session methods. Nothing changes on ACP v1, and the v2 surface stays behind `CLAUDE_AGENT_ACP_EXPERIMENTAL_V2`. ## Change `V2_SESSION_CAPABILITIES` is a constant, since it describes what the v2 surface serves: - **the session baseline:** `session/new`, `list`, `resume` (with replay since #1237), `close`, `prompt`, `cancel`, and `update`; - **`session/delete` and `additionalDirectories`.** Not `session/fork` yet, which v1 advertises but the v2 surface doesn't serve; - **prompt content and MCP transports,** as the agent's v1 `initialize` lists them: images and embedded context; stdio and HTTP servers. v2 has no SSE transport. The top-level `_meta` (`promptQueueing`, `authStatus`) passes through as before. `providers` stays unadvertised. Every v2 golden file changes on its `initialize` line only. ## Release Titled `refactor:` like the other v2 PRs, so it triggers no release on its own. This is the point where the v2 surface becomes usable by a conforming client, so `feat:` would also fit if you want the next release to be a minor one. ## Verification The handshake test pins the advertised capabilities, the schema check validates every recorded `initialize` response, and build, lint and all 2386 tests pass. |
||
|
|
b9a6ec00a6 |
refactor: serve permission requests on the experimental ACP v2 surface (#1234)
First part of step 5 of opt-in ACP v2 support (after #1231, #1232): the v2 surface now sends the agent's permission requests. Nothing changes on ACP v1: no v1 golden file changes. ## What differs in v2 A v1 request asks about a tool call, and clients show the tool call's title as the prompt. A v2 request gives the prompt a required `title` and an optional `description` of its own, apart from the tool call. It asks about a `subject`, which can be a tool call or a command. ## Change - **Prompt text.** The agent already computes the prompt's heading and explanation, but only AIR gets them, in `_meta.jetbrains.air.permission`. The agent's request type, `AcpPermissionRequest`, now carries them as `title` and `description` for a v2 client. It's the same rule #1232 uses for diffs: build what v1 can't express only for a v2 client. So v1 requests are byte-identical, and the v1 scenario golden files, which record what the agent hands `requestPermission`, don't change. The v2 surface fails on a request without a title, so a missed site fails loudly. - **Subject.** Always `{ type: "tool_call", toolCall }`, where `toolCall` is the same patch a `tool_call_update` carries (`v2ToolCallPatch`). The `command` subject would suit Bash once display terminals exist, since its `terminalId` refers to one. - **Approval preview.** The exact patch before an Edit or Write runs is now built in v2 form for a v2 client, and the request shows it. This fixed a real gap: the agent pins the preview as content the client holds, but only AIR's request shape included it. Without the fix, a v2 client saw no diff at all, because the hook's identical patch was then dropped as unchanged. - **Answer.** v2's outcomes map back unchanged. An outcome v1 doesn't know, which the draft says is no approval, becomes `cancelled`, and the agent reads that as an aborted tool use. - **`requires_action`** while a request is open was already in place since #1226. ## Scenario suite All 12 scenarios that stopped at a permission request now pass: Bash, Edit (approved and rejected), Write, NotebookEdit, WebFetch, MCP, network, ExitPlanMode (approved and rejected), and two subagent scenarios. Each recording shows `requires_action`, the request, then `running`. Now only replay and the two plan scenarios remain. ## Verification - **End to end:** a new test drives the agent's real `canUseTool` through a v2 client. Allow, reject, and an unknown outcome become allow, deny, and an aborted tool use. - **Unit tests:** the prompt's title is separate from the tool call's (ExitPlanMode: "Ready to code?" vs "Approve Plan"), the preview shows in the request, the v2 preview fallback works, and the title guard rejects a missing title. - **Mutations:** each rule fails a test when removed. Sending `title`/`description` to every client fails 37 tests, including v1 golden files. - Build, lint and all 2380 tests pass. |