mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
v0.96.0
876 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f6958db1ee |
chore(main): release 0.96.0 (#1425)
🤖 I have created a release *beep* *boop* --- ## [0.96.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.95.0...v0.96.0) (2026-10-26) ### Features * report SendMessage delegations as linked session messages ([#1424](https://github.com/agentclientprotocol/claude-agent-acp/issues/1424)) ([v0.96.0 |
||
|
|
2f3c6bb7b3 |
fix: update to Claude Code 2.1.310 (#1435)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.309 to **0.3.310** (Claude Code **2.1.310**), `@agentclientprotocol/sdk` 2.0.1 → 2.0.2 and `@anthropic-ai/sdk` 1.1.0 → 1.3.0. TypeScript stays on 6.0.3, because typescript-eslint 8.72 still requires `typescript <6.1.0`. ## Behaviour changes ACP users may notice - **2.1.310:** the Monitor tool and plugin monitors now run on Bedrock, Vertex and Foundry, and with telemetry or nonessential traffic disabled. On those setups, long foreground sleeps are now blocked. We already hide Monitor tasks from the client (`local_monitor` / `HIDDEN_TASK_TOOLS`), so the only visible change is that a blocked sleep shows up as a failed Bash call. - **2.1.310:** when an MCP tool call times out because its elicitation went unanswered, the error now tells Claude not to repeat the call. ## Upstream fixes this picks up - Requests behind gateways that reject the `effort-2025-11-24` beta or mid-conversation system messages are retried without them. This affects our `providers/set` gateway sessions. - After a "Prompt is too long" error, compaction now trims dense content (base64, SVG) properly, so the next request no longer fails again. Compaction in SDK sessions with very large transcripts also uses less memory. - File read permission prompts now show where a symbolic link leads. - Windows: PreToolUse and PermissionRequest hooks no longer let a tool call through when Git Bash is missing and the hook's command is only quoted strings. ## Checked, no change needed - **Agent SDK 0.3.310:** the `sdk.d.ts` diff is doc text only (`connectorId` in the `sdkMcpServerConfigs` comment, and the provider list for `'highlights'` thinking display). Every `CLAUDE_*` env var the adapter reads is still in the 2.1.310 binary. - **ACP SDK 2.0.2:** only fixes the HTTP transport (CR line endings in SSE, request-scoped POST failures), which we don't use. - **`@anthropic-ai/sdk` 1.2/1.3:** adds API endpoints (message threads, usage/cost reports, Claude Tag); we only use its types. Verified: `npm run build`, `npm run check`, `npm run test:run` (3323 passed, 30 skipped), and a typecheck of `examples/`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)v0.95.1-preview.6 |
||
|
|
9a8bf71d45 |
fix: refine RFD child models after a load, and retry a failed model update (#1434)
## Summary Follow-up to #1433, which was merged before these review fixes landed. - **RFD child models after a load.** On a real `session/load` (session not live in this process), #1433 also gave `RfdSubagentReplay` no model list, so after the load response `currentStates` could not map a child's model to the root session's `model` entry. RFD children kept the raw model ID. The replay now reads the session's models when needed; only the in-replay mapping is off while the load replays. - **Retry after a failed publish.** `reportModel` skipped a model that matched the stored options, which are written before the publish. A failed publish then blocked every later report of the same model. It now compares with the last model the client actually received. - **Failed load replay.** The replayed child models are also dropped when the replay itself fails. - The buffer of an unannounced child collapses only model reports from `reportModel`; other config updates stay as they are. ## Tests - `replayRfd` gained a `load` option that replays like a real load; "sends the model of each child's responses, and after the load response only a changed entry" uses it (fails without the fix). - A retry after a failed publish, for the RFD and AIR forms. - "refines an ended AIR child's model" now goes through the load path. - `npm run build`, `npm run check`, `npm run test:run`: 3323 passed, 30 skipped. ## Related - Follow-up to #1433 and #1432 - RFD change: https://github.com/agentclientprotocol/agent-client-protocol/pull/2482 - RFD section: [Child configuration](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx#child-configuration) - JetBrains issue: [IJAI-4754](https://youtrack.jetbrains.com/issue/IJAI-4754)v0.95.1-preview.5 |
||
|
|
eb9d63e5fd |
fix: do not hold an AIR load replay until the CLI starts (#1433)
## Summary #1432 made an AIR `session/load` replay wait for the CLI start before it sent the children's models. So the end of every such replay (`loadReplayEnd` and the children's `subagent_state_update`) came only after the CLI started, also for a history without subagents. - The replay no longer waits. It sends each child's model as the replay reaches it. During a load the session's model list is not known yet, so the model goes as its id. - After the load response, each replayed child whose model the list maps to another entry gets that entry. This also applies to a child that the replay already ended. It is a more exact snapshot of the same configuration. - `modelEntryFor` and `modelDisplayName` keep their results per model list. `reportModel` skips a model that the child already has. - The buffer of a child that is not announced yet keeps only its latest configuration update, so model reports do not crowd out the child's output. - AIR: the runtime forgets the configuration of an ended child. ## Validation - `npm run build`, `npm run check` - `npm run test:run`, two runs: 3321 passed, 30 skipped. - `src/tests/acp-scenarios.test.ts`, 30 runs in 6 parallel workers: no failure. - New tests: the load end event before the CLI start, with and without subagents (both fail on `main`); the post-load refinement of an ended child; the buffer; the cleanup; the memo after a list change. - Microbenchmark of 10,000 child responses over 200 children, with 51 models: | | before | after | |---|---|---| | `modelEntryFor`, last entry | 12.16 µs | 0.23 µs | | `modelEntryFor`, no match | 64.60 µs | 0.08 µs | | `reportModel`, AIR | 13.66 µs | 4.03 µs | | `reportModel`, RFD | 12.80 µs | 3.23 µs | ## Related - Follow-up to https://github.com/agentclientprotocol/claude-agent-acp/pull/1432 - RFD change: https://github.com/agentclientprotocol/agent-client-protocol/pull/2482 - JetBrains issue: [IJAI-4754](https://youtrack.jetbrains.com/issue/IJAI-4754)v0.95.1-preview.4 |
||
|
|
c319e5e542 |
feat: report the model of each native subagent (#1432)
## Summary A native subagent child session gets its model only from what the child reports. The update is a `config_option_update` with the child's session id and a single `model` select option. This follows the subagents RFD, [Child configuration](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx#child-configuration) (see also agentclientprotocol/agent-client-protocol#2482). ## Relation to #1417 #1417 reports a child's model from its responses, in the `rfd` form only, with the raw model id as the name. This PR keeps that source and extends it: - The `air` form gets the same update. - History replay sends each child's model from its transcript. - The value maps to an entry of the root session's `model` option, so a client can compare the child's model with the session's. There is no model at spawn. The adapter cannot tell the model that Claude Code picks for a new child, for example with `CLAUDE_CODE_SUBAGENT_MODEL_FORCE`, or when an alias runs on the parent's exact model. So the first update comes with the child's first real response (`message.model`, not `<synthetic>`). A new update goes only when the entry changes. The configuration hooks of #1417 (`PostModelSwitch` and the others) now also run for `air` clients. A hook names the SDK agent id, so in the `air` form its options go to the agent's current generation session (`<agent id>:generation:<n>`). In the `air` form, no configuration goes after a child's end. ## Notification ```json { "sessionId": "<child session id>", "update": { "sessionUpdate": "config_option_update", "configOptions": [{ "id": "model", "name": "Model", "category": "model", "type": "select", "currentValue": "opus[1m]", "options": [{ "value": "opus[1m]", "name": "Opus 4.6 (1M context)" }] }] } } ``` - The client cannot change a child's configuration, so the select offers only the current value. - No field is added to `subagent_spawned` or `subagent_update`. ## Matching a model id to an entry Best first: 1. the root session's current entry, when it runs the same model up to a context hint, such as `opus[1m]` or `default` for `claude-opus-4-6`; 2. the listed entry whose value or resolved model is the id; 3. a listed entry that runs the same model up to a context hint. When several entries run the model, a concrete entry comes before `default`, unless `default` is the current entry. A model id is compared as it is, so a dated or provider id (`claude-haiku-4-5-20251001`, `eu.anthropic.claude-sonnet-5`) matches only the same id: `ModelInfo.resolvedModel` is the wire id that a response reports. A model that matches no entry goes as its full id, with its display name when the session's model list has one. A `PostModelSwitch` hook of a child uses the same matching. ## Replay - A child without responses in its transcript gets no model. - `rfd` form: the replay sends a child's model right after its announcement, from its first response, and again when a later response changes it. During a load, the session's model list can be unknown, so the replay can send the full id. After the load response, a child's model goes again only when the list maps it to another entry. - `air` form: the replay ends every child, and nothing may follow the end. So each child's latest response model goes once the session creation ended, just before the child's end, as the entry of the session's model list. ## Validation - `npm run build` - `npm run check` - `npm run test:run`, three runs: 3315 passed, 30 skipped. - `src/tests/acp-scenarios.test.ts`, 30 runs in 6 parallel workers: no failure. New tests cover: - no model before the child's first response, in both forms; - an update only when the entry changes; - no update to an ended `air` child, also with an update in flight at the end; - a hook's options under the current `air` generation; - matching for `opus[1m]` and `default`, concrete entries before `default`, listed entries, dated and provider ids, and unlisted ids; - the `PostModelSwitch` entry, in both forms; - an `air` load replay that maps a child's model only once the session exists; - `rfd` replay with an unlisted full id, and no duplicate update after the load response. The scenario snapshots for both forms and v2 are updated.v0.95.1-preview.3 |
||
|
|
739dde3ae5 |
feat: report SendMessage delegations as linked session messages (#1424)
In the subagents RFD form, a `SendMessage` call to a child now becomes a message between the two sessions, as a launch's prompt already does (#1405). Before, it was a tool call of its sender plus an unlinked message in the child. Every delegation now has a parent-side message, which a client can anchor a per-delegation card on, and both views name each other with `senderMessageId`/`recipientMessageId`. ## Live - The call is held until its final update. The SDK reports that the message resumed a child before it reports the call's result. - **Message reached an announced child:** the sender gets the outgoing view and the child the incoming one (`linkedSessionMessages`). The call's result isn't shown. - **Message reached no announced child**, for example one sent to the parent: the call is shown after all, with its final status (`unexposedControlFallback`). - **Call a permission request already showed:** it keeps its updates, and its message goes only to the child. ## Replay - A child's history replays right after its launch, so a call from a parent to its child is always matched to its delivery first. - The child's view then names the call, and the sender's view goes out where the call sits. - A call the replay reaches before its delivery stays a tool call, unlinked. That can happen with a child's message to a sibling whose history replays later. `publishPrompt`'s `replacesLaunch` option is now `replacesCall`. ## Testing - **Live:** the existing `SendMessage` tests in `native-subagents.test.ts` and `acp-agent.test.ts` now expect both views and check that the call and its completed result are hidden. A new test checks that a call reaching no announced child is shown with its final status. - **Replay:** the existing test expects the linked views in place of `root tool_call toolu_send`. A new test covers a call reached before its delivery, and fails if delivery ignores that the call was already shown. - `npm run check`, `npm run build` and `npm run test:run` (3301 tests) pass locally. Not tried against a real Claude Code run yet.v0.95.1-preview.2 |
||
|
|
17a967b4da |
fix: send subagents' current state after a load response (#1423)
The subagents RFD makes the response to a load (v1 `session/load`, v2 `session/resume` with replay) the freshness boundary. Work states and `cancel` grants sent before it are history, and "current snapshots are sent after the response". The adapter sent each replayed child's end only during the replay, so a client that follows the RFD strictly showed every child as unknown after a reload. This replaces the RFD carve-out proposed in agentclientprotocol/agent-client-protocol#2475. After the response, each replayed child now gets one current `subagent_update`: - **A child still running in this process**, as when a live session is loaded again, reports its live state and `capabilities.cancel`, as it did live. - **Any other child** can't be running, because the process that ran it is gone. Its replayed end becomes its current state, with no grant. When it's sent: - **v1:** with the commands, in the timer that follows the setup response (`afterSetupResponse`). - **v2:** the surface sends it only once its own resume response is out, after `setupCommands`. A timer set inside `loadSession` could fire before that response. `RfdSubagentReplay.finish` and the new `currentStates` share one `endState`. `NativeSubagentRuntime.liveWorkState` exposes the live state. ## Testing - `sends each child's current state after the load response`: a live child and a finished one, after the replay and its response. - `replays a subagent's history to its own session…` (v2) now records when the client receives the response. It checks that the child's current `state_update` arrives after it, and that the parent's copy grants nothing. It fails without the v2 change. - `npm run check`, `npm run build` and `npm run test:run` (3300 tests) pass locally. Not tested end to end: the v1 timer path. It reuses the timer that already sends v1 commands after the setup response.v0.95.1-preview.1 |
||
|
|
ebfbfbdaa9 |
chore(main): release 0.95.0 (#1391)
🤖 I have created a release *beep* *boop* --- ## [0.95.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.94.0...v0.95.0) (2026-10-23) ### Features * end a turn at its result for AIR clients with backgroundSubagents ([#1262](https://github.com/agentclientprotocol/claude-agent-acp/issues/1262)) ([v0.95.0 |
||
|
|
6ab670baae |
feat: report read-only subagent configuration (#1417)
## Summary Expose the observed configuration of RFD subagent sessions using the same config-option IDs as ordinary sessions, without granting client-initiated configuration changes. - Report model from child responses and model-switch hooks, and mode, applied reasoning effort, and agent type from child-attributed SDK hooks. - Merge partial observations into complete, ordered config snapshots; buffer early observations until truthful child announcement and serialize concurrent publications. - Advertise only each observed current value. Do not inherit parent settings or invent child Fast-mode state. - Keep child plan-mode and model-switch hooks from changing the parent's configuration. - Cover initial/changed settings, announcement ordering, publication failures, root isolation, and hook registration in both protocol versions; update v2 scenario snapshots. ## Related Protocol clarification: https://github.com/agentclientprotocol/agent-client-protocol/pull/2468 Zed client implementation: https://github.com/zed-industries/zed/pull/66058 ## Validation Passed after rebasing onto current `main`: - `npm run build` - `npm run check` - `npm run test:run -- --silent`: 3,294 passed, 30 skipped. No live Claude-to-Zed smoke test was run. Settings are reported only when observed from the SDK; availability depends on runtime support and events received.v0.94.1-preview.13 |
||
|
|
f7d6030c04 |
fix: bump the minor group across 1 directory with 2 updates (#1416)
Bumps the minor group with 2 updates in the / directory: [@opentelemetry/api](https://github.com/open-telemetry/opentelemetry-js) and [jose](https://github.com/panva/jose). Updates `@opentelemetry/api` from 1.9.1 to 1.10.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/open-telemetry/opentelemetry-js/releases">@opentelemetry/api's releases</a>.</em></p> <blockquote> <h2>api/v1.10.0</h2> <h2>1.10.0</h2> <h3>🚀 Features</h3> <ul> <li>feat(api): integrate the Logs API into <code>@opentelemetry/api</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/4862">#4862</a> <a href="https://github.com/hectorhdzg"><code>@hectorhdzg</code></a></li> <li>feat(api): add imperative <code>context.attach()</code> for setting context across callback boundaries that <code>with()</code> cannot wrap. <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6845">#6845</a> <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a> <ul> <li><code>attach()</code> returns a <code>Token</code> whose <code>dispose()</code> method restores the previous context. Use <code>token.dispose()</code> to detach.</li> <li><code>attach</code> is an optional method on the <code>ContextManager</code> interface; when the active context manager does not implement it, <code>context.attach()</code> logs a warning and returns a no-op token.</li> </ul> </li> <li>feat(api): add <code>AnyValue</code> type (of type <code>unknown</code>) and extend <code>Attributes</code> type values to use <code>AnyValue</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6780">#6780</a> <a href="https://github.com/trentm"><code>@trentm</code></a> <ul> <li>This is part of supporting <a href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4485-extending-attributes-to-support-complex-values.md#how">OTEP 4485</a>.</li> <li>This is a <strong>breaking change for SDK implementations</strong>, in that they may need to support more attribute types for any APIs that accept <code>Attributes</code> or <code>AnyValue</code>. Implementors should refer to the OTEP 4485 link above for "MUST / SHOULD" requirements for complex attributes handling. See also the <a href="https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-core/src/common/attributes.ts">attributes-handling utilities provided by <code>@opentelemetry/core</code> to help</a>. For example, <code>cleanSimpleAttributes()</code> can be used to limit user-provided attributes to the set of value types supported in earlier <code>@opentelemetry/api</code> versions.</li> <li>This change is <strong>typically not a breaking change for users</strong> of <code>@opentelemetry/api</code>. There is one possible breaking case: If a TypeScript user was using the <code>Attributes</code> type in their own code and using <a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html">TypeScript Narrowing</a> to reduce an attribute value to an expected type (<a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6579#issuecomment-4345316200">example here</a>). It is expected that this usage should be rare and, likely, limited to SDK implementations, if at all. (If this impacted your code, <a href="https://github.com/open-telemetry/opentelemetry-js/issues/new">please let us know</a>.)</li> <li><strong>Note for users of attributes:</strong> Simple attributes SHOULD be used whenever possible. Instrumentations SHOULD assume that backends do not index individual properties of complex attributes, that querying or aggregating on such properties is inefficient and complicated, and that reporting complex attributes carries higher performance overhead.</li> </ul> </li> </ul> <h3>🐛 Bug Fixes</h3> <ul> <li>fix(api): cap tracestate list-members and total length in the deprecated <code>TraceState.set()</code> so it stays in step with <code>TraceState</code> from <code>@opentelemetry/core</code> <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6964">#6964</a> <a href="https://github.com/Zuhef"><code>@Zuhef</code></a></li> <li>fix: resolve the ESM build through the <code>default</code> export condition instead of <code>import</code>, so a resolver that activates neither <code>import</code> nor <code>require</code> still finds an entry point <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/7132">#7132</a> <a href="https://github.com/overbalance"><code>@overbalance</code></a></li> </ul> <h3>📚 Documentation</h3> <ul> <li>docs(api): complete <code>@since</code> annotations for the public API <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/7023">#7023</a> <a href="https://github.com/nabeelamjadsheikh"><code>@nabeelamjadsheikh</code></a> <ul> <li>Adds the three missing exported-type annotations and the member-level annotations for methods and properties introduced after their enclosing type.</li> <li>Corrects <code>Attributes</code>, <code>AttributeValue</code> and <code>TracerOptions</code> from <code>1.3.0</code> to <code>1.1.0</code>; all three were already importable in 1.1.0 via wildcard re-export.</li> </ul> </li> </ul> <h3>🏠 Internal</h3> <ul> <li>build: migrate the package build from <code>tsc</code> to <code>tsdown</code>, emitting dual CJS/ESM output from a single <code>dist/</code> directory <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6293">#6293</a> <a href="https://github.com/overbalance"><code>@overbalance</code></a> <ul> <li>The public entry points are unchanged: <code>@opentelemetry/api</code> and <code>@opentelemetry/api/experimental</code>. This package already declared an <code>exports</code> map, so deep imports such as <code>@opentelemetry/api/build/src/...</code> were rejected before this change and continue to be.</li> <li>The emitted files moved out of <code>build/src</code> (CJS), <code>build/esm</code> and <code>build/esnext</code> (ESM) into <code>dist/</code>, using <code>.cjs</code>/<code>.mjs</code> extensions with matching <code>.d.cts</code>/<code>.d.mts</code> declarations. The non-standard <code>esnext</code> condition has been dropped in favour of the standard <code>import</code>/<code>require</code> conditions.</li> </ul> </li> <li>perf(api): add getGlobal fast-path <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/6956">#6956</a> <a href="https://github.com/legendecas"><code>@legendecas</code></a></li> <li>chore: remove the redundant <code>module</code> field from <code>package.json</code>; bundlers resolve the ESM build through the <code>exports</code> map <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/7130">#7130</a> <a href="https://github.com/overbalance"><code>@overbalance</code></a></li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md">@opentelemetry/api's changelog</a>.</em></p> <blockquote> <h2>1.10.0</h2> <h3>🚀 (Enhancement)</h3> <ul> <li>feat(resource): create sync resource with some attributes that resolve asynchronously <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3460">#3460</a> <a href="https://github.com/samimusallam"><code>@samimusallam</code></a></li> <li>feat: collect host id for non-cloud environments <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3575">#3575</a> <a href="https://github.com/mwear"><code>@mwear</code></a></li> <li>feat (api-logs): separate Events API into its own package <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3550">3550</a> <a href="https://github.com/martinkuba"><code>@martinkuba</code></a></li> <li>feat(sdk-metrics): apply binary search in histogram recording <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3539">#3539</a> <a href="https://github.com/legendecas"><code>@legendecas</code></a></li> <li>perf(propagator-jaeger): improve deserializeSpanContext performance <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3541">#3541</a> <a href="https://github.com/doochik"><code>@doochik</code></a></li> <li>feat: support TraceState in SamplingResult <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3530">#3530</a> <a href="https://github.com/raphael-theriault-swi"><code>@raphael-theriault-swi</code></a></li> <li>feat(sdk-trace-base): add diagnostic logging when spans are dropped <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3610">#3610</a> <a href="https://github.com/neoeinstein"><code>@neoeinstein</code></a></li> <li>feat: add unit to view instrument selection criteria <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3647">#3647</a> <a href="https://github.com/jlabatut"><code>@jlabatut</code></a></li> <li>feat(tracing): expose dropped counts for attributes, events and links on span <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3576">#3576</a> <a href="https://github.com/mohitk05"><code>@mohitk05</code></a></li> </ul> <h3>🐛 (Bug Fix)</h3> <ul> <li>fix(core): added falsy check to make otel core work with browser where webpack config had process as false or null <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3613">#3613</a> <a href="https://github.com/ravindra-dyte"><code>@ravindra-dyte</code></a></li> <li>fix(instrumentation-http): include query params in http.target <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3646">#3646</a> <a href="https://github.com/kobi-co"><code>@kobi-co</code></a></li> </ul> <h3>📚 (Refine Doc)</h3> <ul> <li>chore: update http example <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3651">#3651</a> <a href="https://github.com/JamieDanielson"><code>@JamieDanielson</code></a></li> </ul> <h3>🏠 (Internal)</h3> <ul> <li>chore(exporter-jaeger): deprecate jaeger exporter <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3585">#3585</a> <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a></li> <li>fix(sdk-metrics): fix flaky LastValueAggregator test by using fake timer <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3587">#3587</a> <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a></li> <li>fix(test): fix failing tests by preventing source-map generation <a href="https://redirect.github.com/open-telemetry/opentelemetry-js/pull/3642">#3642</a> <a href="https://github.com/pichlermarc"><code>@pichlermarc</code></a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="v0.94.1-preview.12 |
||
|
|
28a09b1008 |
chore: remove the unused ts-node devDependency (#1414)
Removes `ts-node` 10.9.2 from devDependencies. Nothing in the repo uses it: no script, config or test references it. `npm run build` uses `tsc`, tests run through vitest, and `npm run example:simple-client` uses `node --experimental-strip-types`. Verified: `npm run build`, `npm run check`, `npm run test:run`. |
||
|
|
a1521b6196 |
feat: update to Claude Code 2.1.309 (#1413)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.303 to **0.3.309** (Claude Code **2.1.309**), `@agentclientprotocol/sdk` 2.0.0 → 2.0.1, `@anthropic-ai/sdk` 0.135.0 → 1.1.0 and typescript-eslint 8.71.2 → 8.72.0. TypeScript stays on 6.0.3. typescript-eslint requires `typescript <6.1.0` and has no TS 7 support yet (typescript-eslint/typescript-eslint#10940), so we wait for that rather than running two TypeScript versions side by side. ## Uses new SDK features - **`/usage` recommendations (0.3.305):** our structured `/usage` Markdown now lists Claude Code's per-window suggestions (`- **41%** Disable my_server · /mcp`) above the MCP server table, in the order Claude Code sends them. `fix` is not rendered: its messages must be queued at priority `later` with `client_composed` set, which an ACP prompt cannot express. A malformed recommendation makes the response fail validation, so we fall back to Claude Code's own `/usage` text. - **`wakeup_source` on `command_lifecycle` "started" (0.3.307):** when a ScheduleWakeup or another scheduled task starts a turn with no prompt open, the async-task (AIR) "Claude is working" task now says `A scheduled wakeup fired (set for <UTC time>).` or `A scheduled task fired.` instead of `Claude Code started a turn without a prompt.` It works whether the frame arrives before or after `session_state_changed: running`; older CLIs and unknown values behave as before. ## Fixes for changes in the new CLI - **Fork transcripts (2.1.307):** the placeholder `tool_result` text in a fork's copied prefix changed (`Fork started — that fork is you.` / `This is the parent's call, not yours. …`). `stripForkPrefix` matches only the directive text, so behaviour is unchanged; its doc comment and the replay fixture now describe both formats. ## Behaviour changes ACP users may notice - **2.1.304:** tool search is on by default behind a custom `ANTHROPIC_BASE_URL` (our `providers/set` gateway sessions), and a gateway that rejects it is retried without it (2.1.305). - **2.1.304:** `rm -r` of a mounted drive or a top-level folder on it (`/Volumes/…`, `/media/…`, `/mnt/c/…`) now asks for permission, even in bypass mode, without an always-allow option. `CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT` turns it off. - **2.1.309:** with `sandbox.enabled` but no runnable sandbox, commands in `sandbox.excludedCommands` now ask before writing to the sandbox's write-list paths, and the unsandboxed fallback is refused. - **2.1.304:** `allowedMcpServers` no longer filters `managed-mcp.json` stdio servers that use `${VAR}` only in `env`. ## Upstream fixes this picks up - Model switches, prompts and interrupts no longer wait behind our background `getContextUsage` call (2.1.304); `setModel()` waits up to 15 s for the API to confirm a model (2.1.307). - A PreToolUse hook's deny reason is no longer replaced by a timeout of our `Edit|Write` preview callback hook (2.1.304). - Host-supplied `managedSettings` no longer drop `disableAllHooks`, `disableSkillShellExecution` and empty HTTP hook lists (0.3.309). - Gateway fixes: `context_management` rejections are retried without the field (2.1.304); no `prompt-caching-scope` header behind custom gateways (2.1.307). - A disabled MCP server is no longer reconnected by a later permission prompt in SDK sessions; HTTP MCP tool calls are no longer sent twice when the connection drops (2.1.307/2.1.309). - Bash permission and sandbox hardening (2.1.309), fewer "prompt is too long" and autocompact-thrashing errors, and faster resume of large compacted sessions. ## Checked, no change needed - **ACP SDK 2.0.1** only changes the client-side `createHttpStream` transport, which we don't use. - **`@anthropic-ai/sdk` 1.x** (API version 2026-10-21, Bearer auth, sampling params removed): we only use its types, none of which changed in a way that affects us. Neither our `dist/` nor the Agent SDK runtime imports it, and the bundled CLI carries its own client. It is Apache-2.0 from 1.0.0 (previously MIT). - **`sed -i` (2.1.309)** runs as plain Bash; SDK sessions already did, since `_simulatedSedEdit` only exists in the TUI preview. - The remaining type changes (`liveReply`, context-usage row `id`s, `pluginId`/`path` on agents and skills, new settings keys) are additive and unused. Verified: `npm run build`, `npm run check`, `npm run test:run` (3277 passed, 30 skipped), and a typecheck of `examples/`.v0.94.1-preview.11 |
||
|
|
0fe22ae5a1 |
fix: bump fast-uri from 3.1.8 to 3.1.9 (#1410)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.8 to 3.1.9. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/fastify/fast-uri/releases">fast-uri's releases</a>.</em></p> <blockquote> <h2>v3.1.9</h2> <h2>Security</h2> <p>This security release fixes four URI parsing and normalization vulnerabilities in fast-uri 3.x. Applications that process untrusted URI input should upgrade to v3.1.9.</p> <ul> <li><strong>Medium — URI scheme spoofing through percent-encoded schemes</strong> (<a href="https://github.com/fastify/fast-uri/security/advisories/GHSA-w74x-v6f9-j3xg">GHSA-w74x-v6f9-j3xg</a>, CVE-2026-110306): percent-encoded scheme input is now rejected instead of being decoded into a live scheme.</li> <li><strong>Medium — Host confusion during domain-host canonicalization</strong> (<a href="https://github.com/fastify/fast-uri/security/advisories/GHSA-wjr9-j795-jxcr">GHSA-wjr9-j795-jxcr</a>, CVE-2026-110180): malformed domain hosts containing ambiguous delimiters or stripped control characters are now rejected instead of being silently rewritten.</li> <li><strong>Low — Incorrect relative-reference normalization</strong> (<a href="https://github.com/fastify/fast-uri/security/advisories/GHSA-xhq7-xmqj-769h">GHSA-xhq7-xmqj-769h</a>, CVE-2026-110095): dot-segment removal now preserves the resolution semantics of relative references.</li> <li><strong>Low — Scheme injection in suffix references</strong> (<a href="https://github.com/fastify/fast-uri/security/advisories/GHSA-333x-w4r6-6crp">GHSA-333x-w4r6-6crp</a>, CVE-2026-110725): first-segment colons in path-only suffix references are now escaped so the output is not reparsed as a scheme.</li> </ul> <p>Upgrade with:</p> <pre lang="sh"><code>npm install fast-uri@3.1.9 </code></pre> <p>Thanks to <a href="https://github.com/zekth"><code>@zekth</code></a> for reporting the scheme, host, and relative-reference issues, and to <a href="https://github.com/UlisesGascon"><code>@UlisesGascon</code></a> for finding the suffix-reference issue and reviewing the remediations.</p> <h2>What's Changed</h2> <ul> <li>ci: enable trusted publishing releases (v3.x) by <a href="https://github.com/mcollina"><code>@mcollina</code></a> in <a href="https://redirect.github.com/fastify/fast-uri/pull/229">fastify/fast-uri#229</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9">https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="v0.94.1-preview.10 |
||
|
|
59e292e29f |
feat: report RFD delegations as linked session messages (#1405)
## Summary For a client on the subagents RFD (every ACP v2 client, and v1 clients that declare `subagents`), session messages now replace the Agent/Task call that launches a child. The parent no longer sees that tool call. AIR's form is unchanged. - **Prompt.** The launch's prompt is a `session_message` in both transcripts: the parent's outgoing view and the child's incoming one. Both use the launch's tool use id, and each names the other with `recipientMessageId` / `senderMessageId` from agentclientprotocol/agent-client-protocol#2465. - **Answer.** A foreground child's answer, from the launch's result, is the child's `session_message` to the parent (`<launch id>:reply`). It uses the result as `AgentReporter` renders it, so the model-facing `agentId: …` line and `<usage>` block are gone. A failure or cancellation comes from the child's `idle` stop reason, which it already reported. A background launch's result gets no reply. - **Hand-back mode.** Claude Code (seen with 2.1.303) can have the child hand its report back with a `SubagentHandback` call; the Agent result then only says that the report "was delivered to you as a message". That call is reported as the child's message to the parent, in both transcripts and linked the same way, and the launch's reply is skipped for that run. A failed hand-back delivered nothing, so it stays a tool call of the child. - **Unexposed work.** A launch whose child is never exposed is shown as a tool call after all, with its final status (`unexposedControlFallback`, the former `failedControlFallback`). - **Hidden launches by id.** The adapter's own report that closes an unfinished call (for example at a forced cancel) carries no `toolName`, so the runtime tracks hidden launches by id to keep that report from reaching the client for a call it never saw. - **Replay** sends the same messages from the child's transcript: the launch's prompt, the reply, and hand-backs. A launch without Claude Code's metadata file keeps its tool call and the missing-history notice. Replay can't tell a child's runs apart, so any hand-back in its transcript suppresses its launch's reply. - **SendMessage** calls stay tool calls of their sender, and the child gets the incoming view without references, as before. - README's subagent section describes the new behavior. Do not merge before agentclientprotocol/agent-client-protocol#2465 settles the field names: a merge to `main` publishes a preview right away. Clients on the published schema ignore the two fields (no `deny_unknown_fields` in the Rust schema; the scenario validation with the SDK's zod schema passes). ## Testing - `npm run build`, `npm run check` and `npm run test:run` pass (57 files, 4 skipped as before). - The six v2 `subagent-*.jsonl` scenario snapshots changed: the launch's tool-call updates are gone, the parent gets the outgoing view, the child's view carries `senderMessageId`, and foreground results became `:reply` messages. AIR snapshots are unchanged. - New unit tests cover hidden launches, nested launches, replies, failed and background launches, unexposed launches with both final statuses, the close report without a tool name, and hand-back success and failure, live and on replay. - A real run: the built adapter driven over v2 stdio with Claude Code, one foreground subagent asked to answer PONG, then `session/resume` with replay in a fresh process. Live and replay both showed the linked prompt (parent → child), the linked hand-back "PONG" (child → parent), and no Agent tool call.v0.94.1-preview.9 |
||
|
|
50cd76e78d |
fix: publish the session title as soon as the CLI has it (#1406)
## Summary Session titles showed up only after the first turn ended. With background subagents, that could be minutes or hours later. Now the adapter asks for the title when the prompt is sent to the CLI. In a live run the title arrived **0.8s** after the prompt, compared with **17.9s** (at turn end) on `main`. ## Why - **The CLI already titles SDK sessions.** The bundled CLI (2.1.303) titles a new session from its first prompt within seconds and stores it as an `ai-title` transcript entry. `getSessionInfo` returns it as `customTitle`. The header comment in `session-titles.ts` said the CLI never titles SDK sessions; that is out of date. The CLI's off switch only applies to the `sdk-cli` entrypoint without `CLAUDE_AGENT_SDK_VERSION`, and the TS SDK sets `sdk-ts` and that variable. - **No message carries an AI title.** The CLI's internal `system/session_title_changed` covers only `/rename`-style names, and its schema says so. - **The adapter only checked at `idle`.** `SessionTitles.onTurnEnd` ran at `session_state_changed: idle`, which comes after the whole turn. Since CLI 2.1.270 it also waits for background subagents to finish. In the common case, `onTurnEnd` just picked up the title that had been on disk for the whole turn. ## Change - **New `SessionTitles.onPromptQueued`**, called right after the prompt is pushed to the SDK. Until the session has a title, it: 1. reads the session file, and publishes a stored title if there is one (a resumed session, or a `/rename`); 2. otherwise sends `generate_session_title` with the prompt text only, in the background, and publishes the result. - **No second model call.** The CLI runs this request alongside the turn instead of queuing it behind it. Its handler returns the stored custom title, then the AI title, and otherwise waits for the CLI's own title call already in flight. - **One try per prompt, like the CLI.** After a prompt-time request, the turn end does not ask again. If nothing came back, it publishes the stored summary as before, and the next prompt tries again. - **Unchanged:** - the turn end still titles a prompt too short to title from (under 10 characters, like "hi") from the prompt plus the reply; - the turn end still picks up a later `/rename`; - a title the client set still wins. ## Verification - `npm run check` and `npm run test:run` pass (3275 passed, 30 skipped). - New tests: - the title is published while the first turn is still running; - the raw prompt is not published at turn end while a title is still pending; - a short "hi" prompt is titled at turn end from the prompt and the reply. - The latch-release test now pins one request per prompt. Removing the new call site makes two of the tests fail. - Live check: one real text-only turn against this branch and against `main`, both on the bundled CLI 2.1.303, timing `session_info_update` against the prompt response: | build | title published | prompt response | | --- | --- | --- | | this branch | 0.8s | 17.1s | | `main` | 17.9s | 17.9s | Both runs got the same title, and both transcripts contain the same two `ai-title` entries. So the change adds no title generation; the CLI itself writes the entry twice. ## Risks - **Undocumented CLI behaviour.** This depends on the CLI answering `generate_session_title` mid-turn and returning the title it is already generating, which `sdk.d.ts` does not document. If a future CLI changes that, the worst case is one extra small-model call per session. The latch still limits it to one success. - **Titles now arrive mid-turn.** Clients that expected `session_info_update` only between turns should be checked. - **Steering is not covered.** Messages injected with `_session/steering` don't trigger a title request.v0.94.1-preview.8 |
||
|
|
aa62f44886 |
ci: resolve latest Node LTS patches before using runner cache (#1404)
## Summary - Enable `check-latest: true` for all three Node setup steps: CI, stable publishing and preview publishing. - Preserve the existing `lts/*` policy, action pins, npm settings, package version and `engines.node` requirement. ## Why The latest sampled [CI](https://github.com/agentclientprotocol/claude-agent-acp/actions/runs/39865544394) and [preview publishing](https://github.com/agentclientprotocol/claude-agent-acp/actions/runs/39865544489) runs both selected cached Node **24.21.0**. That predates **24.22.1**, the fixed Node 24 release for the [October 21 security updates](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases), including CVE-2026-111497. ## Validation The same checks passed before and after: - Baseline on local Node **24.22.0**: `npm ci && npm run check && npm run build && npm run test:run`. - After on official Node **24.22.1** (archive SHA-256 verified): `npm ci && npm run check && npm run build && npm run test:run`. - Both runs: **3,272 passed, 30 skipped**; the skipped/integration coverage remains unverified. - Parsed workflow configuration to verify all three setup steps keep `lts/*` and enable `check-latest`. - Tests ran natively on macOS; GitHub runner selection remains to be confirmed by PR CI. No publish or release commands were run. ## Risk / rollout Low-risk CI-only runtime freshness change. Verify the next CI/stable/preview job's setup-node log resolves at least **24.22.1** for Node 24. Reverting restores cache-first selection, which can restore an unpatched runtime; it is not a mitigation. This patches the build/publishing environment, not end users' separately installed Node runtimes. No application-code or release-version changes are included.v0.94.1-preview.7 |
||
|
|
9b3a5c891c |
feat: end a turn at its result for AIR clients with backgroundSubagents (#1262)
## What This PR adds the AIR client capability `backgroundSubagents`. A client declares it in `initialize.clientCapabilities._meta.jetbrains.air.capabilities`. With the capability, an ACP v1 prompt turn ends at its result. This is also true while background subagents of the turn still run, including a subagent that SendMessage resumed. Subagent updates, permission requests and elicitations then go out after the prompt response. Their routing does not change. The agent advertises the capability only to ACP v1 AIR clients. ## Why The hold of #864/#866 keeps `session/prompt` open until the subagents finish. The Claude CLI ends the model turn at `result`, and only `idle` waits for the background agents. In AIR the hold hides the end of the answer, and the next user message steers into an old turn. One SendMessage-resumed subagent kept a turn open for about two hours. ## Unchanged - A client without the capability keeps the hold exactly. So #866 stays fixed for clients that stop listening at the prompt response. - ACP v2 keeps the hold too. ## Related changes - Out of a turn, the ExitPlanMode request of such a client does not offer the clear-context option. The fresh query must continue an ACP turn, and there is none. - An ExitPlanMode interruption of an autonomous cycle with no held turn now clears its markers. ## Docs `docs/air-extensions.md` has a new "Background subagents" section. ## Tests New capability cases: - a fresh subagent; - a SendMessage-resumed subagent; - a permission request after the prompt response, with the `parentToolUseId` attribution; - an autonomous followup that does not end the queued next prompt; - ExitPlanMode out of a turn, with and without a reset; - the exclusion of a running subagent from the incomplete-tool check. The existing tests cover the path without the capability. When the gate is disabled, 7 of the 8 new cases fail. The eighth is the in-turn control case and passes in both configurations. `npm run build`, `npm run check` and `npm run test:run` pass. The exception is 4 `providers.test.ts` cases. They depend on the environment (a local proxy `baseUrl`) and fail on `main` too.v0.94.1-preview.6 |
||
|
|
ecaa2c935b |
fix: bump postcss from 8.5.30 to 8.5.31 in the minor group (#1403)
Bumps the minor group with 1 update: [postcss](https://github.com/postcss/postcss). Updates `postcss` from 8.5.30 to 8.5.31 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/releases">postcss's releases</a>.</em></p> <blockquote> <h2>8.5.31</h2> <ul> <li>Fix better source map on <code></style></code> escaping (by <a href="https://github.com/firefoxic"><code>@firefoxic</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's changelog</a>.</em></p> <blockquote> <h2>8.5.31</h2> <ul> <li>Fix better source map on <code></style></code> escaping (by <a href="https://github.com/firefoxic"><code>@firefoxic</code></a>).</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="v0.94.1-preview.5 |
||
|
|
5b11f470b1 |
feat: show messages typed in other Remote Control clients (#1371)
A message typed in claude.ai or the Claude mobile app while Remote
Control is on never appeared in the client that hosts the session, even
though Claude's reply to it did. This makes both sides of that
conversation show up.
Two causes, one commit each:
- **Messages from other clients were dropped.** Claude Code echoes them
to the adapter as a replayed user message with `origin: { kind: "human"
}` and a uuid the adapter never created. That matched no turn, so it
fell into the "unrelated replay" branch and was ignored. Human-origin,
top-level, non-synthetic messages that match no turn are now forwarded
as `user_message_chunk` (mapped to `user_message` on ACP v2). The echoes
of the adapter's own prompts still match their turn first and stay out
of the feed.
- **The stream reader was not running after `/remote-control`.** The
reader that forwards Claude Code's output starts with the first prompt,
and `/remote-control` answers without a turn. In a thread where it was
the first thing run, nothing read the stream, so neither the phone's
message nor Claude's reply reached the client. The command now starts
the reader. This also fixes the reply side for #1197.
## Testing
- `npm run check`, `npm run build` and `npm run test:run` pass, with new
tests in `remote-user-messages.test.ts` and `remote-control.test.ts`.
The tests for both fixes fail without them.
- Ran the built adapter as a custom agent in Zed: after
`/remote-control`, a message sent from the Claude iOS app showed up in
the Zed thread along with Claude's reply.
- Not tested: messages with images or attachments, and whether resuming
a session can replay old human messages through the same path.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
v0.94.1-preview.4
|
||
|
|
e9f986b8a9 |
fix: IJAI-3744 keep a session running while its background tasks are open (#1399)
## Summary A Claude Code session can start a background shell command or a background sub-agent, and then end its turn. When a task ends, Claude Code wakes the session for a short turn. The session index reported `idle` at each turn end, so a client saw one completion for each task. Now the index finds the open background tasks in the transcript. A turn end with an open task stays `running`, also after 10 minutes. The session gets one `idle`, when the last task ends. The rules are the same as in the native Claude provider of AIR (IJAI-3744). ## Rules - A task opens with a tool result that has `backgroundTaskId`, or `isAsync` with an `agentId`. - A task closes with its `<task-notification>` (a user record or a queue `remove`), or with a `TaskStop` result. - The wait ends when no live process holds the session, or when the registry status is `idle`. - A task can start before the tail window. A later scan applies the tail on top of the earlier task set. When it cannot, the index reads the whole transcript, but only for a finished turn of a live session. ## Not in this PR - A background task that a sub-agent starts. Its start record is only in the transcript of the sub-agent. - A session that this connection runs. Its state comes from the SDK. ## Tests - `background-tasks.test.ts`: the record orders of the native provider tests, in a small transcript and in one longer than the tail window. The tests also cover a task before the tail window, the end of the wait, and the count of whole-file reads. - A real check with Claude Code 2.1.306 in a terminal. With two background shells or two background sub-agents, `main` gave three `idle` and this change gave one. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.94.1-preview.3 |
||
|
|
4d378f93f3 |
feat: serve experimental ACP v2 (#1400)
## Summary The draft ACP v2 is no longer opt-in. The adapter always connects through the SDK's protocol router (as codex-acp does), so a client that asks for protocol version 2 or later in `initialize` gets v2, and every other client gets v1 as before. v2 is still experimental and follows the draft as it changes. - Removes `CLAUDE_AGENT_ACP_EXPERIMENTAL_V2`, which only turned on what is now the default. - Adds `CLAUDE_AGENT_ACP_PROTOCOL` to limit the adapter to one version: - unset, empty, or `auto`: both, picked by the client's `initialize`. - `v1`: a client that asks for v2 is negotiated down to v1. This is the escape hatch if a client's v2 handling breaks. - `v2`: a v1 client's `initialize` is rejected with `-32022 Unsupported protocol version`. - Any other value fails at startup, so a typo like `CLAUDE_AGENT_ACP_PROTOCOL=1` doesn't silently serve both. - Fixes the comment in `src/acp-subagents.ts` that said v2 clients don't get subagents. Note: a client that already sends `protocolVersion >= 2` switches to the v2 surface as soon as this lands on `main`, which publishes a preview. ## Testing - New routing tests: down-negotiation to v1, rejecting v1 when limited to v2, env parsing. - `npm run check`, `npm run build`, `npm run test:run` pass. - Ran the built binary over stdio with each setting: default → v2 for a v2 client, `v1` → protocol version 1, `v2` → `-32022` for a v1 client, invalid value → exits with an error at startup. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.94.1-preview.2 |
||
|
|
a1d0eeeaad |
fix: publish turns that Claude Code starts after end_turn as an async task in v1 (#1390)
## Motivation
Claude Code can start a turn of its own after a prompt has ended, for
example to answer the notification of a finished background task. ACP v1
has no session state, and the client already got `end_turn` for its last
prompt. The output of that turn just keeps arriving as session updates,
and the client has no signal that the agent is working again, or when it
stops.
## Change
The adapter now publishes such a turn as an async task (AIR async tasks
extension) with the id `autonomous-turn-<uuid>`, the name `Claude is
working`, `taskType: "turn"` and `showInTranscript: false`. Its
description is the summary of the last task notification, when there is
one.
- **Start:** the SDK moves to `running` while no prompt is active or
queued. From CLI 2.1.270 the state stays `running` while background
agents run, so the task also starts at the first root output with no
prompt open.
- **End:** the task ends `completed` at the turn's result or at `idle`,
and `failed` at an error result. A coalesced placeholder result
(`num_turns: 0`) does not end it.
- **Folded prompt:** a prompt that the turn takes in ends the task,
because that prompt owns the turn now.
- **Stop:** `_session/async_task/stop` on this task interrupts the turn,
since the SDK has no task to stop. It returns `{ "stopped": false }`
while a prompt waits for its turn, because the interrupt would drop that
prompt.
- **Lifecycle:** the SDK task list does not end the task, because the
SDK does not know about it. Shutdown fails it like other open tasks.
Clients without the async tasks capability get nothing new. The behavior
is documented in `docs/air-extensions.md` under "Autonomous turn".
## Tests
`acp-agent.test.ts` covers the start at `running` and at the first
output, the end at the result, the folded prompt, stop as an interrupt,
and a client without async tasks. `async-tasks.test.ts` covers the SDK
task list reconciliation and shutdown.
v0.94.1-preview.1
|
||
|
|
7bcbfc7a5c |
chore(main): release 0.94.0 (#1381)
🤖 I have created a release *beep* *boop* --- ## [0.94.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.93.0...v0.94.0) (2026-10-19) ### Features * send one end event after the session/load history replay for AIR ([#1361](https://github.com/agentclientprotocol/claude-agent-acp/issues/1361)) ([v0.94.0 |
||
|
|
2dab922644 |
fix: update to Claude Code 2.1.303 (#1384)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.300 to **0.3.303**
(Claude Code **2.1.303**), along with dependabot's minor-group bumps
(`@anthropic-ai/sdk`, eslint, vitest and transitive packages). 0.3.304
is only on the `next` dist-tag, so it is not included.
## Fixes for changes in the new SDK
The Agent SDK turns on fork subagents (`subagent_type: "fork"`) by
default in SDK sessions (0.3.302). Before this, ACP sessions never got
them. Live streaming already handles forks. History replay did not:
- **Replay showed the parent's launching message inside the fork.** A
fork's transcript starts with a copy of the parent's launching assistant
message (with the same API message id and every `tool_use`), then a user
message with a `"Fork started — processing in background"` result per
`tool_use` and a `<fork-boilerplate>…Your directive: <prompt>` text. On
`session/load`, the parent's text, tool calls and those results appeared
in the fork, and the fork's prompt showed the full boilerplate.
Replaying the copy also consumed the parent's tool-use cache entries, so
the parent's real Agent/parallel tool results were dropped. In the AIR
form, sibling forks were re-registered under the fork.
`readSubagentMessages` now drops everything before the directive message
and reduces it to the prompt (`stripForkPrefix`).
- **Lineage scan** (`subagentLineage`) ignores launch calls in a fork's
copied prefix, so it no longer attributes the parent's launches to the
fork.
- **Agent title** no longer shows `(high effort)` for a fork: a fork
ignores `effort` and runs at the parent's effort.
## Changelog review: nothing else to change
- **0.3.302: an omitted `permissionMode` now starts in `default` on
third-party providers.** The agent always passes `permissionMode`
explicitly, so this does not apply.
- **0.3.301: a full-disk login refresh now reports `invalid_request`
instead of `authentication_failed`/401.** This now maps to `bad_request`
rather than `auth_required`. That's the right outcome, since `/login`
cannot fix a full disk. Its text has no "Please run /login", so the
synthetic-login check does not catch it.
- **0.3.301: `setModel()` no longer fails at the usage limit.** This is
a free fix for model switches.
- **0.3.302: structured output refusals are now error results.** These
are covered by the existing result handling.
- **New APIs not adopted:**
- Per-tool `permission_policy` on sdk MCP servers: we register none.
- `reloadPlugins({ addPlugins })`: there's no plugin reload surface.
- `BUILTIN_TOOL_NAMES`/`LEGACY_TOOL_NAME_ALIASES`: `@alpha`, and nothing
here needs them.
- MCP `_meta.ui`: unused.
Verified: `npm run build`, `npm run check`, `npm run test:run` (3205
passed). New tests cover the fork replay trace (RFD form),
`stripForkPrefix`, the lineage scan with a fork transcript (it fails
without the fix), and the fork title.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.93.1-preview.6
|
||
|
|
ff01e06579 |
fix: update to ACP SDK 2.0.0 (#1386)
Updates `@agentclientprotocol/sdk` from 1.9.0 to 2.0.0. SDK 2.0.0 makes the default entrypoints stable-only and removes the legacy connection classes ([migration guide](https://github.com/agentclientprotocol/typescript-sdk/blob/main/MIGRATION_STABLE_UNSTABLE.md)). - Protocol imports move to `@agentclientprotocol/sdk/unstable` and `@agentclientprotocol/sdk/experimental/v2/unstable`. The agent uses unstable features (session forking, `systemPrompt`, turn usage, subagents, v2 draft extras), so this keeps the same protocol surface as 1.9.0. No wire changes. - The scenario schema validator loads `schema/v1/schema.unstable.json`, because the unversioned `schema/schema.json` export was removed. - `remote-control.test.ts` uses the repo's own `AcpClient` type instead of the removed `AgentSideConnection`. - Runtime code already used the `agent()` app API, so it needed no migration. Verified: `npm run build`, `npm run test:run` (3199 passed), `npm run check`, and a separate typecheck of `examples/`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.93.1-preview.5 |
||
|
|
e155ecb419 |
test: expect the v2 state snapshot after the replay end event (#1385)
`main` has failed CI since #1361, on one test: `ACP v2 prompts > ends a resume with replay with one end event for a client with loadReplayEnd`. Two PRs that merged separately collided: - #1366 made a v2 resume send a `state_update` snapshot after the replay and before the response ("v2 requires a snapshot of the foreground state after the replay", in `src/v2/agent.ts`). - #1361 added the `loadReplayEnd` end event and this test, which expected the end event to be the last update of the response. The test predates the snapshot, so each PR passed CI on its own. The snapshot is required by the v2 spec, so the code is right and the test is stale. The test now expects the end event followed by the snapshot, and still checks that the end event is sent once. Only `src/tests/acp-v2.test.ts` changes. ## Testing - `npm run check`, `npm run build` and `npm run test:run` pass (3199 passed, 30 skipped). - The same test fails on `main` at `bcf74c2` without this change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>v0.93.1-preview.4 |
||
|
|
bcf74c2fe8 |
fix: match OpenRouter model ids in the gateway model listing (#1365)
The model picker was empty behind OpenRouter: `normalizeModelId` did not match `anthropic/claude-opus-5.5:batch[1m]` to `claude-opus-5-5`. It now strips a leading `anthropic/` and a `:variant` suffix, and maps `5.5` to `5-5`.v0.93.1-preview.3 |
||
|
|
1420994ced |
feat: send one end event after the session/load history replay for AIR (#1361)
## What
A client that declares the AIR capability `loadReplayEnd` gets one extra
`session/update` after the history replay of `session/load`:
```json
{
"sessionId": "<loaded session>",
"update": {
"sessionUpdate": "session_info_update",
"_meta": { "jetbrains": { "air": { "version": 1, "loadReplayEnd": true } } }
}
}
```
The client declares the capability in
`clientCapabilities._meta.jetbrains.air.capabilities`, as it declares
the other AIR extensions. The agent does not advertise it.
## Why
ACP has no signal that the history replay of `session/load` is over,
other than the response. This adapter replays the transcript while the
Claude Code CLI starts, and the CLI start takes most of the load time.
So the history is whole long before the response. With this event, the
client can show the history at once.
The protocol side is the RFD
https://github.com/agentclientprotocol/agent-client-protocol/pull/2407.
## Design change
The first version of this PR marked each replayed notification with an
index (`loadSessionReplayIndex`) and the last one with
`loadSessionEndOfRecordedHistory`. To know the last one, it held the
latest update and queued every other outbound message of the session
behind it. That design is replaced with a capability-gated single end
event. The replay sends as before. Only one event is added at its end.
There are no indexes, no held update and no queue.
## When the event is sent
- After every replayed update, the subagent history that the parent
replay sends to the child sessions included, and before the
`session/load` response.
- Also for an empty history.
- For `session/load` of a session that starts while the replay runs, for
`session/load` of a session that is already live, and for the ACP v2
`session/resume` with `replayFrom: {type: "start"}`.
It is not sent:
- When the replay stops (the CLI start failed during the replay) or
fails.
- For a live update.
- To a client without the capability.
The update has no `title` and no `updatedAt`. A `null` there would clear
the value. The load can still fail after the event, for example when the
CLI start fails after the replay. The event means that the recorded
history arrived whole.
## Client status
JetBrains Air shows the history before the load answer only after the
Kotlin SDK delivers the updates of a loading session before the answer.
An SDK PR for this is in progress.
## Tests
- `create-session-options.test.ts`: the event comes once after the
replay and before the response; before the CLI start finishes; for an
empty history; for an already live session; not when the CLI start fails
part way; not when the replay fails; not without the capability. The
event has no `title` and no `updatedAt` key.
- `acp-agent.test.ts`: the subagent history from its own transcript file
comes before the event.
- `acp-v2.test.ts`: v2 `session/resume` with a replay ends with the
event before the response; a resume without a replay sends none.
- `docs/air-extensions.md`: a new "Load replay end" section, and the
capability and key rows.
v0.93.1-preview.2
|
||
|
|
424db87535 |
fix: resolve a skill's SKILL.md by its declared name, not by its directory name (#1372)
## Problem Claude Code advertises a skill by the `name:` of its `SKILL.md` frontmatter, which does not have to match the name of the directory — or of the symlink — that holds the file. `resolveSkillPath` joined the advertised name into each container path and only checked whether the result exists, so every such skill resolved to nothing. For an AIR client the consequence is visible: `getAvailableSlashCommands` attaches `_meta.jetbrains.air.kind = "skill"` and the `SKILL.md` path only when the path resolves, so the skill reached the client as a plain command and disappeared from the skill completion, while `/name` kept working. Symlinked skills hit this most often, because the link name is usually chosen independently of the skill's declared name. Reproduced with a project whose `.claude/skills/mismatch/SKILL.md` declares `name: frontname`, and with `.claude/skills/aliased2 -> …/target2` declaring `name: front2`: both were advertised without any `_meta`. The same happens in a large repo where `.claude/skills/rubymine` declares `name: rubymine-workflows`. ## Fix The existing join-by-name probe stays the first attempt, so nothing changes for the common layout. When it misses, the same containers are scanned, in the same order, for a `SKILL.md` whose frontmatter declares the requested name: - `statSync`/`existsSync` follow links, so a skill behind a symlinked directory is scanned like any other; - each container is listed per lookup, and a `SKILL.md` whose modification time has not changed keeps the name of the previous scan, so an unchanged file is parsed once; - only the frontmatter block of at most 8 KB is parsed, so a `name:` in the body is not a name; - a directory that is itself named like the skill still wins over another skill that merely declares the name. ## Tests Eight cases in `src/tests/tools.test.ts` cover a directory/declared-name mismatch, a symlink with a differing link name in both `.claude/skills` and `.agents/skills`, the precedence of the directory that carries the name, scoped and plugin spellings resolved by declared name, a file without frontmatter, and a declared name that changes after the first resolution. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Happy <yesreply@happy.engineering>v0.93.1-preview.1 |
||
|
|
dbf386d8d3 |
chore(main): release 0.93.0 (#1369)
🤖 I have created a release *beep* *boop* --- ## [0.93.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.92.0...v0.93.0) (2026-10-16) ### Features * add /remote-control command to start Remote Control for a session ([#1197](https://github.com/agentclientprotocol/claude-agent-acp/issues/1197)) ([v0.93.0 |
||
|
|
db9f50f021 |
feat: add /remote-control command to start Remote Control for a session (#1197)
Adds a `/remote-control [name]` command (alias `/rc`) so a session hosted over ACP can be continued from claude.ai/code or the Claude mobile app. Addresses #459. The CLI's own `/remote-control` is terminal-bound and isn't advertised to SDK hosts, and `remoteControlAtStartup` only applies to interactive sessions. The SDK does expose a runtime `enableRemoteControl(enabled, name?)` on the query object, which returns the session URL. See anthropics/claude-agent-sdk-typescript#460. - The adapter handles the command itself in `prompt()`, calls `enableRemoteControl(true, name)`, and replies with the session URL as an agent message. The turn ends without going to the model. - Failures (for example Remote Control disabled by policy) are reported to the user in the same way. - The command is added to `available_commands_update` only when the query exposes the method. - `enableRemoteControl` is not in the published `Query` types yet, so it is typed locally the same way as `mcpAuthenticate`. ## Testing - `npm run check`, `npm run build` and `npm run test:run` pass, including new tests in `src/tests/remote-control.test.ts`. - Ran the built agent through `examples/simple-client.ts` against a real Claude Max session: the command was advertised, `/remote-control` returned a working `claude.ai/code` URL, and prompts sent from the Claude iOS app ran in the session and were answered. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Noah Eisenbruch <noaheisenbruch@gmail.com> Co-authored-by: Andrey Bragin <andrey.bragin@jetbrains.com>v0.92.1-preview.2 |
||
|
|
f5967fd6b4 |
fix: hold a fork back from the session list until its session/fork response is out (#1368)
## Problem `session/fork` copies the transcript and then starts the forked session, which takes several seconds. Meanwhile the list subscription sees the new transcript and sends `_session/list/changes` with the fork row before the fork response. The client meets an unknown session and lists it as a session of its own. JetBrains Air then refuses the fork (IJAI-4105). ## Fix - `ForkHolds` (in `session-index/list-subscriptions.ts`, owned by the session index service) holds the row of a fork until the `session/fork` response is out: - by `forkedFrom`, for an unsent row, while the transcript is copied; - by the fork id after the copy. This covers a fork whose first record is over 64 KB, because the scan reads `forkedFrom` from the head only. - `session/list` leaves a held fork out too. - The v1 and v2 fork handlers run in `forking(...)`. A `setTimeout(0)` releases the rows after the SDK has written the response. - The holds live in the service, so a subscription that starts during a fork holds that fork too. ## Tests `holds a fork back until the response of its session/fork is out` (session-index.test.ts). It fails without the fix.v0.92.1-preview.1 |
||
|
|
af3d7f9335 |
chore(main): release 0.92.0 (#1358)
🤖 I have created a release *beep* *boop* --- ## [0.92.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.91.0...v0.92.0) (2026-10-16) ### Features * add the MCP server and tool to AIR tool calls ([#1359](https://github.com/agentclientprotocol/claude-agent-acp/issues/1359)) ([v0.92.0 |
||
|
|
68bf74f58e |
feat: update ACP SDK to 1.9.0 and adopt the v2 spec clarifications (#1366)
Updates `@agentclientprotocol/sdk` to 1.9.0 (schema v1.26.0, v2.0.0-alpha.9) and adapts the agent to the v2 spec clarifications in that release. ## Changes **Client-provided system prompt (unstable)** - Advertises `sessionCapabilities.systemPrompt` with `mode.override` on v1 and `session.systemPrompt` on v2. - Accepted on `session/new`, `load`, `resume`, and `fork`: - `append` adds the text after the Claude Code preset. - `override` replaces the preset with the text. Tools, MCP servers, permissions, and settings (including CLAUDE.md through `settingSources`) are unchanged. - An unsupported mode or non-text content fails with `invalid_params` before any session is created, reused, or forked. - Takes precedence over the `_meta.systemPrompt` extension. AIR custom instructions still follow the client text. - A different prompt on resume recreates a live session. Claude Code reuses its saved system prompt until the next compaction, which the RFD allows. **v2 session/resume state snapshot** - Sends the current foreground state (`idle`, `running`, or `requires_action`) after any replay and before the response, as the clarified spec requires. This applies even without a replay. **v2 replay of subagents** - No longer replays a child's past `running`/`idle` as current activity. Each replayed child gets one `state_update` with its last state after the replay. **v2 tool call `_meta` patches** - Reports carry only the top-level keys that changed. `claudeCode` is still sent whole, since clients don't merge nested objects. - A removed key is sent as `null`. - A tool call id that starts again removes the old keys. - A replayed tool call this connection has not reported sends `_meta: null` first, then the current keys. **Cancellation and close** - A `session/close` whose turns don't end within the teardown timeout settles them as cancelled. The client gets `idle` with `cancelled`, or `-32800` for a prompt never inserted, before the close response. - A prompt racing `session/close` ends as cancelled (`-32800` on v2) instead of `-32603`. On v1 it now returns `stopReason: cancelled`, matching a prompt queued just before the close. - A stream error after a cancel ends the work as `cancelled` instead of `error`. ## Not in this PR - After a URL-mode MCP elicitation is accepted, the session reports `running` right away. The spec asks for `requires_action` until the external interaction finishes, but a server may never send `elicitation_complete`, so this needs its own design. - `requires_action` is reported while one foreground subagent waits, even if other foreground work can continue. This is SHOULD-level in the spec. ## Testing - `npm run build`, `npm run check`, and `npm run test:run` pass. - Golden files changed because of the new capability and the resume snapshot. `compare.ts` allows the new capability against origin/main. - `session index cost > reads only the changed transcript for a subscription` failed intermittently locally (about 3 in 15 runs) on a loaded machine. This PR doesn't touch the session index, but watch it in CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.91.1-preview.5 |
||
|
|
26f144a2a5 |
fix: restore resumed subagents after an adapter restart (#1364)
After a restart the AIR subagent map is empty, so a SendMessage resume opened no generation and the child's messages stayed buffered under the old launch id. Restore the agent from the session history on a resume signal or on a message with an unseen parent_tool_use_id, with a generation id that can't collide with ids the client stored before.v0.91.1-preview.4 |
||
|
|
872e93f11f |
feat: add the MCP server and tool to AIR tool calls (#1359)
AIR gets `_meta.jetbrains.air.mcp = {server, tool}` on MCP tool calls,
so the MCP card does not depend on the title format.
The adapter splits the `mcp__<server>__<tool>` tool name the way Claude
Code does. `server` is the normalized name. Clients other than AIR get
nothing new.
Split from IJAI-872. The SDK does not expose MCP progress yet, so this
PR does not forward it.
v0.91.1-preview.3
|
||
|
|
78a29f58ba |
feat: publish the predicted follow-up prompt as _meta.promptSuggestions (#1356)
## What
The Claude Code CLI can predict the prompt the user is likely to send
next. This adapter asked for nothing and dropped the `prompt_suggestion`
message. It now asks for the prediction (`promptSuggestions: true`) and
forwards the one prediction of a turn.
ACP has no field for a composer prefill, so the value travels as
```json
{
"sessionUpdate": "session_info_update",
"_meta": { "promptSuggestions": { "suggestions": [{ "text": "Run the tests" }] } }
}
```
The key names no vendor, so another client can read it. AIR already
renders the first non-blank text as gray text in an empty composer,
where Tab fills the composer and sends nothing.
## Behavior
- `suggestions: []` clears the prediction the client holds: at the next
`session/prompt`, because that turn's prediction is spent, and on a
`conversation_reset`, because the transcript it was drawn from is gone.
- A prediction the next prompt outran — the user sent the next prompt
before the adapter read the prediction off the SDK stream — is dropped
instead of published mid-turn.
- A blank prediction never goes on the wire.
- The update is sent only to an AIR client, like the other extension
keys, so non-AIR traffic is unchanged (the key is added to
`AIR_ONLY_META_KEYS`; the `origin/main` scenario baselines stay
byte-identical).
- The CLI predicts nothing on the first turn, after an API error, in
plan mode, and at the usage limit, so a turn without a prediction is
normal. A caller can opt out with
`_meta.claudeCode.options.promptSuggestions: false`.
Documented in `docs/air-extensions.md`.
## Tests
`src/tests/prompt-suggestions.test.ts` covers the wire shape, the
publish after the turn's result, the clear at the next prompt, the
dropped stale prediction, the blank prediction, and a non-AIR client.
`src/tests/create-session-options.test.ts` covers the SDK option and the
caller opt-out.
`npm run check`, `npm run build` and the touched suites are green; the
full `test:run` has pre-existing flakiness on this machine
(session-index watchers, providers) unrelated to this change.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
v0.91.1-preview.2
|
||
|
|
910d83ceea |
fix: include subagent file edits in the file-change report (#1357)
## Summary `agentFileChangeReport` took its path list only from Claude's file checkpoints (`rewindFiles` dry run). A checkpoint tracks only the files that the root agent edits with its own file tool. So a file that only a subagent edited never appeared in the report. Air then showed no "Since last turn" row for it, and Last Turn review could not include it. - Collect the `file_path` / `notebook_path` of successful `Edit`/`Write`/`MultiEdit`/`NotebookEdit` results from subagent messages in the stream. Store them on the active turn. This also covers background subagents, because the turn stays open until they finish. - Merge these paths with the checkpoint list. When the checkpoint preview times out or is unavailable, report the subagent paths instead of `unavailable`.v0.91.1-preview.1 |
||
|
|
c0af592d25 |
chore(main): release 0.91.0 (#1333)
🤖 I have created a release *beep* *boop* --- ## [0.91.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.90.0...v0.91.0) (2026-10-15) ### Features * keep background subagents running after a parent cancel, for clients that can cancel them ([#1348](https://github.com/agentclientprotocol/claude-agent-acp/issues/1348)) ([v0.91.0 |
||
|
|
761b4c8359 |
fix: update Claude Agent SDK to 0.3.300 (#1355)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.296 to 0.3.300 (parity
with Claude Code v2.1.300), and `@anthropic-ai/sdk` from 0.133.0 to
0.134.0.
No code changes needed. The new types are an optional `property_order`
on elicitation requests, `nextFireAt` on the CronCreate/CronList
outputs, and `owned` on Artifact outputs. We don't need any of them.
Fixes picked up that affect this agent:
- `importSessionToStore` (used for session forks) no longer skips
transcript rows containing U+2028/U+2029 on Node 24.
- Resuming an SDK session after a compaction no longer loses the recent
messages or the compaction summary in some cases.
- Background subagents in auto mode no longer send their final report
twice or merge it into another turn.
- Queued content-block prompts merged into one turn are now separated by
a line break.
- `ScheduleWakeup` now fires while a background task is running.
Behaviour change: `MAX_THINKING_TOKENS=0` (sent as `thinking: { type:
"disabled" }`) now runs adaptive thinking on models that can't turn
thinking off.
`npm run build`, `npm run check` and `npm run test:run` all pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.90.1-preview.16
|
||
|
|
1a1f1a0e80 |
feat: mark session list rows of worktrees Claude Code made (agentWorktree) (#1352)
## What Marks `session/list` and `_session/list/changes` rows whose session runs in a linked Git worktree that Claude Code itself created (`claude --worktree`, the `EnterWorktree` tool), as `_meta.jetbrains.air.agentWorktree: true`. A new agent capability `sessionWorktrees`, advertised together with `sessionIndex`, tells the client that rows carry the field, so a missing field means "not made by the agent" rather than "unknown". ## Why With `includeWorktrees`, a client gets sessions from every linked worktree with nothing to tell them apart. A worktree Claude Code made for a session belongs to the project the user started it from; a worktree the user made by hand is a separate checkout. Only the agent knows which is which. ## How - The rule reads the transcript's `worktree-state` records: the latest one wins; the session counts only when `worktreeSession` is non-null and `enteredExisting` is not `true`. `environment.isWorktree` is not used, since plain git detection is also true for user-made worktrees. - The record is parsed in the existing head/tail transcript scan, so no extra file reads. The row is marked only when its `cwd` is in a linked worktree (`<common-dir>/worktrees/<name>`), never for the main checkout or a submodule. - The row's `cwd` stays the session's own directory. The field is `true` or absent. Proposed as `SessionInfo.agentWorktree` plus a `agentWorktrees` capability marker in the session list extensions RFD: agentclientprotocol/agent-client-protocol#2323. Docs: `docs/air-extensions.md`. ## Tests `src/tests/session-index.test.ts`: marked worktree, `enteredExisting`, no record (user worktree), a later `null` record, main checkout, `_session/list/changes` rows, capability negotiation, and scan windows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.90.1-preview.15 |
||
|
|
0c8de1380f |
fix: keep a live session that was never prompted when a resume or load changes its settings (#1353)
Found by running the ACP TCK's v2 suite against the adapter (ACP-ADDDIRS-202). The code is shared, so it also fixes v1's `session/load` and `session/resume`. ## The bug A `session/resume` or `session/load` can change a live session's settings: its `cwd`, additional directories, MCP servers, or the other settings in its fingerprint. When that happens, `getOrCreateSession` tears the live query down and starts a new one with `resume: sessionId`. Claude Code writes the conversation only at the first prompt, so for a session that was never prompted, the resume failed with `-32002` "Resource not found". The live session was already torn down by then, so the client lost a session it had a moment earlier. ## The fix The sign-out path already handled the same case: it tries `resume`, and on `resourceNotFound` it starts a fresh query under the same id with `reuseSessionId`. That logic is now one helper, `recreateLiveSession`, used by both: - **`getOrCreateSession`** uses it when the session was live. A session that was never prompted then starts a fresh query under its own id. It loses no history, because it had none, and it keeps its mode. - **A session that isn't loaded here** still resumes directly. If it has no stored conversation, that still fails with `-32002`, since the session really is unknown. - **`recreateSignedOutQuery`** keeps its own handling: if both attempts fail, it puts the old session object back. ## Checks - **ACP TCK v2** (`agentclientprotocol/acp-tck` at `fa00ccd`) against this build: ACP-ADDDIRS-202 now passes, where it failed before. ACP-ADDDIRS-201 and ACP-RESUME-201 to 205 still pass. - **Live v1 `session/load`**, Claude Code 2.1.296: create a session, then load it with a new additional directory before any prompt. - On `main`, the load failed with `-32002`. - With this fix, the load succeeds. A prompt then read a file that exists only in the new directory, and the transcript was saved under the same session id. - **Tests:** three new tests: - a live session that was never prompted starts a fresh query with its mode; - an unknown session still fails; - another failure of a live session's resume doesn't start a fresh query. Removing the fallback, applying it to every error, or applying it to sessions that aren't loaded each fails a test. The sign-out tests pass through the shared helper. - **Suite:** `npm run check`, build and all 3068 tests pass, with no golden changes. **Not covered:** a model chosen with `session/set_config_option` before the first prompt resets to the default when such a session is recreated, as it already did after a sign-out. The mode is kept.v0.90.1-preview.14 |
||
|
|
fadbe1881c |
fix: let session/delete succeed for a session without a transcript (#1351)
Found by running the ACP TCK's v2 suite against the adapter
(ACP-DELETE-201 and ACP-DELETE-203). The code is shared, so v1 clients
get the fix too.
## The bug
ACP says: "Deleting an already-deleted session, or a session that never
existed, **SHOULD** succeed silently." For a client that is neither AIR
nor a `sessionIndex` client, `session/delete` instead failed with
`-32603` "Internal error" in three cases:
- **A session this connection ran but never prompted.** Claude Code
writes the transcript at the first prompt. So the adapter closed the
live session, then the SDK delete threw "not found in any project
directory".
- **An unknown session.** The SDK delete throws the same "not found"
error.
- **An id that isn't a UUID.** The SDK delete throws "Invalid
sessionId".
## The fix
A new `deleteSessionIfStored` in `src/session-index/service.ts` handles
every other client's delete:
- An id that isn't a session id can't name a transcript, so it returns
`{}` without calling the SDK.
- The SDK's own "not found" means nothing is on disk, so that also
returns `{}`. It's recognised by the existing `isSdkNotFound`.
- Any other SDK failure, such as a permission error, still fails the
request.
The two AIR paths are unchanged, because `docs/air-extensions.md`
documents their errors: `-32002` for an unknown session on a
`sessionIndex` client, and the SDK's error for AIR without
`sessionIndex`. The doc now describes the plain client's behavior.
## Checks
- **ACP TCK v2** (`agentclientprotocol/acp-tck` at `fa00ccd`) against
this build: ACP-DELETE-201 and ACP-DELETE-203 now pass, where both
failed before. ACP-DELETE-202 is still skipped, because `session/list`
doesn't show a session that was never prompted.
- **Tests:** three new tests: a never-prompted live session, an unknown
session, and an id that isn't a UUID. Removing either rule, or
swallowing every error, fails a test. The existing delete tests now use
UUID ids, which is what real sessions have.
- **Suite:** `npm run check`, build and all 3068 tests pass, with no
golden changes.
v0.90.1-preview.13
|
||
|
|
ba3c7be05e |
feat: keep background subagents running after a parent cancel, for clients that can cancel them (#1348)
Stacked on #1347, which gives each running child the `cancel` capability. Merge #1347 first; this PR's base is its branch. ## What changes On a client that gets the subagents RFD form (v1 with `clientCapabilities.subagents` and not AIR, and v2), `session/cancel` on the parent now spares background subagents. They keep running, as acp#2308 prescribes ("a background child that the cancelled work did not wait on may continue"), and the client can stop each one with its child's `cancel` from #1347. Foreground subagents that the turn waits on still stop. Plain v1 clients and AIR are unchanged: the interrupt still stops their background subagents, since they have no way to stop one. ## How - **The declaration.** Claude Code's interrupt kills background subagents and workflows unless the query declares `perTaskStopAffordance`. The SDK describes it as "this consumer renders a per-task stop control", and an RFD client now has that control. The adapter declares the option for those clients. It comes before `_meta.claudeCode.options`, so a client can turn it off. - **Workflows.** The declaration also spares background workflows (`local_workflow`). A workflow's agents are not subagent tasks and get no child session, so a client can't stop a workflow unless it is AIR with `asyncTasks`. When a session declares the option and the client has no such control, `session/cancel` stops each live background workflow with `Query.stopTask` after the interrupt. Workflows therefore behave as they did before. - **Background commands** survive an interrupt with or without the declaration (checked live and in the CLI code), so nothing changes for them. Where I read Claude Code 2.1.296's CLI: without the declaration, the interrupt runs a kill of every running background `local_agent` and `local_workflow` task. With it, the interrupt kills only observer tasks. Adapter interrupts name no prompt, so this runs on every `session/cancel`, including one with no turn running. The declaration's only other effect is on requests with `interrupt_if_running`, which the adapter never sends. ## Checks **Live, Claude Code 2.1.296, through the built adapter.** Every update was checked against the schema, and no run had a violation or left a tool call open. Survival was checked by whether the background command's process was alive 2 s after the cancel. | Client | Background work at the cancel | Result | |---|---|---| | v1 RFD | subagent running a command | Prompt `cancelled` 9 ms after the cancel. The child kept running, completed its command and reported `idle end_turn`. Claude Code's follow-up cycle then streamed to the root. | | v1 RFD | subagent with an open permission request | The request stayed open through the cancel. The client allowed it 5 s later, and the child ran the command and ended `idle end_turn`. | | v1 RFD | workflow | The workflow and its command stopped, with no follow-up cycle. Same as the plain client. | | v1 plain | subagent | Unchanged: the interrupt killed the child, and its command's tool call ended `failed`. | | v1 plain | workflow | Unchanged: stopped, no follow-up. | | v2 | subagent | Root `idle cancelled` 11 ms after the cancel. The child kept running and later reported `idle end_turn`, and the root went `running`, then `idle end_turn`, for the follow-up. | | v2 | workflow | Stopped, no follow-up. | **Direct SDK probes** decided the design: - With the declaration, an interrupt spares a background workflow. - Without it, the interrupt kills the workflow. - A `stopTask` on a workflow, before or after the interrupt, stops it and its command, and Claude Code starts no follow-up cycle, the same as when the interrupt killed it. **Tests:** - Option tests: the declaration for each client form, a caller's override, and when a cancel stops workflows. - Cancel tests: only workflows are stopped, after the interrupt and only when the flag is set. - Removing any of the eight rules fails a test. - `npm run check`, build and all 3020 tests pass, with no golden changes. ## Not covered - A paused background workflow (one Claude Code restored on a resume) would also be stopped at a cancel, where the interrupt left it alone. I didn't check whether such a workflow reports `task_started`. - Standard ACP has no control to stop background work that is not a session, such as a workflow or a background command. Only AIR has one, through its async tasks extension.v0.90.1-preview.12 |
||
|
|
aeb69e7ed1 |
feat: let a client cancel one subagent with session/cancel on its child session (#1347)
A client on the subagents RFD can now stop one subagent without
cancelling the turn. Each running child is announced with `capabilities:
{ cancel: {} }`, and `session/cancel` with the child's session id stops
that child's current work.
## What Claude Code does
From direct SDK probes of `Query.stopTask(taskId)` and runs through the
adapter (Claude Code 2.1.293 and 2.1.296):
| Case | What Claude Code sends |
|---|---|
| Foreground subagent running a command | At once: `task_updated`
`killed` and `task_notification` `stopped` for the subagent, `stopped`
for its command's task, the command's interrupted result, then the Agent
call's interrupted result (`non_execution_kind: "interrupted"`). The
parent's turn goes on and ends normally. |
| Background subagent | The same events and result, then Claude Code's
follow-up cycle about the stop. |
| Subagent with an open permission request | The request is withdrawn
(its `AbortSignal` fires), and both calls get results. |
| Subagent waiting on its own foreground subagent | The grandchild is
stopped too, with its own `killed` and `stopped`, which is the cascade
the RFD requires. |
## The change
- **The grant:** `announceNativeSubagent` grants `cancel` to each live
RFD child. A replayed child gets no capabilities, since it no longer
runs (the RFD's freshness rule for reconnection). A routing-only
ancestor that a restore announces gets none either.
- **The cancel:** `cancel` checks first whether the session id is a
child of one of its sessions (`NativeSubagentRuntime.cancelChild`), and
if so calls `Query.stopTask` with the child's agent id. It sends no
interrupt, so the parent's work goes on. A stop that Claude Code refuses
is logged.
- **The report comes from the SDK's events,** `killed` or `stopped`,
which `finishTask` already turns into `idle` with `cancelled`. Accepting
the stop is no evidence, as the RFD says, and a cancel that races with a
child's end does not rewrite its outcome.
- **Requests:** a request that the stop withdraws reports no `running`
before `idle cancelled`. `awaitingUser` now counts cancels of the child
and of its ancestors, as it counted the root's (#1337). An ancestor's
cancel stops the foreground subagents under it.
- **Unchanged:** AIR keeps its draft and grants nothing, and a cancel of
a root session works as before.
## Evidence
**Live, through the adapter** (Claude Code 2.1.296, Sonnet), every
update checked against the schema, with no violations:
| Run | Child | Parent |
|---|---|---|
| v1 RFD, foreground child running a command | `running`,
`requires_action`, `running`, `idle cancelled` 9 ms after the cancel;
its command `failed` | Agent call `failed`; turn ends `end_turn` |
| v1 RFD, background child while v1 holds the turn | `idle cancelled`;
its command `failed` | the held turn settles with `end_turn` |
| v1 RFD, child with its permission request open | `requires_action`,
then `idle cancelled`, with no `running` between | turn ends `end_turn`
|
| v2, foreground child | `idle cancelled` on the root's stream and its
own | Agent call `cancelled`; root `idle end_turn` |
No tool call was left open in any run.
**Tests:**
- `native-subagents.test.ts`:
- `cancelChild` stops the child's task and reports nothing itself, and
the SDK's `killed` reports `idle cancelled`;
- no stop for a session that is no child, or for AIR;
- no `running` for a request that the child's own cancel withdraws, or
that its parent's cancel withdraws.
- `acp-agent.test.ts`: `session/cancel` on a child calls `stopTask`, not
`interrupt`, and the turn ends `end_turn`; a refused stop is logged;
nothing happens for an unknown session id or an AIR child.
- `acp-v2.test.ts`: the same over v2, with the child's `idle cancelled`
on both streams.
- **Golden files:** the six v2 subagent golden files gain `capabilities:
{ cancel: {} }` on each announcement, and nothing else. No AIR golden
file changes.
- **Mutation checks:** removing each of these fails at least one test:
- the grant;
- the routing to the child;
- the child's cancel count;
- the ancestors in the count;
- the AIR exclusion;
- the error handling.
So does reporting `idle cancelled` as soon as the stop is accepted.
`npm run check`, build and the full suite (3014 tests) pass.
## Not changed, and a decision
- **A parent cancel still stops background subagents.** Claude Code does
that because the adapter does not declare the SDK's
`perTaskStopAffordance` option. Declared, an interrupt spares background
agents and background shell commands (checked live: both ran to
completion after the interrupt), as
agentclientprotocol/agent-client-protocol#2308 prefers. Background shell
commands would then outlive a cancel too, and ACP has no control to stop
them, so declaring it is left for a separate decision.
- **Order:** Claude Code sends the stopped child's command result right
after its `killed` event, so the client sees `idle cancelled` a few
milliseconds before that call ends. The RFD has the client mark the
child's unfinished calls cancelled at that point and still accept later
updates.
- **On v2,** the stopped child's command ends `failed`, not `cancelled`:
Claude Code's result for a subagent's command carries no non-execution
kind.
|
||
|
|
e8eb9d340f |
feat: narrow models to what the providers/set gateway lists (#1340)
### Summary Addresses [IJAI-1783](https://youtrack.jetbrains.com/issue/IJAI-1783). An organization can restrict the models its JetBrains gateway serves, but a session routed through `providers/set` offered the whole Claude Code catalogue, so the picker showed models the gateway then refused. `providers/set` now lists the gateway's models before it responds, and the model picker offers only the SDK models the gateway lists. - `anthropic`: `GET {baseUrl}/v1/models?limit=1000`, paginated by `has_more`/`last_id`, with the provider headers and `anthropic-version`. - `bedrock`: `GET {baseUrl}/foundation-models` (`ListFoundationModels`, `modelSummaries[].modelId`), the listing the JetBrains gateway answers on its Bedrock route. Only `anthropic.` ids count. The cross-region prefix `global` when listed, else that of the first listed id, becomes the session's `ANTHROPIC_BEDROCK_REGION_PREFIX`, so the CLI calls the models under the prefix the gateway serves; ids of other prefixes, and SDK rows resolved to another prefix, are dropped. - Rows match by `resolvedModel` (or `value`) without dates, `-vN:M` suffixes, `[1m]` hints and provider prefixes; the narrowing runs after the `availableModels` allowlist. When the gateway does not list the model a session runs (the dropped default, or a resumed or recreated session's last model), the session switches to the first listed one with `setModel`, so the CLI does not keep running an unlisted model. - The listing runs in the provider-update queue, so `providers/set` and `providers/disable` apply in the order they arrive; logout also clears a `providers/set` that is still listing. - A 4xx answer means the gateway has no listing: `providers/set` succeeds and nothing is narrowed. Any other failed listing (network error, 5xx, unexpected body, 10 s timeout) fails `providers/set` with `internal_error`, and the previous provider stays in effect. An invalid header or a base URL with credentials fails without repeating the value. A listing that matches no SDK model leaves the session with no model to offer: there is no model config option. - `vertex`, gateways set through `authenticate`, and native routing are not listed. Setting the same gateway again reuses its listing; `providers/disable` and logout drop it. - `docs/model-configuration.md` describes the narrowing. ### Not covered Background haiku requests, subagents, and a `/model` typed into the prompt choose their own model and are not checked against the listing. ### Verification ```bash npm run check npm run build npm run test:run -- --no-file-parallelism # 2964 passed, 5 failed, 30 skipped; all 5 in session-index.test.ts, flaky on main too ``` Tests stub `fetch` (`src/tests/setup-no-network.ts` answers an empty listing by default) and cover the listing requests and pagination, the Bedrock region prefix and its env, region-aware matching, the dropped default, the failing `providers/set`, and the empty picker. The JetBrains Bedrock gateway's `/foundation-models` answers `global.anthropic.claude-{haiku,opus,sonnet}-5-5` (seen through Junie, which lists the same route); this adapter has not yet been run end to end against it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>v0.90.1-preview.11 |
||
|
|
d528647162 |
fix: report no running for the session's own request that a cancel withdraws, on ACP v2 (#1346)
## Problem On ACP v2 the adapter reports a session's foreground work as `state_update`s: `running`, then `requires_action` while a permission request or question that blocks the work is open, then `running` again, and finally `idle` with a stop reason. A request that ends because `session/cancel` or `session/close` stopped the work is not an answer, but it still reported `running`. #1337 fixed that for a subagent's child session and listed the session's own request under "Not covered". That request did not change, and since #1338 neither did its AskUserQuestion, which now ends at the abort too. On `main`, with Claude Code 2.1.296, v2, a Bash permission request that the client holds open, and then a cancel or a close: | What stops the work | States the session reported | | --- | --- | | `session/cancel` | `running`, `requires_action`, **`running`**, `idle cancelled` | | `session/close` | `running`, `requires_action`, **`running`**, `idle cancelled`, all before the close response | Claude Code's interrupt withdraws the request, so the SDK aborts the signal of the `canUseTool` call, the request's `finally` calls `syncAwaitingUser`, and with no blocking request left it reports `running`. The result that ends the work comes 2 to 9 ms later in these runs, so the client shows work that is being stopped as working. ## Cause `syncAwaitingUser` reported `running` whenever the last blocking request ended, whatever ended it. A request ends when the user answers it or when Claude Code withdraws it, and a withdrawal alone does not say whether the work was stopped. A `PermissionRequest` hook that answers also makes Claude Code withdraw the request, and the work goes on (9 s more in a live run), as #1337 found. So "a withdrawn request reports no `running`" is wrong. The rule that fits is #1337's: no `running` when the request was withdrawn after a cancel that came while it was open. ## Change - `cancelTurns` (called for `session/cancel`, and by the teardown of `session/close`) marks each request that is open, in `Session.openUserInputs`. - `withPendingUserInput` takes the request's `signal`. When the request ends with its signal aborted and the mark set, `syncAwaitingUser` stops waiting but reports no `running`. The four callers pass their signal: permission requests, AskUserQuestion, MCP elicitation, and the refusal fallback prompt. (#1337 counts the parent's cancels and compares the count when the request ends. Here each open request is already an entry of `openUserInputs`, so the cancel marks it. Same rule.) - Everything else is as before: - an answered request reports `running`, also after the cancel; - a withdrawal without a cancel reports `running`; - with two requests open, only the last one to end reports, by how that one ended; - a request that opens after the cancel is judged on its own, so a hook that withdraws it still reports `running`. - A foreground subagent's request blocks the turn, so it behaves like the turn's own. A background subagent's request does not block it (`blocksForeground`), so the root's states do not change for it. #1337's child states are unchanged. - `ForegroundEvents.running` and `v2Foreground` say when `running` is not reported. v1 reports no session state, so nothing changes there. AIR is unchanged: the scenario golden files do not change. ## Evidence Live runs against Claude Code 2.1.296 (Sonnet), driving `dist/index.js` over stdio as a v2 client (`CLAUDE_AGENT_ACP_EXPERIMENTAL_V2=1`) that holds the request and ignores `$/cancel_request`, with the session's cwd in default permission mode. Every update and request validated against the SDK's v2 JSON schema: 0 violations in all runs. The root's states: | Run | `main` | This PR | | --- | --- | --- | | `session/cancel`, Bash permission request | `running`, `requires_action`, `running`, `idle cancelled` | `running`, `requires_action`, `idle cancelled` | | `session/cancel`, AskUserQuestion | `running`, `requires_action`, `running`, `idle cancelled` | `running`, `requires_action`, `idle cancelled` | | `session/close` | `running`, `requires_action`, `running`, `idle cancelled` | `running`, `requires_action`, `idle cancelled` | | `session/cancel`, request of a foreground subagent (#1337's run) | root as above; child `running`, `requires_action`, `idle cancelled` | root `running`, `requires_action`, `idle cancelled`; child the same | | `PermissionRequest` hook allows after 3 s, no cancel | `running`, `requires_action`, `running`, `idle end_turn` | the same | | the client answers after 2 s | `running`, `requires_action`, `running`, `idle end_turn` | the same | | `kill -9` of Claude Code, no cancel | `running`, `requires_action`, `running`, `idle error` | the same (see below) | The hook run is why a withdrawal alone cannot mean "stopped": Claude Code withdrew the request 3 s in, and the command then ran 9 s more. A v1 run of the cancel (no state updates there) is identical on both builds. Tests, in `acp-v2.test.ts`, through the v2 client app, which validates every update: - For each of the four kinds of request (permission request, AskUserQuestion, an MCP server's elicitation, the refusal fallback prompt): a `session/cancel` withdraws it, a `session/close` withdraws it (the harness gets a `close` hook, as the SDK's cleanup of a closed query aborts open requests), and Claude Code withdraws it without a cancel. - The user answers after the cancel; a request that opens after the cancel and is then withdrawn; two requests open together (both withdrawn by the cancel, and one withdrawn and the other answered, in each order); a request of a cycle that Claude Code started. - Subagents: a foreground subagent's request also holds the root (an assertion added to #1337's test); a background subagent's request changes nothing of the root's states, alone and next to the turn's own request. 13 of them fail on `main` (12 new tests and the extended one); the other 8 new tests pass there and guard what must not change. Removal checks, each run against `acp-v2.test.ts`: | Removed or changed | Tests that fail | | --- | --- | | no rule: the end always reports `running` | 13 | | the cancel marks no request | 13 | | a mark is enough, the signal is not required | 2 (answered after the cancel) | | the simple rule "any withdrawal means stopped" | 5 (the hook cases, and the request after the cancel) | | `session.cancelled` in place of the mark of a cancel that came while the request was open | 1 (the request after the cancel) | | sticky: after one request was withdrawn by a cancel, no later request reports `running` | 1 (answers the one the cancel did not withdraw) | | permission requests do not pass their signal | 6 | | AskUserQuestion does not pass its signal | 3 | | MCP elicitation does not pass its signal | 2 | | the refusal fallback prompt does not pass its signal | 2 | `npm run check`, `npm run build` and `npm run test:run` pass (2999 tests passed, 30 skipped; the scenario golden files do not change). ## Not covered - **A stream end without a cancel.** If Claude Code exits by itself, the SDK's cleanup aborts every request before the consumer reads the end of the stream, and nothing at that moment tells it from a hook's withdrawal. Live (`kill -9` of the CLI, table above): `running` 2 ms before the `idle`, and the session is dead right after. Left as it is, for the reasons #1337 gives for children. - The rule assumes, like #1337's, that the work reports its end right after such a withdrawal. A turn always does (the force-cancel backstop ends a wedged one); a cycle with no active turn has no such backstop. - Open PRs #1344 and #1345 also change `src/acp-agent.ts`; this branch merges cleanly with each (checked with `git merge-tree`).v0.90.1-preview.10 |
||
|
|
594aa8c955 |
fix: close the tool calls that a cycle Claude Code started abandoned, at its end (#1344)
A model cycle that Claude Code starts on its own now closes, at its end, the tool calls that Claude abandoned before they ran, as a turn's end already does. ## The bug Claude can start a tool use and never finish the message that holds it, for example when the stream is cut off. The tool never runs, and no result comes. At a turn's end, `settleActive` closes such a call (`closeUnfinished`: "Claude stopped this tool call before it ran.", `failed` on v1, `cancelled` on v2). A cycle that Claude Code started on its own, such as its follow-up after a background command finished, has no turn. So nothing closed its abandoned calls, and the client kept them open. ## The fix `closeCancelledCycleToolCalls` becomes `closeCycleToolCalls` and runs at every end of a cycle: its result, or an idle that no result precedes. - **After a cancel:** unchanged (#1339, #1342). - **At any other end:** every call that ran already has its result. What is open is a call of the main conversation that was never dispatched, which is closed with the same report a turn's end sends. A subagent's calls are not the cycle's: a background subagent runs on. On v2 the report comes before the cycle's `idle`. ## Evidence **Tests:** - v1, a cycle that runs a command, starts a background subagent that is streaming a tool use as the cycle ends, and abandons a tool use: only the abandoned call is closed, once. The command keeps its one result, and the subagent's call stays open. - v1, a cycle that ends at an idle with no result (the stream dropped mid-cycle): its abandoned call is closed there. - v2: the abandoned call goes `pending`, then `cancelled`, before the cycle's `idle end_turn`. - **Mutation checks:** each of these fails at least one test: - no closing at a normal end; - closing a subagent's calls; - closing dispatched calls (an existing test catches it); - closing only at the result, not at the idle. `npm run check`, build and the full suite (2982 tests) pass, with no golden changes. **Not checked live:** Claude abandons a tool use only when something cuts its message off, such as a steer or a dropped stream, which a live run cannot set up reliably. |
||
|
|
bcc024d2c9 |
fix: deliver the output of a background subagent that a cancel did not stop (#1345)
After a cancel, the output of a background subagent that the cancel did not stop now reaches the client: its tool results, its new tool calls, and its permission requests. ## The bug The adapter has one rule for work that a cancel does not stop, as agentclientprotocol/agent-client-protocol#2308 asks: a background subagent that still runs (`runsInBackground`). The closing at the cancel leaves that subagent's tool calls open for their results (`closedAtCancel`). But the window after the cancel dropped the same subagent's output, results included: - **A subagent that completes after the cancel** kept its calls open. Its commands' results came after the cancel was reported and were dropped, and since it was not stopped, nothing closed them. - **A subagent that runs on** after the cancel showed nothing more: new tool calls were dropped, and its permission requests were denied as the stopped work's (#1334). Claude Code currently stops background subagents at an interrupt, as live runs show, so today this applies only to the output that comes before the SDK reports a subagent's end. It decides the rest once Claude Code lets them run. ## The fix `isRunningBackgroundSubagentOutput` uses the closing's predicate for the three places the window applies: - the drop of streamed output; - the record that `canUseTool` and the v2 preview hook read (#1334); - the guard of the user/assistant messages. A subagent's output after the SDK reported its end is still dropped, and #1336 closes its calls. ## Evidence **Tests** (`src/tests/incomplete-tools.test.ts`): - **A command's own result,** for a subagent that completes after the cancel, reaches the client, for a held turn and with no turn. This replaces "leaves the command of a subagent that completed after the cancel as it is", whose fixture sent no result at all. - **New work after the cancel was reported:** a command's streamed start, complete message and result reach the client. - **Permission requests after the cancel:** they are allowed, for a tool use that streamed first and for one that only its complete message holds. - **Unchanged:** a stopped subagent's late result is still dropped, and its call closed. - **Mutation checks:** removing each of the three uses, or widening the predicate to every subagent, fails at least one test. **Live, Claude Code 2.1.296,** a v1 cancel of a turn held for a background subagent that runs a command, on a plain client and an RFD client: Claude Code stopped the subagent, and its command was closed after the cancelled response, as on `main`. No schema violations. A subagent that survives a cancel cannot be set up live today. `npm run check`, build and the full suite (2983 tests) pass, with no golden changes. ## Not covered - **A foreground subagent nested in a background one:** its own Agent call is not backgrounded, so the closing closes its calls at the cancel, and the window drops its output, both as before. The two stay consistent.v0.90.1-preview.9 |
||
|
|
b4788d61ea |
fix: end the tool calls of a cycle that Claude Code started and a cancel stopped, on v1 (#1342)
On v1, when a cancel stops a model cycle that Claude Code started on its own, the cycle's open tool calls now end. Before, they stayed `pending` for good. ## The bug Example, from a live run (Claude Code 2.1.296, a v1 client): 1. A prompt starts a background command, and its turn ends. 2. The command finishes, and Claude Code starts a cycle on its own: it launches a foreground subagent, which runs a command. 3. The client sends `session/cancel` while no prompt runs. Claude Code stops both and sends the Agent call's interrupted result (`user-rejected`). The client never got it, and the Agent call stayed `pending`. Before #1336, the subagent's command stayed `pending` too. **Why:** the window after a cancel drops output once the client has been told that the stopped work ended (#1313). On v1 that report is the prompt response, read as "no active turn". A cycle never has a turn, so from the cancel on, everything it sent counted as late output, including Claude Code's results for its own tool calls. v2 tracks a cycle as foreground work and closes its calls before `idle cancelled` (#1339). v1 tracked nothing. ## The fix - **The agent tracks foreground work on every connection.** Only v2 reports it, through the `foreground` option; v1 gets events that do nothing (`UNREPORTED_FOREGROUND`). The rules for when a cycle starts and ends are the ones v2 already uses. - **On v1, a cycle's end is its result or its idle** (`workEndReported`). Until then, Claude Code's interrupted results reach the client. At that end, `closeCancelledCycleToolCalls` closes the calls that got none, as on v2. After it, the window drops what comes, as before. A v1 turn's rule is unchanged: no active turn. - **A held turn's follow-up shares the held turn's closing.** v1 holds a turn for its background subagents, and the held turn's follow-up is a cycle too, now on v1 as well. When a second cancel settled the held turn in `cancelTurns` while the cycle's closing was in flight, the two ran separate passes. The response then came before the cycle's first report, and on v2 a call could be closed twice. `closeCancelledCycleToolCalls` now uses the held turn's closing (`Turn.closingCancelledToolCalls`), which every settle of the turn waits for. The existing test "closes each tool call once, before the response, when a cancel settles a held turn that the consumer is closing" caught this. ## Evidence **Live, Claude Code 2.1.296, the scenario above**, every update checked against the schema (no violations): | Client | Agent call | Subagent's command | |---|---|---| | v1, before | `pending` | `failed` (closed by #1336) | | v1, this PR | `failed`, with Claude Code's own `user-rejected` result | `failed`, closed at the cycle's end | | v1 RFD client, this PR | the same | the same, in the child's session | | v2, this PR | `cancelled`, before `idle cancelled` | `cancelled`, before `idle cancelled` (unchanged) | A v1 cancel with a queued prompt, followed by the next prompt, still left no tool call open, and the next prompt's Write asked for its permission and ran. **Tests** (`src/tests/incomplete-tools.test.ts`): - **#1339's test for v1 now asserts the fix:** a cycle's command gets Claude Code's interrupted result. - **New:** the live scenario, where the Agent call gets Claude Code's result and the subagent's command is closed; and a cycle that ends at an idle with no result, where its call is closed at the idle and Claude Code's later result is dropped. - **#1336's "no turn" fixture** launched the background subagents in a cycle it never ended before the cancel, so v1 now delivered Claude Code's real result there. The fixture now ends that cycle, which is the "cancel while nothing runs" case the tests mean. - **Mutation checks:** each of these fails at least one test: - the v1 rule without the cycle; - no cycles on v1; - no foreground tracking on v1; - a separate closing for a held turn's follow-up. `npm run check`, build and the full suite (2979 tests) pass, with no golden changes. ## Not covered - A cycle that ends normally with a `tool_use` that Claude abandoned keeps that call open. - A subagent that completes after a cancel keeps its calls open.v0.90.1-preview.8 |