## Problem After a cancel, the consumer drops the main conversation's consolidated `assistant`/`user` messages until Claude Code starts a model request that serves none of the cancelled prompts (#1309). `stream_event` frames skipped that check. Claude Code always streams, so when it had already taken an abandoned prompt off its queue, the prompt's answer still streamed to the client as deltas. Only the consolidated copy was dropped. That output arrives after the cancel has been reported: on v1 after the turn's `cancelled` response, and on v2 after `idle cancelled`, outside any cycle. Both protocol versions require an agent's updates to come before that report. Streamed tool calls leaked the same way. #1309's tests missed this because they scripted only consolidated messages. ## Change - **After the client has been told the stopped work ended**, while the window is still open, streamed text, thinking and tool calls are dropped like the consolidated messages they belong to. On v1 that report is the turn's prompt response (no active turn). On v2 it's the idle state (no open foreground work). The v2 signal also covers cycles that Claude Code starts on its own and turns that v1 holds for their subagents. - **Before that report**, what the interrupt flushes still streams, as it does today. Both drafts allow updates between the cancel and the cancelled report, and the text is in Claude Code's transcript, so a reload shows it anyway. Existing tests pin this: v2's queued-prompt cancel, a held turn cancelled mid-follow-up, and a cancel that arrives during a buffered backlog. - **A prompt that is merely queued no longer keeps the window open.** #1309 held the window while any queued prompt was unsettled. So a follow-up that Claude Code ran ahead of a prompt sent after the cancel lost its consolidated copy, and with this change it would have lost its streamed copy too. Now a request holds the window only when: - the stopped turn is still unsettled; - an abandoned prompt's result is still expected; - the request names a cancelled prompt; or - the request names an unsettled prompt. That covers one that arrived while the cancel was still awaiting, before the cancel records it. - **Per-block drop latch.** Where the consolidated message delivers the text, a stream that loses a delta keeps dropping until its streamed-text record resets: at the next message, or when a consolidated message consumes the record. If a window closes mid-block (an older CLI's late prompt echo), the record holds nothing, so the consolidated message delivers the whole block once. Without the latch the record would hold only the tail, which isn't a prefix of the text, and the client would get the tail and then the whole text again. A client that gets subagent text only streamed has no consolidated copy to fall back on, so its subagent streams don't latch. - Bookkeeping for dropped events (message id, usage, compaction heartbeat) is unchanged. ## Evidence **Tests** - The "output after a cancel" suite now streams every answer the way Claude Code does, with Claude Code's follow-up in two chunks. Without the fix, 9 of its tests leak the streamed copy. - New v1 tests: - a follow-up that runs ahead of a prompt sent after the cancel; - a prompt sent during the cancel whose request arrives before the cancel abandons it; - an echo in the middle of a two-block answer; - a subagent stream for a client that gets subagent text only streamed. - New v2 tests: - an abandoned prompt's streamed answer; - what the interrupt flushes of a cycle that Claude Code started; - a held turn's follow-up that the cancel stopped before it showed anything. - **Mutation checks:** each of these fails at least one test: - removing any of the window's four rules, or restoring #1309's hold for any queued prompt; - removing the drop; - dropping the flushed tail before the report too; - using the v1 signal on v2; - removing the latch, either of its record resets, or the subagent condition. The exception is clearing the latch when a subagent finishes. That's memory cleanup that no test can observe, like the `streamedBlocksByParent.delete` beside it. - `npm run check`, build and all 2796 tests pass, with no golden changes. **Live, CLI 2.1.293 on Haiku, v1 and v2** - A plain cancel mid-answer, and cancels with a background command finishing during or after the cancelled turn. - Nothing of the cancelled turn arrived after its report. - Claude Code's follow-up after the cancel streamed in full. On v2 it came in its own `running` … `idle end_turn` cycle. - An abandoned prompt that Claude Code had already dequeued is a narrow race through the adapter, so I didn't reproduce it live. The tests follow the order a direct CLI probe recorded for #1309. ## Not changed - A dropped prompt that runs a tool can still surface it after the cancel, through `canUseTool` (tool call plus permission request) and the PreToolUse preview hook. Neither checks the cancel. A check there would read the consumer's state, which lags the CLI's control channel, so this needs its own design. - On CLIs without `request_marker_lists_v1`, the window still lasts until the next prompt's echo. Claude Code's own follow-ups in that window now lose their streamed copy as well as the consolidated one. The bundled CLI has the marker.
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).