Found while reviewing #1226. It affects ACP v1 and v2. ## Problem A turn held open for its background subagents (`Turn.deferredSettle`, issues #864–#866) was settled `cancelled` inside `cancel()`, before the interrupt. The followup cycle can be live at that point, writing the subagents' summary or blocked on a permission request. The interrupt still flushes that cycle's output, so: - **v1:** the output arrives after `session/prompt` has answered `cancelled`, when many clients have stopped listening. - **v2 (#1226):** the output arrives after `idle cancelled`. The v2 draft says updates after a cancel MUST come before it. ## Change `cancel()` now settles inline only where the consumer can't: - **a settling turn,** blocked in its checkpoint preview, which can't process the interrupt's trailing idle (unchanged); - **a held turn whose session sits idle,** or has reported no state yet: the interrupt emits nothing, so no trailing idle comes (unchanged). **A held turn with a live followup** (`running` or `requires_action`) is now left to the consumer, like any live turn. It ends at the interrupt's trailing idle, or at the next prompt's echo if that idle lags, after the output the interrupt flushed. The force-cancel backstop is armed for it meanwhile. Two consequences, both handled: - **The trailing-idle debt:** the interrupted followup's result is autonomous, so it records its trailing idle as owed. When that idle ends the cancelled held turn, it now also pays the debt. Otherwise the debt would absorb a later issue-#825 signal. - **Usage:** a held turn can now be ended by the trailing idle, the echo hand-off, stream end or the backstop, not only by `cancel()`. All of them take the outcome from one rule, `cancelledOutcome`: a turn that already has its result keeps that result's usage and `_meta`, as the inline settle did (issue #844). Today the session's tally equals that recorded outcome anyway, since followup results aren't accumulated. Stream end already treats the stored one as authoritative. ## Testing - **New:** `src/tests/turn-events.test.ts` cancels a held turn mid-followup. The interrupt flushes more of the summary, then the followup's result and idle arrive. It asserts: - the flushed output comes before the turn ends `cancelled`; - a wedged next turn still fails at its un-owed idle (issue #825), so no trailer debt leaked. - **Updated:** the two existing tests that cancel a held turn in a live cycle (running, and blocked on a permission request) expected the prompt to resolve at `cancel()`. Their scripts send the interrupt's trailer only after the next prompt's echo, so the turn now ends at that echo hand-off. They keep their issue #825 assertion, and one now also checks the usage the turn keeps (issue #844). - **Unchanged:** the tests for an idle hold, which settles at `cancel()` without pre-counting a trailer, pass as before. - **Mutations:** settling a live held turn inline again, settling an idle one at an idle that never comes, or skipping the debt payment each fail at least one test. `npm run build`, `npm run check`, `npm run test:run` (2244 passed).
ACP adapter for the Claude Agent SDK
Use Claude Agent SDK from ACP-compatible clients!
This tool implements an ACP agent by using the official Claude Agent SDK, supporting:
- Context @-mentions
- Images
- Tool calls (with permission requests)
- Compact file changes through the negotiated AIR diff patch extension
- Following
- Edit review
- TODO lists
- Nested subagent transcripts
- Interactive (and background) terminals
- Custom Slash commands
- Client MCP servers
/mcpin the chat: the MCP server status as a list. The adapter runs/mcp reconnect,/mcp enable, and/mcp disablethrough the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation- Session-scoped long-running goals for AIR through the goal extension under
_meta.jetbrains.air.goal - Structured errors, recovery, and warnings through the opt-in session failure extension
- Concrete model and effort defaults through the opt-in recommended config value extension
- Tool permission presentation, editable choices, and durable effects through the permission extension
- One fact per field in every tool call report, as the ACP tool call contract defines
- All AIR extensions, capabilities, and
_metakeys: AIR extensions
Learn more about the Agent Client Protocol.
To try changes that have landed on main but are not released yet, install from the
preview channel — every push to main publishes one. See
docs/RELEASES.md.
npm install @agentclientprotocol/claude-agent-acp@preview
Subagent sessions
Subagents are exposed only after bilateral capability negotiation. Until the released ACP SDKs
preserve the draft clientCapabilities.subagents field, a supporting client may advertise
nativeSubagentSessions in _meta.jetbrains.air.capabilities; the adapter mirrors the capability
in its initialize response. The canonical field remains supported and takes precedence once it is
available. Without either client signal, Agent/Task lifecycle keeps its legacy ordinary ACP
tool-call representation and child interactions stay on the root session. Clients that use the
historical _meta["subagent-transcript"] capability or forwardSubagentText session option retain
the flattened child transcript behavior. See AIR extensions.
Contribution Policy
This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms:
By contributing to this project, you agree that your contributions will be licensed under the Apache License, Version 2.0. You affirm that you have the legal right to submit your work, that you are not including code you do not have rights to, and that you understand contributions are made without requiring a Contributor License Agreement (CLA).