Commit Graph

33 Commits

Author SHA1 Message Date
Ben Brandt
c7393d27ff refactor: experimental ACP v2 support for subagents (#1325)
Adds subagents to the experimental [ACP
v2](https://agentclientprotocol.com/protocol/v2/overview) surface. With
them, it covers v2's baseline session model, so this is the `feat:` that
announces v2 support. The README gains a section on how to enable it and
what it serves.

Based on #1324, a v1 fix that this PR's golden scenarios exposed. Merge
#1324 first; this PR then retargets to `main`.

## Experimental ACP v2

Started with `CLAUDE_AGENT_ACP_EXPERIMENTAL_V2=1`, the adapter serves
the draft ACP v2 to a client that asks for protocol version 2 or later
in `initialize`, and v1 to every other client, as before.

- **Served on v2:**
- the session baseline (`session/new`, `list`, `resume` with replay,
`close`, `prompt`, `cancel`)
  - `session/delete`, `session/fork`, and additional directories
  - config options (the mode is one), plans, notices, compaction
  - display terminals for commands
  - permission requests, elicitation, providers, auth
  - and now subagents
- **v1 only for now:** steering and the AIR extensions.

The draft can still change, and the surface changes with it.

## What changes

The [subagents
RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx)
makes subagent updates part of v2's baseline: "support is assumed rather
than negotiated".

- **Every v2 client gets subagents.** The v2 bridge declares `subagents`
in the v1 request it makes for a v2 client (`v1InitializeRequest`), so
the agent reports each Agent or Task subagent in the RFD form from
#1320–#1322.
- **The updates pass through:** `subagent_update`, `session_message` and
`session_message_chunk` have the same shape in v1 and v2.
- **The child's state goes to its own stream too.** v2 reports a child's
work state on the child's own stream, mirrored on the parent. So a
`subagent_update` with a `state` also becomes the child's
`state_update`, sent right after the parent's copy
(`v2ChildStateUpdate`). The announcement thus comes first, and both
copies of a state come before any later one, as the RFD requires. This
covers `requires_action`, and a failed child's `idle`/`error` with its
JSON-RPC `error`.
- **A state the RFD doesn't define throws,** as an update kind the
surface doesn't translate already does. The SDK types the open members
of v1's and v2's `StateUpdate` apart, so the bridge checks each state
against the RFD's before passing it on.
- **Replay doesn't flatten a child's inline text onto the root.** On
replay, a subagent's text goes to the root only for a client that gets
it there live: one without native subagent sessions that asked for the
flattened transcript. Before, an RFD client (v1, and now v2) got the
inline subagent text of a transcript replayed on the root, though the
RFD form doesn't replay children yet. Current Claude Code keeps subagent
messages in `subagents/`, not inline, so only the `session-load-replay`
scenario's inline message showed it. AIR's replay is unchanged.

## Live run

Against real Claude Code (Sonnet), with a v2 client that checks every
`session/update` and permission request against the SDK's v2 schema,
there were no schema violations:

- **Launch:**
- the child is announced on the root, then reports its own `running`,
then gets its prompt as a `session_message` from the root
- its tool calls arrive on its session, each with a title on its first
report
  - the Agent call's 35-second heartbeat stays on the root
- **Permission:**
- `requires_action` appears on the child's stream and on the parent's
copy, and on the root, whose turn waits on the child
  - the request goes to the child's session
  - then the child is back to `running`
- **SendMessage resume:**
  - the same child session runs again, with the root as sender
- the root turn goes idle while the resumed child keeps running, and the
child reports `idle`/`end_turn` later, which v2 allows for background
work
- **Cancel:** the child is `idle`/`cancelled` on both streams, then the
root is `idle`/`cancelled`.
- A tool call still running at the cancel never gets a final status.
That's a known bug on `main` for every client, fixed in a separate PR.

## Tests

- **End to end on v2:** an Agent call whose child fails with an SDK
error. Traffic arrives in order on both streams, and the child's `idle`
carries `error: { code: -32603, message }`.
- **Unit:**
  - the pass-through of the three updates
- the child's `state_update`, and no copy for an association update
without a state
  - the throw for a state and a stop reason the RFD doesn't define
- **v2 golden files:**
- the six subagent scenarios now show the RFD form: announcements,
prompts, child sessions, both copies of each state, and permission
requests on the child session
  - `session-load-replay` is unchanged from `main`
- the scenarios also check every message against the v2 schema, and that
each tool call's first report has a title (that check found #1324)
- **Mutation checks:** each of these fails tests:
  - not declaring `subagents`
  - dropping the child's copy
  - sending it before the parent's
  - dropping the state check
  - dropping the replay rule
- **v1 golden files:** unchanged.
- **Checks:** `npm run check`, build and all 2882 tests pass. CI runs
only on PRs to `main`, so I ran `ci.yml` on this branch by hand.

## Release

Titled `feat:`, so its title becomes the release note that announces v2.
2026-10-13 16:57:14 +02:00
Ben Brandt
252126f616 feat: report a subagent as requires_action while its request is open for subagents RFD clients (#1322)
The third of three stacked PRs that replace #1257. It's based on #1321,
so its diff shows only the `requires_action` part. Merge #1320 and #1321
first; this PR then retargets to `main`.

1. #1320: the RFD form for a v1 client that declares `subagents` and
isn't AIR.
2. #1321: a child keeps its session across SendMessage resumes.
3. **This PR:** `requires_action` while a child's request is open.

**AIR is unchanged.**

## What changes

The [subagents
RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx)
reports a child that waits for the user as `requires_action`.

- **While a request is open:** a child whose permission or question
request is open reports `requires_action` on its parent, then `running`
again when its last open request ends
(`NativeSubagentRuntime.awaitingUser`). The agent wraps the permission
and elicitation requests of a child session in it
(`withChildAwaitingUser`).
- **Overlapping requests** keep `requires_action` until the last one
ends. A child that ended while it waited doesn't report `running` again.
- **A resume while a request is open** reports `requires_action`, not
`running`.

## Live run

Against real Claude Code (Sonnet), a child's Bash permission request
showed `running` → `requires_action` while the request was open →
`running` once it was answered. No schema violations.

## Follow-ups (low severity, not in this PR)

- **Brief `running` after a withdrawal:** after a cancel withdraws a
child's request, the child briefly reports `running` before the SDK's
stop event. The same happens at a stream end, whose cleanup aborts every
pending request.
- **Ignored cancel requests:** AskUserQuestion's `createElicitation` is
not raced against the abort. If a client ignores `$/cancel_request`, the
child's open-request count stays up, and a later resume reports
`requires_action`.

## Tests

- **End to end:** `requires_action` around a child's permission request.
The cancel test now also expects the background child's
`requires_action` before the cancel.
- **Runtime:** overlapping requests, a child that ended while it waited,
and a resume with an open request.
- **Checks:** `npm run check`, build and all 2877 tests pass on this
branch, whose tree is #1257's with `main` merged in. CI runs only on PRs
to `main`, so I ran `ci.yml` on this branch by hand.
2026-10-13 16:11:25 +02:00
Ben Brandt
bd06804b67 feat: keep a subagent's session across SendMessage resumes for subagents RFD clients (#1321)
The second of three stacked PRs that replace #1257. It's based on #1320,
so its diff shows only the resume part. Merge #1320 first; this PR then
retargets to `main`.

1. #1320: the RFD form for a v1 client that declares `subagents` and
isn't AIR.
2. **This PR:** a child keeps its session across SendMessage resumes.
3. #1322: `requires_action` while a child's permission or question
request is open.

**AIR is unchanged:** it keeps a new session per generation, as in its
draft.

## What changes

The [subagents
RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx)
gives a child one session id across every delegation to it. With #1320
alone, a resumed child stays `idle`.

| RFD | With #1320 | Now |
| --- | --- | --- |
| One session id per child, across delegations | a resumed child stays
`idle`, and its resumed work isn't shown | the child runs again in the
same session, under the SDK agent id. The CLI runs a resumed agent under
the SendMessage call's tool id, so that call becomes the child's current
delegation: the resumed run's output and end reach the child |
| The parent's message to the child is an incoming `session_message` |
only the launch prompt | each SendMessage that resumes the child is a
`session_message` with the SendMessage tool use's id. Its sender is the
session that made that call, or left out when unknown. A repeated resume
signal doesn't repeat it |

Out-of-order events, from the reviews of #1257:
- **Late end:** the end of an earlier delegation ends nothing
(`isStaleEnd`). It also no longer settles the resumed run's live task at
the agent level.
- **Late result:** a SendMessage result that arrives after its run ended
resumes nothing (`isEndedDelegation`).
- **Early output:** a resume's output that arrives before its
`task_started` is buffered, then delivered to the child.
- **Reused id:** a run under an id the child used before ends the run,
since the SDK falls back to it.
- **Running patch:** a `task_updated` running patch reports `running`
without its guessed prompt or call.

## Live run

Against real Claude Code (Sonnet), a SendMessage to a finished child ran
it again in the same session. Its message arrived with the SendMessage's
id and the root as sender, and the turn waited for the resumed run. No
schema violations.

## Tests

- **End to end:**
- a SendMessage resume under the SendMessage's tool id: one session, two
prompts, the resumed output in the child, a stale end ignored
- the resumed run's live task kept when an earlier delegation's end
arrives late
  - a late SendMessage result
- **Runtime:**
  - reuse and resume
  - a resume's output buffered before its `task_started`
  - the sender of a resume's prompt: the root, a child, or unknown
  - a run under a reused id
  - a running patch
  - a result without a text
- **Mutation checks during #1257's reviews:** removing any of these
rules fails tests.
- **Checks:** `npm run check`, build and all 2873 tests pass on this
branch. CI runs only on PRs to `main`, so I ran `ci.yml` on this branch
by hand.
2026-10-13 16:11:25 +02:00
Ben Brandt
5758ac84a4 feat: report subagents as the subagents RFD defines, for clients that are not AIR (#1320)
The first of three stacked PRs that replace #1257, split for review.
Together they bring v1 clients the [subagents
RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx),
as revised by agentclientprotocol/agent-client-protocol#2281 and #2308.
**AIR is unchanged**: its golden files don't change.

1. **This PR:** the RFD form for a v1 client that declares `subagents`
and isn't AIR.
2. #1321: a child keeps its session across SendMessage resumes.
3. #1322: a child reports `requires_action` while its permission or
question request is open.

Merge them in that order. Each later PR is based on the previous branch,
so its diff shows only its own part.

## Who gets what

- **AIR** (any client that declares `_meta.jetbrains.air`): the earlier
draft it implements, exactly as before. That's `subagent_spawned`,
`subagent_state_update`, a session per generation, the hidden Agent/Task
tool call, and draft replay. AIR keeps it whichever signal it declares.
- **A v1 client with `clientCapabilities.subagents` that isn't AIR:**
the RFD form below. SDK 1.8.0 types it (`subagent_update`,
`session_message`, the v1 `StateUpdate`).
- **Without either signal:** nothing changes.

`subagentForm` (`src/acp-subagents.ts`) picks the form.
`NativeSubagentRuntime` keeps the routing, buffering and nested lineage,
and renders either form.

## The RFD form

| RFD | Before | Now |
| --- | --- | --- |
| Announce the child before any of its traffic | `subagent_spawned {
name, task, prompt }` | `subagent_update { sessionId, title,
description, state: { state: "running" } }` |
| `description` is the role or purpose, "not a transcript of
instructions" | `task` was the prompt | `title`: the agent's `name`,
with its role (`subagent_type`) after it, else the role. `description`:
the Agent input's short `description` |
| The parent's message to the child is an incoming `session_message` |
`prompt` on the announcement | a `session_message` in the child's
transcript, with the Agent/Task tool use's id and the session that made
the call as sender |
| Delegation tools stay tool calls | the Agent/Task tool call was hidden
| it stays a tool call of the session that made it, with the report its
model got. Its heartbeats and result stay there too |
| Work-state snapshots, not a terminal lifecycle | `completed`,
`failed`, `cancelled`, `disconnected` | `running`; `idle` with
`end_turn`, `cancelled`, or `error` with the SDK's error text as its
JSON-RPC `error`; `idle` with no stop reason for work that ended without
a known reason (a worker restart, or still running when the stream ends)
|
| `idle` isn't terminal | later updates were dropped | an idle child
still gets its updates, such as its background work's |
| A cancel stops the work it waits on; other descendants are up to the
Agent (#2308) | `session/cancel` ended every child as cancelled | the
adapter ends no child itself. Claude Code's interrupt stops the
foreground children, and currently background ones too; each reports
`idle`/`cancelled` from the SDK's events, and the foreground ones'
requests are withdrawn |
| Never guess a parent | a child that ended before its spawning frame
was announced under the root | it stays unexposed. A nested child waits
for its parent's announcement |
| Never move a tool call | | a tool call that a permission request
emitted on the root before its child was announced stays on the root
after a cancel. AIR keeps the cancel's cleanup, since its draft ends
every child there |
| No agent-side capability | `sessionCapabilities.subagents` for every
client | for AIR only |

## Not in this PR

- **SendMessage resumes** (#1321). Until then a resumed child stays
`idle`. Requests find a child by its agent id, so only the permission
and question requests of the resumed run appear in its session. A resume
never opens a second session for the child, which the RFD forbids, and a
test pins that.
- **`requires_action`** (#1322).
- **Replay** doesn't expose children yet. Today's replay invents
`<session>:replay-subagent:<id>` ids, and the RFD requires the original
ones. The RFD makes child replay a SHOULD, so an RFD client's replay
leaves children out until the replay PR.

## Settled during the review of #1257

- **Cancel cascade:** the RFD leaves background children to the Agent
(#2308). An agent-level test pins #2308's validation scenario.
- **Session ids across forks:** the raw SDK agent id stays the session
id. The SDK's fork copies no subagent history, so a fork can't repeat an
id under another root. The reasoning is on `nextChildSessionId`.
- **Held turns:** the adapter holds a v1 prompt open while a background
subagent the turn started runs. A cancel still settles the prompt.

## Live run

Against real Claude Code (Sonnet), with a v1 client that declares
`subagents` and checks every update and permission request against the
SDK's v1 schema, there were no schema violations:

- **Launch:** `running` with the title (`scout (general-purpose)`) and
description, the prompt as a `session_message` from the root, and the
child's tool calls in its session. Then `idle`/`end_turn` before the
Agent call completes on the root.
- **A 40-second child:** the Agent call's heartbeat and result stay on
the root. Before the fix, they moved to the child, because the heartbeat
named the Agent call as its own parent.
- **Cancel:** a foreground child is `idle`/`cancelled` 15 ms after
`session/cancel`, and the prompt returns `cancelled`. Claude Code
stopped a background child too (its metadata says `stoppedByUser`).
- **Found on `main`, for every client, and fixed separately:** after a
cancel, a tool call that was running never gets a final status.

## Tests

- **AIR:** the end-to-end tests of the earlier draft run with an AIR
client that declares `nativeSubagentSessions` (`AIR_NATIVE_SUBAGENTS`).
The AIR golden files are unchanged.
- **RFD, end to end:**
  - announcement and prompt
- a failure with the SDK's error, also when the error arrives after the
failure
  - a stop, and a worker-restart orphan
  - no draft updates
  - the cancel per #2308
- the raced tool call after a cancel, kept on the root, with AIR's
cleanup kept
  - the Agent call's heartbeat

  Every update is checked against the SDK's v1 schema.
- **RFD, runtime:**
  - the visible control tool call, also nested
  - labels, also from a late frame
- an unparented child staying unexposed, and a nested child announced
only after its parent
  - nothing reported at the parent's cancel
  - the end of the stream, and `unknown`
  - a resume opening no second session
- **Plain/Zed comparison with origin/main:** it now allows the missing
agent capability, which `compare.ts` documents.
- **Mutation checks during #1257's reviews:** removing any of these
rules fails tests.
- **Checks:** `npm run check`, build and all 2863 tests pass.

## Release

Titled `feat:`, because it changes what a v1 client that declares
`subagents` receives.
2026-10-13 16:11:24 +02:00
nikita-ashihmin
190a00f68f feat: answer /mcp in chat with the MCP server status and a reconnect (#1218)
## Summary

In an ACP client, `/mcp` showed the CLI text "… Use `/mcp` in the
terminal for details." The user of an ACP client has no terminal. In SDK
mode, Claude Code also refuses `/mcp reconnect|enable|disable` with
"Reconnect, enable, and disable aren't available in this session." and
changes nothing.

- `/mcp` and `/mcp reconnect|enable|disable [<server>|all]` still run as
a normal Claude Code turn. The adapter replaces the text of the local
command, through the same hook that `/usage` uses. Prompt order, cancel,
and the provider update stay with the turn.
- For an action, the adapter runs it in that turn through the SDK
control API (`reconnectMcpServer`, `toggleMcpServer`). It shows one
result line for each server, then the status list.
- The status list comes from `query.mcpServerStatus()`. It has a summary
line and groups by status: failed, needs authentication, connecting,
connected, and disabled on one line. A failed server shows a short
cleaned error. A list is used and not a table, because a table breaks
words in narrow columns.
- Useful CLI sentences stay, for example "There's no MCP server named
…". Sentences that point to the terminal are removed.
- After an explicit reconnect, a server from the ACP session that needs
authentication starts the existing MCP OAuth flow, when the client
supports URL elicitation.
- `/mcp` is advertised with the input hint `[reconnect|enable|disable
[<server>|all]]` and is hidden on history replay.

## Other commits in this branch

This branch also carries commits of other work: skill command metadata
for AIR, a faster `session/load` replay, and the resumed permission
mode. They can move to separate PRs if preferred.

## Tests

- `src/tests/mcp-command.test.ts` (61 tests). The fake CLI answers with
the real SDK-mode texts and message shapes (synthetic assistant +
result) and does not change any status.
- A live run through the adapter with a broken and a working stdio
server: `/mcp`, `/mcp disable good`, `/mcp enable good`, `/mcp reconnect
broken`, `/mcp reconnect nosuch`.
- `compare.ts` allows one new difference against the origin/main
baseline: the `mcp` command in `available_commands_update`. The baseline
files are unchanged.
- `npm run check` and `npm run test:run` pass.
2026-10-02 17:37:59 +04:00
nikita-ashihmin
875d75f2a4 feat: AIR tool call contract, exact git patches, plan file, and fixes for every client (#1153)
## Summary

One PR with separate commits (the earlier stack #1168 and #1169 is
folded back in).

### Exact git patches
- `src/diff.ts` builds exact git patches for Edit and Write: real line
numbers, file modes, CRLF, the final-newline marker, a size limit
(including a bound for `replace_all`), binary detection, and an
asynchronous diff with a time budget. The approval preview mirrors the
input normalisation of the bundled CLI.
- No creation patch over an existing file or for an Edit of an existing
empty file, and no patch for a Write that changed nothing.

### For every client
Clients that do not declare `_meta.jetbrains.air` (Zed, plain ACP
clients) get the same information as on `main`.
- A `tool_call_update` omits top-level fields that did not change since
the last report of the same tool call (ACP merge semantics). The `_meta`
of each report stays complete.
- Bug fixes: a failed approval preview no longer blanks the approval
diff; a permission denial replaces a pinned approval patch; the
compaction summary is not repeated when the streamed text is identical;
streamed subagent text is not sent twice; async task fields are recorded
only after a successful send; late updates of a finished native subagent
are dropped; the updates of a fresh Claude context after a clear-context
restart reach the ACP session.

### For AIR only
- One fact in one field: input in `rawInput`, the file text of an edit
only in the diff (`diffPatch`), output once, no display copies of the
input (`rawInputRendering`), `_meta.claudeCode` reduced to `toolName`,
`parentToolUseId` and the `toolResponse` markers, per-key `_meta`
filtering.
- Plan file mode (`planFile`): ExitPlanMode carries only
`rawInput.planFilePath`; AIR follows the file that Claude writes.
- AIR-only keys move under `_meta.jetbrains.air.*` and are no longer
sent to other clients.

### Structure and docs
- `src/tool-calls/`: one reporter per tool, one `ClientCapabilities`,
one `AcpToolCallRenderer`, the field tracker and the changed-meta
filter.
- `docs/air-extensions.md` replaces the per-extension AIR docs.

## Tests
- `npm run build`, `npm run check`, `npm run test:run`: 1995 passed.
- Scenario tests for plain, Zed, and AIR clients: the SDK mock honours
the adapter's session id, every update must reach its ACP session, every
message is validated against the ACP JSON schema with all formats
checked, plain and Zed are compared with a recorded `main` baseline, AIR
keeps compact golden recordings.
2026-09-28 17:58:00 +04:00
nikita-ashihmin
2b666c3d35 feat: advertise recommended config values (#1111)
Clients that negotiate the AIR `recommendedValue` capability receive
concrete model and effort choices instead of ambiguous default rows. The
SDK's default model is mapped only to an exact selectable model
(including equivalent context suffix spellings); unavailable
recommendations retain the legacy default. Recommendations remain
independent of explicit selections.

The adapter applies the displayed effort to the SDK at startup and on
model switches, preserving supported user pins and re-reading per-model
settings for automatic choices. Permission modes and clients without the
capability retain their existing configuration behavior. Model and
effort configuration logic is extracted into dedicated modules.

Terse SDK model labels are enriched with concrete versions from model
metadata: for example, `Fable` becomes `Fable 5.1`, `Sonnet` becomes
`Sonnet 5`, and `Opus (1M context)` becomes `Opus 5`. Custom names are
preserved, and collisions fall back to the original labels. An SDK
version guard requires review on upgrades, and an opt-in live
initialization test verifies the upstream model contract without sending
a prompt.

Validation:
- `npm run check`
- `npm run build`
- `env -u ANTHROPIC_BASE_URL npm run test:run` — 1253 passed, 28 skipped
- `RUN_INTEGRATION_TESTS=true npx vitest run
src/tests/model-presentation.test.ts` — 4 passed
- Regression coverage for unavailable model generations/context windows,
startup effort, per-model settings, unsupported effort pins, and
duplicate Opus labels
2026-09-10 01:10:31 +04:00
Andrey Bragin
0dd58685b1 ci: set up preview publishing (#1084)
Today nothing reaches npm between two release-please releases, so trying
a merged
but unreleased change means building from source. This adds a
continuously
published preview channel.

Every CI-green push to `main` that is not a release merge publishes
`<package.json version, patch bumped>-preview.N` under the `preview`
dist-tag,
tags the commit `v<version>`, and dispatches the same agent registry
update a
stable release does. With `main` at 0.73.0 that gives
`0.73.1-preview.1`,
`0.73.1-preview.2`, and so on; `N` restarts at 1 whenever a release
moves the
base. `latest` is untouched.

## Approach

- **Added to `publish.yml` rather than a new workflow.** npm binds a
trusted
publisher to one repo + workflow filename, and a package gets only one.
A
  separate file would fail OIDC and cost the stable path its publisher.
- **Gated on the `CI` workflow succeeding** (`workflow_run`), so a red
commit is
never published. release-please's release merge is excluded by the head
commit's author and subject — its `releases_created` output is not
reachable
  from a different workflow run.
- **`N` is the max of the npm registry and local git tags.** npm
reserves
versions forever, even after `unpublish`, so a reused `N` is a hard
failure;
but the registry is CDN-served and lags, which the tags cover. A
registry read
  failure aborts rather than guessing.
- **Base is patch+1, not release-please's prediction**, which moves
mid-flight
  and would reset `N` under already-published previews.
- **The version is applied to the CI working tree only**, never
committed —
`package.json` and the release-please manifest stay release-please's to
own.
- **`--tag preview` is mandatory:** `npm publish` defaults to `latest`
even for a
  semver prerelease.
- **Three jobs.** `publish-npm-preview` mirrors `publish-npm` and only
publishes
(no repo write access); `publish-tag-preview` only tags, so a failed tag
is
retryable with _Re-run failed jobs_ without re-publishing an immutable
version;
`trigger-registry-update` is shared with the stable path. Both
downstream jobs
gate on a `published` output the publish step sets, not on job status —
the tag
push happens after the publish, so a red job can still mean npm has the
  version.

## Next step

The publish is `npm publish --dry-run` for now. The `workflow_run`
trigger and
the `release` environment's protected-branch policy mean this job cannot
run off
the default branch at all, so merging this is the only way to exercise
the
trigger, the gate and the version calculation — and with `published`
unset, the
rehearsal neither tags nor touches the registry.

Once one run looks right, a follow-up drops `--dry-run` and adds
`echo "published=true" >> "$GITHUB_OUTPUT"` to that step. Nothing else
changes.
2026-09-08 14:16:37 +02:00
nikita-ashihmin
14d192d108 feat: expose native subagents and async tasks (#1017)
## Summary

- negotiate native ACP subagent sessions and AIR async task support
- map Claude Agent/Task lifecycle to independent child session streams
- publish background workflow, monitor, MCP, and backgrounded shell work
as async tasks
- attach background shell tasks to their originating tool calls so
clients can update one command card and open its output log
- stream task progress and preserve running, paused, completed, failed,
and stopped states
- keep synchronous shell work, generic tools, and task notifications out
of user message chunks
- preserve typed session failures and root permission/elicitation
requests
- preserve the legacy generic Agent/Task representation and flattened
transcript opt-ins for clients without native subagent capability

## Compatibility

The published ACP SDK does not yet contain PR #1992 types, so the change
isolates the temporary structural cast in `src/acp-subagents.ts`.
Clients that do not negotiate native subagent sessions retain the
existing generic tool-call fallback, including the historical
`_meta["subagent-transcript"]` capability and `forwardSubagentText`
session option. Async tasks use the negotiated `asyncTasks` AIR
capability and remain invisible to clients that do not advertise it.
Claude does not expose a safe targeted child-cancel primitive, therefore
child `cancel` and `close` capabilities remain disabled.

## Validation

- `npm run check`
- `npm run build`
- `env -u ANTHROPIC_BASE_URL npm run test:run` (830 passed, 20 skipped)
- `npm run test:run -- src/tests/async-tasks.test.ts` (5 passed)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:11:13 +02:00
nikita-ashihmin
996d488589 feat: align Claude modes and clear-context planning (#1004)
## Summary
- translate Claude SDK permission suggestions and tool-specific
decisions into ACP v1 presentation and editable option metadata
- align Claude session permission modes and clear-context plan handling
- split permission effects, modes, normalization, option construction,
presentation, and response handling into focused modules
- document the permission extension and add unit plus live-audit
coverage for native edge cases

## Tests
- `npm run check`
- `npm run build`
- `env -u ANTHROPIC_BASE_URL npm run test:run` (931 passed, 27 skipped)
2026-08-22 14:59:03 +04:00
nikita-ashihmin
0581b9cf39 feat: align typed session failures with AIR protocol (#992)
## Summary

- align `_meta.jetbrains.air.sessionFailure` with AIR's
transcript-record protocol: `id`, `revision`, broad `category`, explicit
`severity`, `title`, optional `details`, and `actions`
- forward Claude's actual user-facing assistant/result text as `title`;
use adapter-authored fallback text only when Claude provides no message
(transport failure, worker EOF, or an internal invariant)
- reduce categories to `connection`, `access`, `limit`, `request`,
`service`, and `unknown`
- limit suggested actions to `retry`, `login`, and `new_session`
- update one transcript record through `id + revision`, keep resolved
entries in history, and remove clear/tombstone updates
- suppress duplicate assistant failure prose only for clients that
negotiated typed failures; preserve legacy ACP behavior otherwise
- restore synthetic usage-limit failures from session history with their
exact stored Claude text
- document negotiation, delivery carriers, identity/revision behavior,
mappings, recovery, and compatibility in the extension spec

## Validation

- `npm run check`
- `npm run build`
- `npm run test:run` — 777 passed, 26 skipped
- `git diff --check`

## Tracking

[IJAI-993](https://youtrack.jetbrains.com/issue/IJAI-993)
2026-08-14 17:29:39 +04:00
nikita-ashihmin
8b31dea11b feat: expose provider-neutral ACP goal extension (#964)
## Summary

- advertise a provider-neutral goal extension in
`InitializeResponse._meta.goal`
- map Claude SDK `active_goal` messages to
`session_info_update._meta.goal`
- expose the shared `_session/goal` control method with the truthfully
supported `clear` action
- document the extension as a candidate shape for a future first-class
ACP goal API

## Architecture

A goal is persistent **session state**, not the lifecycle of one
`session/prompt` request. `goal.status === "active"` says that the
objective may drive more work; it does not say that a prompt is
executing now.

This keeps two independent axes:

1. **Goal state** is published asynchronously as session metadata and
may survive multiple prompt turns.
2. **Prompt activity** follows the backend's actual running/quiescent
boundaries. While a turn runs, clients use advertised steering or prompt
queueing. Once the backend is quiescent, an ordinary `session/prompt` is
valid even if the goal remains active.

The metadata is deliberately provider-neutral:

```json
{
  "_meta": {
    "goal": {
      "version": 1,
      "controlMethod": "_session/goal",
      "actions": ["clear"]
    }
  }
}
```

State is emitted as `session_info_update._meta.goal`; `null` clears it.
The common snapshot vocabulary supports `objective`, common statuses,
timestamps in Unix milliseconds, budgets/usage, iteration count, and a
continuation reason. Each implementation emits only fields it actually
knows. This shape is intentionally close to a future typed ACP
capability/update so promotion out of `_meta` would not require a
provider-specific migration.

Control actions are capability-negotiated. The shared vocabulary
reserves `set`, `pause`, `resume`, and `clear`, but an agent advertises
only the subset it can implement with correct semantics.

## Claude mapping

The Claude runtime already emits internal `active_goal` messages,
although that message is not currently included in the public
`SDKMessage` union. The adapter handles it before the exhaustive switch:

- `condition` → `objective`
- `iterations` → `iterations`
- `set_at` → `createdAt`
- `last_reason` → `lastReason`
- `value: null` → `goal: null`

Provider bookkeeping such as `tokens_at_start` is intentionally not
exposed. Goal updates do not settle, extend, or otherwise mutate ACP
prompt lifecycle. Claude's existing Stop hook remains authoritative for
whether the backend itself continues working.

Only `clear` is advertised for out-of-band control. It submits `/goal
clear` through the existing session prompt queue, preserving the
adapter's established ordering and turn ownership. Setting a goal
remains available through `/goal <condition>` but is not advertised as a
control action because it starts the long-running Stop-hook workflow.

The companion Codex implementation is agentclientprotocol/codex-acp#371.

## Verification

- `npm run test:run` — 692 passed, 20 skipped
- `npm run build`
- `npm run check`
2026-08-06 17:01:56 +04:00
nikita-ashihmin
8663170f18 Add structured Bash titles and nested subagent transcripts (#881)
## Motivation

ACP clients currently have two gaps when rendering Claude Code activity:

1. Bash tool calls expose the shell command as the standard ACP title,
but the concise human-readable `description` produced by Claude Code is
lost.
2. ACP 1.2 has no standard subagent tool kind or nested-message
relationship, so subagent prose and child tools are either flattened
into the main conversation or filtered out entirely.

This PR adds namespaced metadata and an explicit client capability so
richer clients can render both cases without changing the standard ACP
schema or regressing legacy clients.

## What changed

### Bash descriptions

- Keeps the shell command in the standard ACP `title`, preserving
command-oriented clients.
- Exposes the optional Claude Code Bash `description` as
`_meta.claudeCode.title`.
- Applies the metadata consistently to eager permission tool calls,
normal tool calls, and streamed input refinements.
- Falls back to the command title when no description is available.

### Nested subagent transcripts

- Adds the opt-in client capability
`clientCapabilities._meta["subagent-transcript"] = true`.
- Enables the Claude Agent SDK `forwardSubagentText` option when the
client advertises support.
- Preserves the pre-existing caller-provided `forwardSubagentText: true`
option for compatibility.
- Marks Agent and legacy Task launches with `_meta.claudeCode.subagent =
true`.
- Relates nested text, thinking, and child tool calls to the launching
Agent/Task call through `_meta.claudeCode.parentToolUseId`.
- Preserves parent attribution during session-history replay, not only
during live streaming.
- Handles nested Agent-to-Agent launches without inferring subagents
from the generic ACP `think` kind.

## Compatibility

Clients that do not advertise `subagent-transcript` retain the previous
behavior: subagent text and thinking remain filtered from the top-level
feed.

The normal Agent/Task tool result is still emitted in every mode as the
protocol-compatible fallback. No existing result or standard ACP field
is removed or redefined.

## Validation

- `npm run build`
- `npm run check`
- `npm run test:run`
- 623 tests passed, 20 skipped

Coverage includes live and replayed nested text/thinking, child tool
attribution, Agent/Task launch markers, nested Agent launches, legacy
filtering, and SDK-option compatibility.
2026-07-27 07:22:07 +00:00
Ben Brandt
b409782992 Update for ACP Org (#471) 2026-03-26 12:19:06 +01:00
Steffen Deusch
101f09dbf8 Add single-file executable using bun (#332)
This adds a single-file executable using bun. The trick is to use a
separate flag for ACP `--acp` and spawn the regular `cli.js` bundled by
the Agent SDK if no flags are passed. This was, we can pass ourselves as
pathToClaudeCodeExecutable.
2026-02-20 12:23:04 +01:00
Ben Brandt
0e77751692 Claude Code -> Claude Agent (#319)
Update based on
https://platform.claude.com/docs/en/agent-sdk/overview#branding-guidelines
2026-02-17 12:14:55 +01:00
Ben Brandt
3db76b2d20 Update clients list in readme (#228) 2025-12-30 21:51:22 +00:00
Forte Scarlet
db965a31aa Change npm install command to global installation in README (#224)
As mentioned in #216, npm installations should be global, but the
documentation does not reflect this
2025-12-30 21:39:07 +00:00
Piet Brömmel
d19e399018 rename Claude Code SDK to Claude Agent SDK in the README und update the link (#220) 2025-12-18 22:27:09 +00:00
Ben Brandt
92c5cd84e8 Update clients list (#67) 2025-09-25 12:26:40 +00:00
Christian Tietze
8311a7a222 add Emacs client to README (#66) 2025-09-25 12:21:43 +00:00
Myles Scolnick
9ff7919ea4 readme: Add marimo notebook (#47) 2025-09-15 10:11:14 +02:00
Agus Zubiaga
5cd3ac91e8 Removing "Coming soon" 2025-09-08 13:18:28 -03:00
Xiangyi Li
96261a5b50 Remove dollar signs from CLI commands in installation section (#22)
Makes it easier to copy and paste the installation commands without
having to manually remove the dollar signs. This is a common convention
in documentation to improve user experience when following installation
instructions.
2025-09-03 22:01:24 -03:00
Oli
f70730f1ab Update README for CodeCompanion.nvim (#17)
Added CodeCompanion.nvim to the README, linking to my own setup
instructions.
2025-09-03 12:53:40 -07:00
Agus Zubiaga
eb282abb50 Fix readme format 2025-09-03 11:39:33 -03:00
Agus Zubiaga
74c0be057b Add npm badge 2025-09-03 11:36:39 -03:00
Agus Zubiaga
e66091f723 Add permission request note 2025-09-03 11:02:33 -03:00
Agus Zubiaga
f54da50bf5 Update README.md 2025-09-02 22:35:45 -03:00
Agus Zubiaga
28f36d7474 Format README 2025-09-02 18:24:51 -03:00
Agus Zubiaga
a07f132a9a Improve "Other clients" section 2025-09-02 18:23:05 -03:00
Agus Zubiaga
7d969fd9ff Update README.md 2025-09-02 18:17:17 -03:00
Conrad Irwin
03b51ae927 README v0 2025-08-28 15:20:53 -06:00