876 Commits

Author SHA1 Message Date
acp-release-bot[bot]
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))
([739dde3](739dde3ae5))
* report the model of each native subagent
([#1432](https://github.com/agentclientprotocol/claude-agent-acp/issues/1432))
([c319e5e](c319e5e542))


### Bug Fixes

* do not hold an AIR load replay until the CLI starts
([#1433](https://github.com/agentclientprotocol/claude-agent-acp/issues/1433))
([eb9d63e](eb9d63e5fd))
* refine RFD child models after a load, and retry a failed model update
([#1434](https://github.com/agentclientprotocol/claude-agent-acp/issues/1434))
([9a8bf71](9a8bf71d45))
* send subagents' current state after a load response
([#1423](https://github.com/agentclientprotocol/claude-agent-acp/issues/1423))
([17a967b](17a967b4da))
* update to Claude Code 2.1.310
([#1435](https://github.com/agentclientprotocol/claude-agent-acp/issues/1435))
([2f3c6bb](2f3c6bb7b3))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.96.0
2026-10-26 09:50:05 +00:00
Ben Brandt
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
2026-10-26 10:46:44 +01:00
nikita-ashihmin
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
2026-10-25 23:24:19 -07:00
nikita-ashihmin
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
2026-10-25 23:05:36 -07:00
nikita-ashihmin
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
2026-10-25 22:37:14 -07:00
Ben Brandt
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
2026-10-25 00:14:54 +02:00
Ben Brandt
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
2026-10-24 23:28:11 +02:00
acp-release-bot[bot]
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))
([9b3a5c8](9b3a5c891c))
* report read-only subagent configuration
([#1417](https://github.com/agentclientprotocol/claude-agent-acp/issues/1417))
([6ab670b](6ab670baae))
* report RFD delegations as linked session messages
([#1405](https://github.com/agentclientprotocol/claude-agent-acp/issues/1405))
([59e292e](59e292e29f))
* serve experimental ACP v2
([#1400](https://github.com/agentclientprotocol/claude-agent-acp/issues/1400))
([4d378f9](4d378f93f3))
* show messages typed in other Remote Control clients
([#1371](https://github.com/agentclientprotocol/claude-agent-acp/issues/1371))
([5b11f47](5b11f470b1))
* update to Claude Code 2.1.309
([#1413](https://github.com/agentclientprotocol/claude-agent-acp/issues/1413))
([a1521b6](a1521b6196))


### Bug Fixes

* bump fast-uri from 3.1.8 to 3.1.9
([#1410](https://github.com/agentclientprotocol/claude-agent-acp/issues/1410))
([0fe22ae](0fe22ae5a1))
* bump postcss from 8.5.30 to 8.5.31 in the minor group
([#1403](https://github.com/agentclientprotocol/claude-agent-acp/issues/1403))
([ecaa2c9](ecaa2c935b))
* bump the minor group across 1 directory with 2 updates
([#1416](https://github.com/agentclientprotocol/claude-agent-acp/issues/1416))
([f7d6030](f7d6030c04))
* IJAI-3744 keep a session running while its background tasks are open
([#1399](https://github.com/agentclientprotocol/claude-agent-acp/issues/1399))
([e9f986b](e9f986b8a9))
* publish the session title as soon as the CLI has it
([#1406](https://github.com/agentclientprotocol/claude-agent-acp/issues/1406))
([50cd76e](50cd76e78d))
* publish turns that Claude Code starts after end_turn as an async task
in v1
([#1390](https://github.com/agentclientprotocol/claude-agent-acp/issues/1390))
([a1d0eee](a1d0eeeaad))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.95.0
2026-10-23 21:58:47 +02:00
Ben Brandt
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
2026-10-23 21:51:35 +02:00
dependabot[bot]
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 &quot;MUST / SHOULD&quot;
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="56e6b1bb89"><code>56e6b1b</code></a>
chore: Release API 1.4.1, SDK 1.10.0, Experimental 0.36.0 (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3654">#3654</a>)</li>
<li><a
href="ecb5ebe86e"><code>ecb5ebe</code></a>
fix: Minor typing issues for web sandbox (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3660">#3660</a>)</li>
<li><a
href="48ecf229eb"><code>48ecf22</code></a>
feat(sdk-trace-base): add dropped attributes and events count on span
(<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3576">#3576</a>)</li>
<li><a
href="95bf6fc7cc"><code>95bf6fc</code></a>
fix(build): add <code>example/http</code> to ignore list in
<code>update-ts-configs</code> (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3657">#3657</a>)</li>
<li><a
href="3a2ca58075"><code>3a2ca58</code></a>
feat: add unit to view instrument selection criteria (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3647">#3647</a>)</li>
<li><a
href="ebc8575a0c"><code>ebc8575</code></a>
chore(jeager-exporter): deprecate jaeger exporter (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3585">#3585</a>)</li>
<li><a
href="2f715bd78d"><code>2f715bd</code></a>
feat: allow to add attributes from grpc metadata in the patched server
(<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3589">#3589</a>)</li>
<li><a
href="cbbdfd3798"><code>cbbdfd3</code></a>
fix(sdk-node): update instrumentations once MeterProvider is initialized
(<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3624">#3624</a>)</li>
<li><a
href="4a4484a910"><code>4a4484a</code></a>
chore: update http example (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3651">#3651</a>)</li>
<li><a
href="494efffcf4"><code>494efff</code></a>
fix(otlp-exporter-base): fix changelog for merged retry pr (<a
href="https://redirect.github.com/open-telemetry/opentelemetry-js/issues/3650">#3650</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/open-telemetry/opentelemetry-js/compare/v1.9.1...v1.10.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `jose` from 6.2.12 to 6.2.13
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/panva/jose/releases">jose's
releases</a>.</em></p>
<blockquote>
<h2>v6.2.13</h2>
<h3>Refactor</h3>
<ul>
<li>align terminology with JOSE specifications (<a
href="bafcb74f5f">bafcb74</a>)</li>
<li>preserve underlying error causes (<a
href="724ba24b5a">724ba24</a>),
closes <a
href="https://redirect.github.com/panva/jose/issues/908">#908</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/panva/jose/blob/main/CHANGELOG.md">jose's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/panva/jose/compare/v6.2.12...v6.2.13">6.2.13</a>
(2026-10-20)</h2>
<h3>Refactor</h3>
<ul>
<li>align terminology with JOSE specifications (<a
href="bafcb74f5f">bafcb74</a>)</li>
<li>preserve underlying error causes (<a
href="724ba24b5a">724ba24</a>),
closes <a
href="https://redirect.github.com/panva/jose/issues/908">#908</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="316b7f2cb4"><code>316b7f2</code></a>
chore(release): 6.2.13</li>
<li><a
href="8106395886"><code>8106395</code></a>
chore: bump packages</li>
<li><a
href="724ba24b5a"><code>724ba24</code></a>
refactor: preserve underlying error causes</li>
<li><a
href="b112a5e6e1"><code>b112a5e</code></a>
chore: bump packages</li>
<li><a
href="2a3a525968"><code>2a3a525</code></a>
chore(deps-dev): bump workerd in /tap/workerd</li>
<li><a
href="8f540d89de"><code>8f540d8</code></a>
chore(deps): bump the actions group with 3 updates</li>
<li><a
href="8d6c02b5fa"><code>8d6c02b</code></a>
chore(deps-dev): bump electron from 44.0.0 to 44.4.5 in
/tap/electron</li>
<li><a
href="55c959fd16"><code>55c959f</code></a>
ci: use gh run rerun for retries</li>
<li><a
href="5b00c649d7"><code>5b00c64</code></a>
ci: address zizmor findings and bump shared workflows</li>
<li><a
href="1761d7f1b5"><code>1761d7f</code></a>
chore: bump packages</li>
<li>Additional commits viewable in <a
href="https://github.com/panva/jose/compare/v6.2.12...v6.2.13">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
v0.94.1-preview.12
2026-10-23 19:49:50 +00:00
Ben Brandt
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`.
2026-10-23 21:43:16 +02:00
Ben Brandt
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
2026-10-23 21:43:02 +02:00
dependabot[bot]
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="0def70338a"><code>0def703</code></a>
Bumped v3.1.9</li>
<li><a
href="a1d0da2f5c"><code>a1d0da2</code></a>
test: generate only relative path references</li>
<li><a
href="9eb8759a98"><code>9eb8759</code></a>
fix: preserve relative paths in suffix and relative resolution</li>
<li><a
href="54c30f7589"><code>54c30f7</code></a>
docs: note suffix-reference authority ambiguity</li>
<li><a
href="39e6493c13"><code>39e6493</code></a>
fix: escape a first-segment colon in suffix references</li>
<li><a
href="745183c1d9"><code>745183c</code></a>
test: cover first-segment colon in suffix references</li>
<li><a
href="ddfc6a5112"><code>ddfc6a5</code></a>
fix(SEC-03): reject non reg-name characters in domain hosts</li>
<li><a
href="d86a8d29f4"><code>d86a8d2</code></a>
fix: keep a colon-first segment behind &quot;./&quot; in relative
normalization</li>
<li><a
href="eb483b5da2"><code>eb483b5</code></a>
test: cover colon-first relative references in normalization</li>
<li><a
href="b110a3571c"><code>b110a35</code></a>
fix(SEC-02): preserve meaning of relative references during
normalization</li>
<li>Additional commits viewable in <a
href="https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for fast-uri since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fast-uri&package-manager=npm_and_yarn&previous-version=3.1.8&new-version=3.1.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
v0.94.1-preview.10
2026-10-23 20:54:32 +02:00
Ben Brandt
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
2026-10-23 13:12:33 +02:00
Ben Brandt
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
2026-10-23 11:08:18 +00:00
Ben Brandt
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
2026-10-23 11:19:16 +02:00
nikita-ashihmin
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
2026-10-22 15:31:05 -07:00
dependabot[bot]
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>&lt;/style&gt;</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>&lt;/style&gt;</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="d1908579d6"><code>d190857</code></a>
Release 8.5.31 version</li>
<li><a
href="4ef6d8cc03"><code>4ef6d8c</code></a>
Update dependencies</li>
<li><a
href="6abd674231"><code>6abd674</code></a>
Call the builder with every node when the output is escaped (<a
href="https://redirect.github.com/postcss/postcss/issues/2175">#2175</a>)</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.30...8.5.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.5.30&new-version=8.5.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
v0.94.1-preview.5
2026-10-22 16:38:37 +02:00
Charles McLaughlin
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
2026-10-21 18:05:56 +00:00
Ilya Kirillov
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
2026-10-21 15:27:16 +02:00
Ben Brandt
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
2026-10-21 13:37:06 +02:00
Andrey Bragin
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
2026-10-20 15:24:42 +02:00
acp-release-bot[bot]
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))
([1420994](1420994ced))


### Bug Fixes

* match OpenRouter model ids in the gateway model listing
([#1365](https://github.com/agentclientprotocol/claude-agent-acp/issues/1365))
([bcf74c2](bcf74c2fe8))
* resolve a skill's SKILL.md by its declared name, not by its directory
name
([#1372](https://github.com/agentclientprotocol/claude-agent-acp/issues/1372))
([424db87](424db87535))
* update to ACP SDK 2.0.0
([#1386](https://github.com/agentclientprotocol/claude-agent-acp/issues/1386))
([ff01e06](ff01e06579))
* update to Claude Code 2.1.303
([#1384](https://github.com/agentclientprotocol/claude-agent-acp/issues/1384))
([2dab922](2dab922644))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.94.0
2026-10-19 21:29:13 +00:00
dependabot[bot]
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
2026-10-19 23:26:23 +02:00
Ben Brandt
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
2026-10-19 21:01:53 +00:00
Charles McLaughlin
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
2026-10-19 22:12:23 +02:00
Andrey Efimov
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
2026-10-19 08:33:33 +00:00
nikita-ashihmin
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
2026-10-19 08:05:46 +04:00
nikita-ashihmin
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
2026-10-19 08:05:25 +04:00
acp-release-bot[bot]
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))
([db9f50f](db9f50f021))


### Bug Fixes

* hold a fork back from the session list until its session/fork response
is out
([#1368](https://github.com/agentclientprotocol/claude-agent-acp/issues/1368))
([f5967fd](f5967fd6b4))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.93.0
2026-10-16 21:19:20 +02:00
Charles McLaughlin
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
2026-10-16 21:07:25 +02:00
nikita-ashihmin
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
2026-10-16 22:38:37 +04:00
acp-release-bot[bot]
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))
([872e93f](872e93f11f))
* publish the predicted follow-up prompt as _meta.promptSuggestions
([#1356](https://github.com/agentclientprotocol/claude-agent-acp/issues/1356))
([78a29f5](78a29f58ba))
* update ACP SDK to 1.9.0 and adopt the v2 spec clarifications
([#1366](https://github.com/agentclientprotocol/claude-agent-acp/issues/1366))
([68bf74f](68bf74f58e))


### Bug Fixes

* include subagent file edits in the file-change report
([#1357](https://github.com/agentclientprotocol/claude-agent-acp/issues/1357))
([910d83c](910d83ceea))
* restore resumed subagents after an adapter restart
([#1364](https://github.com/agentclientprotocol/claude-agent-acp/issues/1364))
([26f144a](26f144a2a5))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.92.0
2026-10-16 14:39:40 +02:00
Ben Brandt
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
2026-10-16 14:37:08 +02:00
Mark Tkachenko
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
2026-10-16 14:15:35 +02:00
Mark Tkachenko
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
2026-10-15 17:04:17 +02:00
nikita-ashihmin
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
2026-10-15 17:55:44 +04:00
Mark Tkachenko
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
2026-10-15 15:39:18 +02:00
acp-release-bot[bot]
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))
([ba3c7be](ba3c7be05e))
* let a client cancel one subagent with session/cancel on its child
session
([#1347](https://github.com/agentclientprotocol/claude-agent-acp/issues/1347))
([aeb69e7](aeb69e7ed1))
* mark session list rows of worktrees Claude Code made (agentWorktree)
([#1352](https://github.com/agentclientprotocol/claude-agent-acp/issues/1352))
([1a1f1a0](1a1f1a0e80))
* narrow models to what the providers/set gateway lists
([#1340](https://github.com/agentclientprotocol/claude-agent-acp/issues/1340))
([e8eb9d3](e8eb9d340f))
* restore a subagent from before a reload when SendMessage resumes it,
for subagents RFD clients
([#1332](https://github.com/agentclientprotocol/claude-agent-acp/issues/1332))
([df21b3f](df21b3fb56))


### Bug Fixes

* close the tool calls of a background subagent that the SDK reports
stopped after a cancel
([#1336](https://github.com/agentclientprotocol/claude-agent-acp/issues/1336))
([9a1b90f](9a1b90fb9d))
* close the tool calls of a cycle that Claude Code started and a cancel
stopped
([#1339](https://github.com/agentclientprotocol/claude-agent-acp/issues/1339))
([1fd217f](1fd217f3e7))
* close the tool calls that a cycle Claude Code started abandoned, at
its end
([#1344](https://github.com/agentclientprotocol/claude-agent-acp/issues/1344))
([594aa8c](594aa8c955))
* deliver the output of a background subagent that a cancel did not stop
([#1345](https://github.com/agentclientprotocol/claude-agent-acp/issues/1345))
([bcc024d](bcc024d2c9))
* end the terminal of a command whose tool call is closed without a
result
([#1335](https://github.com/agentclientprotocol/claude-agent-acp/issues/1335))
([764f339](764f339c3e))
* end the tool calls of a cycle that Claude Code started and a cancel
stopped, on v1
([#1342](https://github.com/agentclientprotocol/claude-agent-acp/issues/1342))
([b4788d6](b4788d61ea))
* keep a live session that was never prompted when a resume or load
changes its settings
([#1353](https://github.com/agentclientprotocol/claude-agent-acp/issues/1353))
([0c8de13](0c8de1380f))
* let session/delete succeed for a session without a transcript
([#1351](https://github.com/agentclientprotocol/claude-agent-acp/issues/1351))
([fadbe18](fadbe1881c))
* report no running for the session's own request that a cancel
withdraws, on ACP v2
([#1346](https://github.com/agentclientprotocol/claude-agent-acp/issues/1346))
([d528647](d528647162))
* show nothing of a tool that cancelled work asks to run after the
cancel was reported
([#1334](https://github.com/agentclientprotocol/claude-agent-acp/issues/1334))
([ebf8b49](ebf8b49fd6))
* stop waiting for a question the client leaves open after Claude Code
cancels it
([#1338](https://github.com/agentclientprotocol/claude-agent-acp/issues/1338))
([c1e83b4](c1e83b46b7))
* update Claude Agent SDK to 0.3.300
([#1355](https://github.com/agentclientprotocol/claude-agent-acp/issues/1355))
([761b4c8](761b4c8359))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
v0.91.0
2026-10-15 12:22:03 +00:00
Ben Brandt
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
2026-10-15 12:17:45 +00:00
nikita-ashihmin
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
2026-10-15 12:15:52 +04:00
Ben Brandt
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
2026-10-14 23:16:29 +02:00
Ben Brandt
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
2026-10-14 23:14:54 +02:00
Ben Brandt
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
2026-10-14 21:56:01 +02:00
Ben Brandt
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.
2026-10-14 21:56:01 +02:00
Sergey Shkredov
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
2026-10-14 17:03:27 +02:00
Ben Brandt
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
2026-10-14 13:22:12 +02:00
Ben Brandt
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.
2026-10-14 13:21:04 +02:00
Ben Brandt
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
2026-10-14 13:20:36 +02:00
Ben Brandt
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
2026-10-14 12:19:29 +02:00