Compare commits

..

89 Commits

Author SHA1 Message Date
Slapoguzov Alexander
8bbdea6cc6 fix: honor configured fallback for unavailable restored modes (#1520)
A restored transcript mode such as `dontAsk` is recognized by the Claude
SDK but absent from the ACP mode catalog. It previously reached
initialization and was forced to Manual, skipping a supported
`permissions.defaultMode` from settings.

Use the same catalog for restored-mode selection and initialization.
Supported restored modes retain precedence; an unavailable restored mode
falls back to an available configured mode, then Manual. Explicit
initial modes, bypass restrictions, model-specific Auto handling, and
new-session configuration behavior remain unchanged.

Validation: TypeScript build, ESLint, Prettier, and 243 focused tests
pass. Added regressions cover helper selection plus v1 and v2 responses;
nine checks fail against unchanged production code.

The full local suite **FAILED**: 3,549 passed, 20 failed, 30 skipped.
Four selected failures also reproduce on unchanged base: three
session-index cases (watcher setup, live notification, SDK path-list
parity) and one background-work case. The remaining 16 failures were not
individually classified. No test timeouts or product behavior were
changed to suppress them. Live CLI and IDE permission-enforcement
acceptance remain unverified.

Co-authored-by: Codex <codex@openai.com>
2026-11-03 10:40:14 +01:00
Ben Brandt
1e986b526d refactor: give each session one owner of its background work (#1517)
Follow-up to #1510, #1511 and #1512. Clients receive exactly what they
did before.

## Why

The v2 background work grew as about 14 free functions taking `(session,
sessionId, …)` and reaching into six `Session` fields. Four of them it
kept itself: the task set, terminals, tool calls, and
`reportsBackground`. Three belong to other modules:
`liveBackgroundTasks`, the subagent map, and `toolUseCache`. The repo
guidelines ask for one module that owns the state.

## What changed

- **One owner per session.** `SessionBackgroundWork`, stored on
`Session.background`, owns the task set, terminals, tool calls, output
tails, and whether the client gets the lists. It has one method per
event: a Bash result, tool results, a task's end or pause, a new task
set, and a closed query.
- **The reporter only filters.** The object builds each session's
report, so `BackgroundWorkReporter` only drops unchanged lists and
forwards the rest.
- **No reference back to the session.** A recreated session (sign-out
respawn, provider change) gets a new `Session` under the same id, so an
old consumer must not reach the new one. Callers pass in the state other
modules own.
- **No `toolUseCache` dependency.** Whether a task's launching call has
returned used to be judged by the call still being in `toolUseCache`,
which Claude Code prunes at the tool's result. The object now records
returned calls itself, capped at 1024 like the other per-session sets.
- **Stricter typing on the output path.** Typing the terminals map
properly exposed that the parsed `outputFilePath` was loosely typed, so
it is now checked to be a string.
- **Test doubles.** The hand-built `Session` doubles in
`acp-agent.test.ts` and `session-doubles.ts` now carry the new required
field.

## Tests

- The existing suite passes unchanged.
- One new v2 test covers a task whose `task_started` arrives after its
call returned, so it is listed as `_claude/task`. A first version of
this refactor recorded "returned" per task and silently missed that
case. The test fails against that version, and against listing tasks
before their call returns.

`npm run check`, `npm run build` and `npm run test:run` pass (3556
passed, 30 skipped).
2026-11-02 19:52:25 +01:00
Ben Brandt
53e3eec013 feat: stream the output of v2 background commands (#1512)
Follow-up to #1510 and #1511, phase 2b of the v2 [Background Work
RFD](https://agentclientprotocol.com/rfds/v2/background-work).

Claude Code streams no output of a Bash command that went to the
background: it writes the output to the task's output file. So the
command's terminal showed only the Bash result's placeholder ("Command
running in background with ID …") until the command exited. The v2 spec
says the Agent **SHOULD** keep a listed terminal's output current
whenever it can read the process output.

## What a v2 client gets

- **Snapshot:** the file's first text replaces the placeholder as a
`terminal_output` snapshot.
- **Chunks:** later text is appended as `terminal_output_chunk`s.
- **Task end:** the rest of the file is read before the terminal's
`exitStatus`, as the spec asks.
- **Query close:** reading stops.

## Design

- New `src/background-output.ts` (`BackgroundOutputTail`) follows one
file:
- It polls every 500 ms, because watching a file is not reliable on
every platform and a missing file just means nothing was written yet.
- It reads at most 256 KiB per read, so a burst of output doesn't block
the event loop. Each read is sent before the next, including when
reading to the end at the task's end.
- Past 1 MiB of unread output (a command writing faster than the polls
read, about 512 KiB/s), it skips to the file's last 1 MiB and sends it
as a replacing snapshot, so the terminal shows the latest output without
a gap in it. A file that shrinks is read again from its start, also as a
snapshot.
- Its timer is unref'd, so a long-running command doesn't keep the agent
process alive.
- A `StringDecoder` holds a UTF-8 character split across reads until its
last byte is written.
  - Reads are serialised.
- `background-work.ts` starts the tail once the Bash result's own
updates are sent, so the first output replaces the placeholder rather
than coming before it. It finishes the tail before the terminal's exit
and stops it when the query closes. `acp-agent.ts` only gets wiring.
- The output path comes from the Bash result's text, the only place the
SDK gives it before the task ends. The existing
`asyncTaskOutputFilePath` parser only accepts an absolute path ending in
`tasks/<taskId>.output` for this task. Without a path, the terminal
keeps the placeholder as before.
- v2 only (`reportsBackground`), like the rest of the background work.
v1 clients that render terminals through the `_meta` extension could use
it too. That is a small follow-up, but it changes what v1 clients
receive.

## Tests

- Nine unit tests for `BackgroundOutputTail`:
  - the replacing first read, and appends
  - a file that appears later, and a file that never appears
  - a UTF-8 character split across reads
  - finishing, an empty output, and stopping
  - a backlog past 1 MiB, and a file that shrinks
- One v2 test over a real connection with a real output file. It checks
that the placeholder is replaced, later output arrives as chunks, and
the rest arrives before the exit.
- I checked by mutation that removing the read-before-exit, the
replacing snapshot, the UTF-8 decoder, the backlog cap or the shrink
restart each fails a test.

`npm run check`, `npm run build` and `npm run test:run` pass (3555
passed, 30 skipped).
2026-11-02 19:37:49 +01:00
acp-release-bot[bot]
981bf9a28a chore(main): release 0.100.0 (#1485)
🤖 I have created a release *beep* *boop*
---


##
[0.100.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.1...v0.100.0)
(2026-11-02)


### Features

* list v2 background tool calls
([#1511](https://github.com/agentclientprotocol/claude-agent-acp/issues/1511))
([4d79b9f](4d79b9ffbc))
* report subagent working directories
([#1496](https://github.com/agentclientprotocol/claude-agent-acp/issues/1496))
([eb78afa](eb78afa855))
* report v2 background work
([#1510](https://github.com/agentclientprotocol/claude-agent-acp/issues/1510))
([5565259](5565259771))
* report why Claude Code could not start a session
([#1508](https://github.com/agentclientprotocol/claude-agent-acp/issues/1508))
([38cf656](38cf656fc9))


### Bug Fixes

* align session/cancel, session/close, and v2 setup with the ACP v2 spec
([#1494](https://github.com/agentclientprotocol/claude-agent-acp/issues/1494))
([f50598a](f50598a39c))
* forward v2 elicitation values and read dismissed v2 permission prompts
as a refusal
([#1493](https://github.com/agentclientprotocol/claude-agent-acp/issues/1493))
([3bc6711](3bc671193c))
* keep native Windows paths in v2 git patches
([#1492](https://github.com/agentclientprotocol/claude-agent-acp/issues/1492))
([c3428de](c3428de1bf))
* preserve live session settings across provider refreshes
([#1474](https://github.com/agentclientprotocol/claude-agent-acp/issues/1474))
([e01a288](e01a288384))
* reject unreadable skill manifests
([#1482](https://github.com/agentclientprotocol/claude-agent-acp/issues/1482))
([99a6e76](99a6e76158))
* report the fresh context's usage when ExitPlanMode clears context
([#1484](https://github.com/agentclientprotocol/claude-agent-acp/issues/1484))
([2fd46c8](2fd46c866d))
* update to @agentclientprotocol/sdk 2.2.0
([#1491](https://github.com/agentclientprotocol/claude-agent-acp/issues/1491))
([1d2b92f](1d2b92feae))
* update to @agentclientprotocol/sdk 2.4.0
([#1509](https://github.com/agentclientprotocol/claude-agent-acp/issues/1509))
([720bbb3](720bbb3e4c))
* update to Claude Agent SDK 0.3.316 (Claude Code 2.1.316)
([#1507](https://github.com/agentclientprotocol/claude-agent-acp/issues/1507))
([1d03064](1d0306468b))
* validate session, prompt, config, and auth requests per the latest ACP
spec
([#1495](https://github.com/agentclientprotocol/claude-agent-acp/issues/1495))
([d2ef490](d2ef490a31))

---
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>
2026-11-02 15:33:56 +01:00
Ben Brandt
4d79b9ffbc feat: list v2 background tool calls (#1511)
Follow-up to #1510, phase 2a of the v2 [Background Work
RFD](https://agentclientprotocol.com/rfds/v2/background-work).

A Monitor, Workflow or backgrounded MCP call returns as soon as its task
starts, so its tool call completed at its result while the task ran on.
#1510 listed such tasks as `_claude/task` items, which clients don't
recognise. This PR lists them as the tool calls that started them, as
the RFD's adapter table describes.

## What a v2 client gets

| When | The tool call |
|---|---|
| Tool result, while the task is in Claude Code's live background task
set | Reports `in_progress` instead of `completed`, and is listed as a
`tool_call` item |
| While listed | Advertises `capabilities.cancel`. `tool_call/cancel`
stops the task with `stopTask`, or returns `-32602` if the call isn't
listed (also when its task ended mid-cancel) |
| `task_updated` with `paused` / `running` | `paused` / `in_progress` |
| The task ends | `completed`, `failed`, or `cancelled` for a stopped
(`killed`) task, with `capabilities: null`, before the list drops it.
The same happens when the live set drops the task without an end event
(`completed`, since nothing was reported), or the query closes
(`cancelled`) |
| `session/resume` | The call's current state, rebuilt from its tool
use, then its capability, before the snapshot |

The task is matched to its call through the structured `taskId` of a
Monitor or Workflow result, or through the live task whose
`task_started.tool_use_id` names the call (backgrounded MCP calls).

`session/cancel` no longer stops background workflows for sessions that
report their background work, because listed work must keep running.
Clients stop them with `tool_call/cancel` instead. v1 and AIR are
unchanged.

A task without such a call is still a `_claude/task` item. It is now
listed only once its `task_started` has arrived and the call that
started it, if any, has returned. Before that, the foreground work is
still waiting on that call, and the spec says foreground work is not
listed.

## Design

- `background-work.ts` holds the logic: registering background tool
calls, pause and end updates, the shared `cancelBackgroundWork` for
`terminal/cancel` and `tool_call/cancel`, and the resume states.
- `acp-agent.ts` only gets wiring.
- `paused` travels as a cast v1 status, alongside the existing
`V2_CANCELLED`.
- `V2Background` advertises and clears the cancel capability.

## Tests

- Six new v2 tests, using the real CLI's event order (tool use, task
joins the live set, `task_started`, tool result) and the `killed` +
`stopped` pair for a stopped task. They cover:
  - listing, pause and resume, and the final status
  - `tool_call/cancel`
  - a query that closes
  - a listed Workflow left running on `session/cancel`
  - resume
  - the `_claude/task` fallback
- I checked by mutation that each new guard fails a test when removed.

`npm run check`, `npm run build` and `npm run test:run` pass (3545
passed, 30 skipped).

## Still to come

- Tailing background Bash `output_file` into terminal output (phase 2b).
- `ScheduleWakeup` / `CronCreate`, which need a signal from the SDK.
- The SHOULD gap about follow-up work is being relaxed upstream in
agentclientprotocol/agent-client-protocol#2745.
2026-11-02 14:40:01 +01:00
Ben Brandt
5565259771 feat: report v2 background work (#1510)
Implements phase 1 of the ACP v2 [Background Work
RFD](https://agentclientprotocol.com/rfds/v2/background-work), now part
of schema v2.0.0-alpha.13. v2 sessions report the work Claude Code keeps
running independently of foreground work as `background` on every
`state_update`.

## What the client gets

The lists are built from Claude Code's live background task set
(`background_tasks_changed`). Tasks Claude Code marks `ambient` are
skipped, since they are housekeeping. The root session and each subagent
session (subagents RFD form) has its own list. Each task is listed once,
in the list of the session that runs it:

| Work | Listed as |
|---|---|
| A Bash command that went to the background | Its display terminal,
from the Bash result until its task ends. While listed, the terminal
advertises `capabilities.cancel`, and `terminal/cancel` stops its task
with `stopTask`. |
| A detached subagent | Its child session, while it works, and while it
is idle with background work of its own. The child reports its own list
on its `state_update` and on the parent's `subagent_update.state`
mirror. |
| Any other task (`Monitor`, `Workflow`, `mcp_task`, …) | A
`_claude/task` item with `taskId` and `description`. Its tool call ends
at its tool result, so it cannot be a `tool_call` item yet. |

The ordering rules the spec requires:
- **List changes:** the state is re-sent with the new list and no stop
reason, so a repeated `idle` is not a new completion.
- **Before a terminal is listed:** a `terminal_update` advertising
`cancel` goes out first.
- **Before a terminal leaves the list:** its `exitStatus` goes out, with
`capabilities: null`. This also holds when the level drops the task
without an end event, or when the query closes (process death, stream
error, sign-out recreation, `session/close`).
- **A child and its parent:** the child's own list goes out before the
parent's list drops it.
- **`session/resume`:** each listed subagent's current `subagent_update`
and each listed terminal's current `terminal_update` come before the
state snapshot.
- **`terminal/cancel`:** it returns `-32602` for a terminal that is not
listed, including one whose command ended while the stop was in flight.

## Gating

Only v2 sessions with `perTaskStopAffordance` report lists. That is the
default for a non-AIR v2 client. Without it, Claude Code's interrupt
kills background tasks, which would break the rule that `session/cancel`
leaves listed work running. v1 and AIR are unchanged.

## Design

- `src/background-work.ts` owns the lists. It builds them from session
state and publishes changes. `acp-agent.ts` only gets wiring calls: one
sync per message-loop iteration, the level update, terminal exits when a
query closes, and the `terminal/cancel` entry point.
- `src/v2/background.ts` adds the lists to v2 state updates and to
subagent state mirrors. The cancel capability lives in `V2Terminals`,
which already owns terminal state.
- `NativeSubagentRuntime.currentUpdate` builds a child's live
`subagent_update` for resume.

## Not in this PR

- **SHOULD gap:** when finished background work starts a follow-up
cycle, the spec wants `running` in the same snapshot that drops the
item. We send `idle []` and then `running`, because the adapter can't
know in advance that Claude Code will start a follow-up. The RFD
acknowledges this.
- **Phase 2:**
- List `Monitor`, `Workflow` and `mcp_task` as `tool_call` items that
stay `in_progress` (or `paused`), with `tool_call/cancel`.
  - Tail background Bash `output_file` into terminal output.
- **Phase 3:** `ScheduleWakeup` / `CronCreate`. The SDK gives no signal
for when one fires or is removed.

## Tests

- New v2 tests cover:
- terminal listing, cancel advertisement, exit ordering, ambient tasks,
and a level drop without an end event
- query death, `terminal/cancel` including the race, and resume for both
terminals and subagents
- a background subagent, an idle subagent kept listed by its own
command, `_claude/task` items, and sessions without
`perTaskStopAffordance`
- I checked by mutation that the guards for each required ordering fail
a test when removed.
- The v2 golden files gain `background: []`, and the subagent ones also
gain child lists.

`npm run check`, `npm run build` and `npm run test:run` pass (3540
passed, 30 skipped).
2026-11-02 14:16:10 +01:00
Ben Brandt
720bbb3e4c fix: update to @agentclientprotocol/sdk 2.4.0 (#1509)
Updates `@agentclientprotocol/sdk` from 2.2.0 to 2.4.0 (schema v1.29.1
and v2.0.0-alpha.13).

## What changed in the protocol

- **v1:** the stable schema and spec pages only changed wording. There
are no requirement changes.
- **v2 `session/cancel`:** the Client no longer answers pending
permission requests with `cancelled`. The Agent MUST stop waiting for
the cancelled work's permission requests and SHOULD cancel each one with
`$/cancel_request`.
- **v2 `session/close`:** the existing MUST to cancel pending
elicitations with `$/cancel_request` before responding now covers
permission requests too.

The adapter already did both: the tool call's signal and the session's
abort controller cancel the request, and a hand-written abort race
stopped the wait. Until now, no test checked that a v2 client actually
receives `$/cancel_request` for a permission request.

## Changes

- Permission requests and elicitations, on both v1 and v2, now use SDK
2.4.0's `stopWaitingOnCancel` option instead of `raceWithAbort`. The SDK
sends `$/cancel_request`, rejects at once with `-32800`, and quietly
drops any late response. Every caller already maps a rejection after an
abort to "Tool use aborted" or to `cancel`, so nothing else changes.
`raceWithAbort` is still used for the eager `tool_call` notification.
- **Tests:**
- **v2:** the existing tests where a `session/cancel` or `session/close`
withdraws a held request now also check that the client received
`$/cancel_request`. For `session/close`, it must arrive before the
response.
- **v1:** new tests over a real SDK connection with a client that holds
requests. These cover the v1 `ClientConnection` wiring, which the
mocked-client tests bypass.
- The v1 mock clients that "ignore cancellation" now model the SDK's
behaviour with a `heldRequest` helper.
- Removed one test that only covered `raceWithAbort` handling a
rejection from the client.
- I checked that the new assertions fail if the `stopWaitingOnCancel`
option is removed or v2 permission requests stop passing their
cancellation signal.

## Follow-ups (not in this PR)

- v2 background work: the `background` list on state updates, the
`paused` status, and `tool_call/cancel` / `terminal/cancel`. The AIR
`asyncTasks` extension stays as it is.
- The new unstable v1 methods (`session/set_title`,
`session/archive`/`unarchive`, `session/status`, `auth/status`).

`npm run check`, `npm run build` and `npm run test:run` pass (3520
passed, 30 skipped).
2026-11-02 11:41:32 +00:00
Ben Brandt
38cf656fc9 feat: report why Claude Code could not start a session (#1508)
When Claude Code refuses to start (invalid proxy URL, wrong organization
sign-in, a required sandbox that can't run, …), `session/new`,
`session/load`, `session/resume` and `session/fork` used to fail with a
generic `Claude Code process exited with code 1. stderr: …` error.

This sets `CLAUDE_CODE_STARTUP_FAILURE_RESULTS=1` on the query env, so
the CLI writes an error result with a `startup_failure_reason` before
exiting on a known startup failure. The SDK then rejects
`initializationResult()` with an error that carries
`startupFailureReason`. The new `src/startup-failure.ts` maps it to an
ACP error:

| `startupFailureReason` | ACP error |
|---|---|
| `gateway_signin_required`, `org_pin_mismatch`, `org_config_refused` |
`authRequired` (signing in again, or with another account, fixes it) |
| `cwd_unavailable` | `invalidParams` (matches the existing cwd
validation) |
| any other reason | `internalError` |

The message is the CLI's own text, which says what to fix (e.g. `Invalid
proxy URL in HTTPS_PROXY: … Fix or unset HTTPS_PROXY and restart Claude
Code.`), and `data.startupFailureReason` carries the reason. Errors
without a reason (older CLIs, unknown causes) pass through unchanged,
and the existing `sessionNotFound` mapping for resumes is untouched.

Checked against the real CLI (SDK 0.3.316): with `HTTPS_PROXY="not a
url"`, `initializationResult()` rejects with `startupFailureReason:
"proxy_invalid"` when the variable is set, and with a plain exit-code
error without it.

## Testing

- v1: `create-session-options.test.ts` covers the env var, each mapping,
and pass-through of errors without a reason.
- v2: `acp-v2.test.ts` covers the `session/new` error over the wire.
- `npm run build`, `npm run check`, `npm run test:run` pass. The
subprocess integration tests also passed locally with this change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 11:23:33 +00:00
Ben Brandt
1d0306468b fix: update to Claude Agent SDK 0.3.316 (Claude Code 2.1.316) (#1507)
Updates dependencies to their latest versions.

- `@anthropic-ai/claude-agent-sdk` 0.3.312 → 0.3.316 (bundles Claude
Code 2.1.316)
- `@types/node` 26.6.5 → 26.6.6, `eslint` 10.13.0 → 10.14.0, `globals`
17.13.0 → 17.14.0
- `npm update` for transitive patch/minor bumps in the lockfile
- Bumps the "as of" version in two comments about SDK type gaps
(`Query.getSettings()` still missing from the declarations; `unanswered`
still only in the AskUserQuestion output schema). Both were re-checked
against 0.3.316.

TypeScript 7.0.2 is left out: `@typescript-eslint` 8.72.1 supports
TypeScript `<6.1.0`.

## Changelog review (SDK 0.3.313–0.3.316, Claude Code 2.1.313–2.1.316)

No required changes. The SDK type changes only add fields and union
members (`sandbox_required_unavailable` startup failure reason,
`blockWritesOutsideWorkingDirectories` / `blockReadsExemptPaths`
settings, `pending_elicitation_requests` on `initialize`, alpha
`prewarm()` in `/core`), none of which the adapter reads or needs. No
new `fast_mode_disabled_reason` values accompany the Opus 4.8 fast mode
deprecation.

Follow-up planned separately: use `CLAUDE_CODE_STARTUP_FAILURE_RESULTS`
/ `startup_failure_reason` to report typed startup failures to clients.

## Testing

`npm run build`, `npm run check`, `npm run test:run` pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 12:18:36 +01:00
Andrey Bragin
c6de68797b docs: add design-fit and test-duplication guidelines to AGENTS.md (#1480)
Adds guidelines to `AGENTS.md` that raise the baseline quality of new
PRs and features. Changes should fit the existing architecture instead
of working around it, respect the session's turn lifecycle, stay
compatible with existing clients, and come with focused, non-duplicated
tests. Authors and their agents can check these points before review.

- **Coding guidelines** replaces the **Code comments** section and
covers only the code itself. The comment rules are now one bullet, which
also requires comments and docs to be true on every code path they
describe. It adds rules for refusing requests with an actionable
`RequestError`, not reporting success for work started without awaiting
it, not letting results depend on cache state, and stating unenforced
ordering assumptions.
- **Architecture and design fit** is a new section. Build on the module
that owns the concept, put new features and components in dedicated
modules instead of `acp-agent.ts`, prefer extending existing mechanisms,
reuse sibling logic so paths that handle the same input (live stream and
`session/load` replay, command list and dispatch, `prompt` and `steer`)
apply the same rules, keep ACP v1 and v2 on one implementation, use
`startTurn`'s busy check and the turn queue for anything that sends
`session/update`, avoid branching on stale local copies of remote state,
don't shadow existing commands, keep backward compatibility, and explain
any unavoidable workaround.
- **Testing guidelines** is a new section. It adds rules against
duplicate or parametrizable tests, asks to reuse the existing test files
and helpers, to build test doubles from what the real Claude Code CLI
sends, to cover both protocol versions when client-visible output
changes, and to have a test for each behavior the docs promise.
2026-11-02 11:40:07 +01:00
Ben Brandt
eb78afa855 feat: report subagent working directories (#1496)
Schema v1.28.0 / v2.0.0-alpha.11 adds `cwd` and `additionalDirectories`
to `subagent_update` (agentclientprotocol/agent-client-protocol#2593,
#2660). When a child runs in a different directory from its parent, for
example an Agent call with `isolation: "worktree"`, the Agent must
report the child's `cwd` before sending child traffic that depends on
it.

**Signal**
- The agent reads the `cwd` of the child's own hooks, matched by
`agent_id`. The existing per-child configuration hook already runs on
SubagentStart, PreToolUse, PostToolUse and the others.
- Claude Code runs SubagentStart before the child's first model request
and waits for the hook, so the `cwd` reaches the client before the
child's first tool call.
- `worktreePath` in the Agent tool result arrives only when the child
finishes, which is too late.

**Behaviour (RFD form only; AIR is unchanged)**
- `cwd` is sent only when the child's directory differs from its
parent's, compared with `realpath`. A hook that runs before the
announcement puts the `cwd` in the announcing `subagent_update`.
Otherwise a separate `subagent_update { cwd }` follows.
- A child that moves back to its parent's directory gets `cwd: null`. A
child whose hook `cwd` changes later, for example after a `cd` that
Claude Code keeps, is reported again, since that is where it now runs.
- `additionalDirectories` is never sent. Claude Code gives subagents the
session's additional directories, so inheriting them is accurate.
- Replay uses the child's `.meta.json` (`cwd`, `worktreePath`,
`inheritedWorktreePath`) and the first `cwd` in its transcript. After
the load response, a child whose directory no longer exists gets `cwd:
null`.
- v2 passes the fields through unchanged.

**Not covered**
- `remote` isolation, which fires no local hooks.
- After a reconnect to a live session, the `cwd` the runtime holds is
not sent again.
2026-10-30 16:05:38 +01:00
Ben Brandt
d2ef490a31 fix: validate session, prompt, config, and auth requests per the latest ACP spec (#1495)
Request validation from the ACP schema v1.28.0 / v2.0.0-alpha.11 review.
Each change has a test.

**Paths**
- `session/list` rejects a relative `cwd` with -32602. Without a session
index it passes `includeWorktrees: false`, so the filter matches only
sessions of exactly that directory, not other git worktrees of the same
repo.
- Resume, load and fork check `cwd` and `additionalDirectories` before
anything else. Before, a resume with a bad `cwd` tore down the live
session and then failed, and a fork copied the transcript first.
- Relative `additionalDirectories` entries are rejected with -32602.

**Session setup (v2)**
- Uses the SDK's `assertSupportedMcpServers`, which also rejects
duplicate MCP server names with -32602.
- Rejects an empty `prompt` with -32602.
- The v2 initialize response no longer advertises the AIR `asyncTasks`
capability or the `goal` entry, since the v2 app has no handlers for
`_session/async_task/stop` or `_session/goal`. v1 AIR is unchanged.

**Errors**
- `session/fork` of an unknown session returns -32002 with
`data.sessionId` instead of -32603. This matches the Claude SDK's
"Session … not found" and "Invalid sessionId" messages.
- `authenticate` with an unknown `methodId` returns -32602 instead of
-32603.
- `set_config_option` rejects a value whose type doesn't match the
advertised option type. A `type: "boolean"` option takes `true`/`false`,
and a select option takes a string. Model aliases such as `"opus"` are
still accepted.
- An AIR `session/delete` of a session that has no transcript now
succeeds instead of failing with a generic error.

**Prompts and commands**
- v1 `session/prompt` rejects `audio` and unknown content blocks with
-32602 before queueing them. Before, they were silently dropped.
- Advertised command names that contain whitespace are dropped, and of
duplicate names only the first is kept.
- The `/mcp:server:prompt` rewrite now accepts arguments that span
lines, and applies only to the first text block.
2026-10-30 16:03:58 +01:00
Ben Brandt
f50598a39c fix: align session/cancel, session/close, and v2 setup with the ACP v2 spec (#1494)
**`session/cancel` cancels only earlier prompts**
(agentclientprotocol/agent-client-protocol#2685)
- `cancelTurns` used to read the turn queue again after two awaits: the
compaction interrupt and publishing the subagent state. A prompt that
arrived during those awaits was cancelled too, which is -32800 on v2.
- Each prompt now gets a sequence number when `startTurn` receives it.
The cancel records the count before its first await and cancels only
turns at or below it. That includes a prompt received earlier that
`startTurn` had not queued yet.
- Known gap: if the cancelled turn ends during those awaits and Claude
Code starts the newer prompt, the `query.interrupt()` that follows can
still stop that prompt. Fixing it means skipping the interrupt when
nothing the cancel covers is still running, which also affects
cancelling background work, so it is left for later.

**`session/close` cancels pending requests first**
(agentclientprotocol/agent-client-protocol#2683)
- MCP elicitations, AskUserQuestion, the refusal-fallback dialog and
permission requests now combine their callback signal with the session's
`abortController.signal`.
- `teardownSession` therefore sends `$/cancel_request` for each of them
before `session/close` responds, instead of relying on Claude Code to
abort its callback signals in time.

**v2 setup responses** (agentclientprotocol/agent-client-protocol#2688,
#2603)
- If reading the commands for the setup response fails, the agent now
sends them in an `available_commands_update` after the response. Before,
the client was never sent them.
- On v2, MCP OAuth starts after the `session/new`, resume or fork
response is sent, so its URL elicitation no longer reaches the client
before the response.

Not done: withdrawing a held prompt with `$/cancel_request`. The user
message is already in Claude Code's input once it is queued, and
removing it safely needs `cancel_async_message`, which not every CLI
version has. agentclientprotocol/agent-client-protocol#2636 allows
ignoring the cancel and sending the normal response.
2026-10-30 16:03:03 +01:00
Ben Brandt
3bc671193c fix: forward v2 elicitation values and read dismissed v2 permission prompts as a refusal (#1493)
**Elicitation content** (agentclientprotocol/agent-client-protocol#2595)
- v2 lets an elicitation answer hold any JSON value, such as an object.
The agent checked accepted content with the v1 guard, which allows only
strings, numbers, booleans and string arrays. A v2 accept with an object
therefore reached the MCP server as `{ action: "accept", content: {} }`.
- The agent now forwards the accepted content unchanged, and the MCP
server checks it against its own schema. If Claude Code's MCP client
still uses the older value types, it rejects such an answer with an
error instead of passing on empty content.
- AskUserQuestion reads only string, number and boolean picks, so it no
longer shows an object as `[object Object]`.
- The MCP OAuth url-mode check now tests `action !== "accept"` instead
of the v1 guard.

**Permission `cancelled` on v2**
(agentclientprotocol/agent-client-protocol#2582, #2658)
- In v2, `cancelled` also means the user dismissed the prompt or the
client declined it by policy.
- When no `session/cancel` is in progress, a foreground v2 `cancelled`
is now denied as "User refused permission to run tool", the same denial
background subagents already get, and the turn goes on. Before, Claude
Code reported it as "Tool permission request failed".
- During a `session/cancel` it is still aborted. v1 is unchanged.
- Unknown v2 outcomes map to `cancelled`, so they are also denied now
instead of aborted.
2026-10-30 14:44:34 +00:00
Ben Brandt
c3428de1bf fix: keep native Windows paths in v2 git patches (#1492)
ACP v2 now says each `git_patch` path, once unquoted, is the same native
absolute path string as the matching `changes` entry
(agentclientprotocol/agent-client-protocol#2650).

The v2 form converted `C:\work\App.ts` to `C:/work/App.ts`, so on
Windows the patch and `changes[].path` named different strings. The v2
form now keeps the native path, which Git quoting writes as
`"C:\\work\\App.ts"`. Git's own form, which the AIR extension uses,
still uses forward slashes and the `a/`/`b/` prefixes.
2026-10-30 14:39:21 +00:00
Ben Brandt
1d2b92feae fix: update to @agentclientprotocol/sdk 2.2.0 (#1491)
Moves to ACP schema v1.28.0 and v2.0.0-alpha.11.

- v2 dropped JSON-RPC batching, so `AnyWireMessage` is gone. The test
harnesses use `AnyMessage`, and a request id can now be `null` in the
types.
- The v2 elicitation schema now requires `properties`. A forwarded MCP
schema without them, such as `{ "type": "object" }`, gets an empty map,
so clients on SDK 2.2.0 don't reject it.

Runtime changes that come with the SDK and need no agent code:
- The SDK handles a repeated `initialize`, rejects batches, and rejects
messages sent before initialization. A failed `initialize` now leaves
the connection open.
- v2 `usage_update.cost` is now a patch field, where omitted means
unchanged. The agent sends `cost` only on the result update, so the
client keeps showing the last cost during a turn.

Follow-up PRs for the spec changes in this range are stacked on this
branch.
2026-10-30 14:39:09 +00:00
xiaofeilovecoding
99a6e76158 fix: reject unreadable skill manifests (#1482)
Fixes #1478.

Skip SKILL.md candidates when opening or reading the file fails, while
preserving readable unnamed manifests. Do not cache failed reads so
later resolutions can recover.

Adds nine resolver regressions for open/read errors, descriptor cleanup,
recovery, and valid alternatives.

Validation (Windows, Node 24.18.0):
- Nine new tests, TypeScript build, and ESLint pass.
- Full suite: 3,354 passed, 57 failed, 52 skipped. The same 57 failures
reproduce on unmodified 52c241e in the affected files.
- npm run check fails only on unchanged CLAUDE.md: Windows checked out
the symlink as a plain target file. The baseline has the same issue.

Co-authored-by: xiaofeilovecoding <fxy850328059@gmail.com>
2026-10-30 13:57:42 +01:00
Slapoguzov Alexander
e01a288384 fix: preserve live session settings across provider refreshes (#1474)
## Problem

When the provider changes on a running agent (`providers/set`,
`providers/disable`, or logout with a provider override), every open
session gets a new Claude Code process. For example, this happens after
a BYOK key edit or a proxy recovery. The new process started from the
original `session/new` params only. The session lost the model, effort,
mode, Fast mode, ultracode and agent the user had picked, while the
client still showed the old values (IJAI-4592).

## Fix

- After the new process starts, the session's previous choices are
applied again through the same code paths as client requests. The client
then gets a `config_option_update` with the actual configuration.
- If the new provider doesn't support a choice (for example, the new key
has no access to that model), the choice is logged and skipped. The
client sees the real value.
- Effort: only the user's current pin comes back. An effort the user
reset to Default, or that a model switch dropped, stays off.
- Mode, config changes and restarts run one at a time per session. A
choice sent during a restart lands after the restored ones. Other
sessions are not blocked.
- The respawn after a sign-out goes through the same path, so it keeps
the choices too.
- When a provider change overlaps that respawn, it replaces the current
recovered session and holds waiting prompts until the provider update
finishes. Those prompts then run once on the latest route with the
restored choices.
- Closing a session while it restarts no longer leaves a running process
behind.

## Tests

- `provider-session-configuration.test.ts`: the overlapping
sign-out/provider change and two waiting prompts held until the final
replacement initializes.

- `provider-session-configuration.test.ts`: restore order, effort edge
cases, races, and close or dispose during a restart.
- `session-hooks.test.ts`: real Claude Code CLI against a local fake
API. After a refresh, the next request goes out with the chosen model
and `effort: high`.

## Validation

[CI on the published
follow-up](https://github.com/agentclientprotocol/claude-agent-acp/actions/runs/40989963422)
passes formatting, lint, build and tests: 3,474 passed, 32 skipped,
including 37 provider, 107 authorization and five real CLI-hook tests
against a local fake API. Independent review passed; 151 scoped tests
also covered seven additional recovery/failure schedules.

<details><summary>Local baseline comparison and scope</summary>

The added overlap regression failed on the preceding draft and passes
with this repair. Complete local runs used identical locked dependencies
and two workers: baseline 3,431 passed / 19 failed / 30 skipped;
candidate 3,433 passed / the same 19 failed / 30 skipped. All local
failures are existing session-index cases; filesystem watching reported
EMFILE, with two SDK path-list assertions also failing. The complete
Linux CI run above passes.

This follow-up is a normal appended commit preserving the existing
implementation. The PR remains a draft. Released-package Air acceptance
follows merge and release.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Aleksandr Slapoguzov <aleksandr.slapoguzov@jetbrains.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:57:13 +01:00
Ben Brandt
2fd46c866d fix: report the fresh context's usage when ExitPlanMode clears context (#1484)
Choosing one of `ExitPlanMode`'s "clear context" options restarts
Claude's conversation with only the plan. The ACP session ID and the
turn stay the same, and the adapter resets its context count to 0, but
it didn't tell the client. Clients kept showing the old context's usage
until the fresh context first reported its own, so nothing on the client
side showed that the context had been cleared.

`publishSessionState` now sends a `usage_update` for the empty fresh
context (`used: 0`, the fresh session's context window as `size`) right
after the `current_mode_update` and `config_option_update` it already
sends. `used: 0` slightly understates the fresh context, which already
holds the system prompt and the plan, until Claude's next usage report.

Testing:
- `npm run build`, `npm run test:run`, and `npm run check` pass.
- `clear-context-coordinator.test.ts` checks the update's order and
values.
2026-10-29 22:09:59 +01:00
acp-release-bot[bot]
0f78dbbdd0 chore(main): release 0.99.1 (#1472)
🤖 I have created a release *beep* *boop*
---


##
[0.99.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.0...v0.99.1)
(2026-10-29)


### Bug Fixes

* add approval id to plan
([#1469](https://github.com/agentclientprotocol/claude-agent-acp/issues/1469))
([4b4b2c4](4b4b2c4de7))
* allow forking sessions into a different directory
([#1483](https://github.com/agentclientprotocol/claude-agent-acp/issues/1483))
([8bd0bc9](8bd0bc9b03))
* detect "Run /login" login-expired messages as authRequired
([#1481](https://github.com/agentclientprotocol/claude-agent-acp/issues/1481))
([fccaebc](fccaebcf87))
* mark plugin, bundled and nested Claude skills as skills for AIR
([#1473](https://github.com/agentclientprotocol/claude-agent-acp/issues/1473))
([52c241e](52c241e715))
* title subagent sessions by their task
([#1479](https://github.com/agentclientprotocol/claude-agent-acp/issues/1479))
([7dcb629](7dcb62923d))

---
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>
2026-10-29 20:34:22 +01:00
Ben Brandt
8bd0bc9b03 fix: allow forking sessions into a different directory (#1483)
Fix `session/fork` when the requested working directory differs from the
directory containing the source session's transcript, such as a
subthread in another worktree.

The Claude Agent SDK's `dir` option restricts **source transcript
lookup**; it does not select the fork's runtime directory. Passing ACP's
destination `cwd` therefore prevents the SDK from finding a source
session stored elsewhere.

- Omit `dir` from both SDK fork calls, active-history lookup, and
full-history import so every fork-resolution path searches all projects.
- Continue starting the forked query with the requested `cwd`, MCP
servers, and other session settings.
- Add 12 real-SDK tests using isolated temporary transcripts, covering
same- and cross-directory forks through latest history, active messages,
inactive branches, fingerprints, and live message maps, plus
missing-message errors. Check copied history, fresh UUIDs, and an
unchanged source transcript.
- Extend ACP v2 coverage to verify same- and cross-directory forks
remain immediately promptable and use the requested runtime directory.

## Validation

- Before the fix, all six cross-directory regression cases failed while
the six same-directory cases passed; after the fix, all 12 pass.
- `npm run check` — passed.
- `npm run build` — passed.
- `npm run test:run -- --reporter=dot --silent` — passed: 60 files,
3,437 tests; 4 files and 30 tests skipped.
- `git diff --check` — passed.

Final validation ran on macOS with Node 24. Earlier Windows validation
passed all fork-related tests but encountered unrelated path-separator
assertions and checkout line-ending formatting failures; those are
outside this change.

No authenticated Claude prompt or end-to-end Delta subthread test was
run. The new transcript tests use the real SDK without starting Claude
Code; ACP query execution is mocked in the protocol test.
2026-10-29 19:20:46 +00:00
Mark Tkachenko
52c241e715 fix: mark plugin, bundled and nested Claude skills as skills for AIR (#1473)
AIR lists a command as a skill only when the adapter finds its
`SKILL.md`, and the resolver only probed `<cwd|~>/.claude/skills`.
Plugin skills, CLI-bundled skills and nested `.claude/skills` therefore
reached AIR as plain commands.

The adapter now takes the paths from Claude Code's own skill list,
`getContextUsage().skills.skillFrontmatter`:
- a skill with a `path` gets `kind: "skill"` and `skillPath`; one
without (bundled `simplify`, remote connections) gets `kind` only;
- a skill not in the list falls back to the existing `SKILL.md` lookup;
builtin commands like `compact` stay unmarked;
- the read runs in the background (the first call takes about 1 s) on
the first `system/init` and on every `commands_changed`, and the command
list is re-sent when the result changes.
2026-10-29 14:42:08 +01:00
Ben Brandt
fccaebcf87 fix: detect "Run /login" login-expired messages as authRequired (#1481)
The CLI now reports an expired login (no refresh token) as:

Login expired · Run /login to sign in again, or re-authenticate your
Anthropic profile

and tags the synthetic assistant message `error: "invalid_request"`, not
`authentication_failed`. The adapter only recognised the exact phrase
"Please run /login", so this message streamed to the client as agent
text and the turn failed with a generic internal error instead of
`RequestError.authRequired()`. Clients never learned auth was required,
so they couldn't offer their terminal auth methods. "Not logged in · Run
/login" had the same gap.

Changes:
- Add `isLoginRequiredText()`, which matches "run /login"
case-insensitively, and use it in `isSyntheticLoginMessage()` (live loop
and replay) and the two result-text checks.
- Exclude synthetic usage-limit banners from the login match, since some
mention `/login` to switch accounts and have their own handling.

Tests cover both new wordings, the usage-limit exclusion, and an
end-to-end turn where the `invalid_request`-tagged "Login expired"
message must reject with `authRequired` and not leak "/login" text to
the client.
2026-10-29 13:17:53 +00:00
Ben Brandt
7dcb62923d fix: title subagent sessions by their task (#1479)
A subagent's `subagent_update.title` was its type (`general-purpose`),
or
`name (type)`, so a client showing the title couldn't tell siblings
apart;
the task was only in `description`. The RFD makes the title the parent's
display label and the description the child's role or purpose.

The title is now the agent's name when it has one, else the task's short
description, else its type. The description is the type, unless the type
is already the title.
2026-10-29 11:16:05 +00:00
Kirill Alekhnovich
4b4b2c4de7 fix: add approval id to plan (#1469)
Include the approval tool call ID in each Air plan ID so the client can
bind the plan to its review. Keep the legacy plan ID for other clients.
2026-10-28 16:34:01 +01:00
acp-release-bot[bot]
106e3031e3 chore(main): release 0.99.0 (#1456)
🤖 I have created a release *beep* *boop*
---


##
[0.99.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.98.0...v0.99.0)
(2026-10-28)


### Features

* Add thinking display configuration
([#1081](https://github.com/agentclientprotocol/claude-agent-acp/issues/1081))
([2f1f4d1](2f1f4d1441))
* send embedded blob resources in prompts to Claude
([#1460](https://github.com/agentclientprotocol/claude-agent-acp/issues/1460))
([d0ee308](d0ee30813f))
* tell the client when an MCP server fails to start
([#1464](https://github.com/agentclientprotocol/claude-agent-acp/issues/1464))
([54b35ee](54b35ee0f4))
* update to Claude Agent SDK 0.3.312
([#1471](https://github.com/agentclientprotocol/claude-agent-acp/issues/1471))
([4cb8906](4cb8906d32))
* **v2:** send the current session info on session/resume
([#1462](https://github.com/agentclientprotocol/claude-agent-acp/issues/1462))
([f6ed4a4](f6ed4a4f7d))


### Bug Fixes

* deny a background permission request that the client cancels
([#1466](https://github.com/agentclientprotocol/claude-agent-acp/issues/1466))
([f646c82](f646c821f1))
* don't report decline when the client can't render an MCP elicitation
([#1465](https://github.com/agentclientprotocol/claude-agent-acp/issues/1465))
([54b869c](54b869c990))
* list the current model among the model config option's values
([#1463](https://github.com/agentclientprotocol/claude-agent-acp/issues/1463))
([8e6fe92](8e6fe92a98))
* report EnterPlanMode tool calls with the switch_mode kind
([#1467](https://github.com/agentclientprotocol/claude-agent-acp/issues/1467))
([93c1f43](93c1f43519))
* return ACP error codes for unknown sessions and invalid config values
([#1458](https://github.com/agentclientprotocol/claude-agent-acp/issues/1458))
([c2e85cb](c2e85cb580))
* update ACP SDK to 2.1.0 (schema v1.27.0 / v2.0.0-alpha.10)
([9f9aebf](9f9aebf44b))
* **v2:** keep clients in sync with config changes and replayed tool
calls
([#1461](https://github.com/agentclientprotocol/claude-agent-acp/issues/1461))
([2a0c188](2a0c188a0a))
* **v2:** reject MCP transports and prompt content the agent doesn't
advertise
([#1459](https://github.com/agentclientprotocol/claude-agent-acp/issues/1459))
([2480888](2480888b90))

---
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>
2026-10-28 15:24:45 +01:00
Ben Brandt
4cb8906d32 feat: update to Claude Agent SDK 0.3.312 (#1471)
Updates `@anthropic-ai/claude-agent-sdk` from 0.3.310 to 0.3.312, which
bundles Claude Code 2.1.312.

## Changes

- `@anthropic-ai/claude-agent-sdk` 0.3.310 → 0.3.312
- `@typescript-eslint/eslint-plugin` and `@typescript-eslint/parser`
8.72.0 → 8.72.1
- `npm update` for transitive packages (`hono`, `ignore`, `ip-address`,
`magic-string`, `postcss`, `qs`)
- TypeScript stays on 6.0.3. typescript-eslint 8.72.1 only supports
TypeScript below 6.1, so 7.0.2 has to wait.

No code changes were needed for the upgrade itself.

## Report why an AskUserQuestion question went unanswered

SDK 0.3.312 lets an AskUserQuestion answer set
`annotations[question].unanswered` to `skipped`, `replied_in_chat` or
`dismissed`. A session can receive this only if its `system/init` lists
the `ask_user_question_unanswered_v1` capability, because an older CLI
refuses the whole answer when the field is present.

- The session records the capability from `system/init`, the same way it
already records `msg_lifecycle_v1`.
- `applyAskElicitationResponse` takes a `reportUnanswered` option. If
the user declines the form, every question is marked `dismissed`. If the
user accepts it with a question left blank, that question is marked
`skipped`.
- The model does not see this field. It goes into the tool result for
clients and transcripts.
- `annotations` is now typed with `AskUserQuestionOutput`, since as of
0.3.312 only the output schema lists `unanswered`.

`replied_in_chat` is not used because ACP has no way to report that the
user replied in chat instead.

## Testing

`npm run build`, `npm run check` and `npm run test:run` pass. Two new
tests in `src/tests/elicitation.test.ts` cover a declined form and an
accepted form with a blank question.
2026-10-28 15:20:03 +01:00
Ben Brandt
d0ee30813f feat: send embedded blob resources in prompts to Claude (#1460)
Stacked on #1457 (SDK 2.1.0).

`promptToClaude` used to drop embedded `resource` blocks whose content
is a base64 `blob`, even though the agent advertises `embeddedContext`.
They are now sent to Claude:

- **Images** (PNG, JPEG, including `image/jpg`, GIF, WebP) become
`image` blocks, right after the `[@name](uri)` link.
- **PDFs** become `document` blocks.
- **Text types** (`text/*`, JSON, XML, YAML, TOML, JS/TS, shell, SQL,
GraphQL, `+json`/`+xml`) are decoded with their `charset` parameter
(UTF-8 by default, BOM dropped) and sent like text resources.
- **Other types:** v2 rejects them with `-32602`. v1 still skips them,
as before.

Known gap: a replayed user PDF does not appear in the replayed history,
because `document` blocks have no ACP mapping on replay.
2026-10-28 12:45:07 +00:00
Ben Brandt
54b35ee0f4 feat: tell the client when an MCP server fails to start (#1464)
Stacked on #1457 (SDK 2.1.0).

Agents SHOULD let the user know when a client-provided MCP server fails
(agentclientprotocol/agent-client-protocol#2512). Before this PR,
failures only showed up in `/mcp`.

- **On `system`/init:**
  - A client-provided server with status `failed` gets a warning notice.
- A `needs-auth` server gets an info notice when the OAuth flow can't
run: the client has no URL elicitation, or the query has no
`mcpAuthenticate`.
- **Once per server per session.** This holds across repeated inits and
across the session rebuilt after a sign-out.
- **Servers from the user's settings are left to `/mcp`.** Otherwise a
broken one would be reported in every session.
- **Rendering:** v1 clients without the notices capability get a
transcript line instead.
2026-10-28 13:43:34 +01:00
Ben Brandt
93c1f43519 fix: report EnterPlanMode tool calls with the switch_mode kind (#1467)
Stacked on #1457 (SDK 2.1.0).

EnterPlanMode tool calls now use `kind: "switch_mode"`, like
ExitPlanMode (agentclientprotocol/agent-client-protocol#2543). The title
is unchanged.
2026-10-28 13:40:19 +01:00
Ben Brandt
f646c821f1 fix: deny a background permission request that the client cancels (#1466)
Stacked on #1457 (SDK 2.1.0).

Per agentclientprotocol/agent-client-protocol#2508, `session/cancel`
cancels foreground work, and the client answers every pending permission
request with `cancelled`, including background ones.

- **Before:** for a background subagent, which keeps running after the
interrupt, `canUseTool` threw "Tool use aborted". The CLI turned that
into "Tool permission request failed", which the model can take for a
transient failure and retry.
- **Now:** a `cancelled` outcome for background work (decided by
`blocksForeground`, including subagents nested under a backgrounded one)
returns a `deny` with `decisionClassification: "user_reject"`.
- **Foreground** work still throws, and its turn ends through
`query.interrupt()`.

Open question: AskUserQuestion's `cancel` still throws for background
work.
2026-10-28 13:39:46 +01:00
Ben Brandt
54b869c990 fix: don't report decline when the client can't render an MCP elicitation (#1465)
Stacked on #1457 (SDK 2.1.0).

When an MCP server's elicitation is forwarded and the client answers
with an error (for example `-32602` for a form it can't render,
agentclientprotocol/agent-client-protocol#2556), the agent used to tell
the MCP server `decline`. That reported a choice the user never made.

- It now returns `cancel`.
- The same applies to the two early returns where the user never saw
anything: a mode the client didn't advertise, and a request that can't
be converted to ACP. These apply to v1 clients too.
- `decline` now only comes from an actual decline.
2026-10-28 13:39:04 +01:00
Ben Brandt
8e6fe92a98 fix: list the current model among the model config option's values (#1463)
Stacked on #1457 (SDK 2.1.0).

A select option's `currentValue` MUST be one of its `options`
(agentclientprotocol/agent-client-protocol#2557). A session on a model
outside the picker (resumed onto a model the allowlist excludes, or a
refusal fallback) used to report a `currentValue` with no matching
entry.

- The live model is now added as an option, named from its model info,
or by its raw id when there is none.
- The entry is not added to `availableModels`, so it disappears after
you switch to a listed model.
- The special case in `setSessionConfigOption` that accepted an unlisted
`currentValue` is removed.
- Allowlisted models still win alias resolution, so no model outside the
allowlist becomes selectable.
- Effort, Agent, Fast mode and ultracode already kept the invariant.
2026-10-28 13:37:54 +01:00
Ben Brandt
f6ed4a4f7d feat(v2): send the current session info on session/resume (#1462)
Stacked on #1457 (SDK 2.1.0).

On v2 `session/resume`, the agent now sends `session_info_update` with
the current title and `updatedAt`, after any replay and before the
response (agentclientprotocol/agent-client-protocol#2492).

- The agent already sent this update when the title changed. Only the
snapshot on resume was missing.
- The title comes from the same source as `session/list`. If a title has
been published but not yet stored, for example a generated title still
being written, that title is sent instead of the raw first prompt.
- A read failure is logged and never fails the resume.
- v1 is unchanged. The shared helper `publishCurrentSessionInfo` would
let v1 adopt it later.
2026-10-28 13:37:04 +01:00
Ben Brandt
2a0c188a0a fix(v2): keep clients in sync with config changes and replayed tool calls (#1461)
Stacked on #1457 (SDK 2.1.0).

- **`config_option_update` on `session/set_config_option`:** v2 now
sends the update before the response, as `session-config-options.mdx`
says Agents SHOULD. This is v2 only, because v1 tests intentionally
assert that no duplicate is sent.
- **Replayed tool calls reset their fields:** a v1 `tool_call` for an id
the client may already hold (during `session/resume` replay, or an id
already reported on this connection) now sends `null` for the fields it
leaves out: `name`, `title`, `kind`, `status`, `content`, `locations`,
`rawInput`, `rawOutput`. In v2 an omitted field keeps its old value and
`null` resets it (agentclientprotocol/agent-client-protocol#2544).
- This also fixes an earlier bug: the replay's `_meta: null` reset never
reached the wire, because `v2ToolCallPatch` dropped every `null`.
  - The only golden that changes is the replay one.
2026-10-28 13:36:21 +01:00
Ben Brandt
2480888b90 fix(v2): reject MCP transports and prompt content the agent doesn't advertise (#1459)
Stacked on #1457 (SDK 2.1.0).

The v2 spec says Agents MUST reject with `-32602` a transport or content
type they did not advertise. Before this PR, both of these were accepted
and then silently dropped:

- **`acp` MCP servers:** the agent advertises only `stdio` and `http`.
The v1 setup skipped these servers.
- **`audio` blocks:** not advertised, and `promptToClaude` ignored them.
This also applies to a v2 `systemPrompt`, since it is converted the same
way.

Embedded blob `resource` blocks are still accepted; see the blob
resources PR.
2026-10-28 13:35:01 +01:00
Ben Brandt
c2e85cb580 fix: return ACP error codes for unknown sessions and invalid config values (#1458)
Stacked on #1457 (SDK 2.1.0).

The spec now defines these error codes. They are a MUST in v2 and a
SHOULD in v1 (agentclientprotocol/agent-client-protocol#2504,
agentclientprotocol/agent-client-protocol#2497). Before this PR, plain
`Error`s reached clients as `-32603`.

- **`RequestError.sessionNotFound` (`-32002`, `data.sessionId`)** for an
unknown session in prompt, steer, sign-out respawn, `set_config_option`
and `set_mode`. Resume/load of a missing session switches from
`resourceNotFound` (same code, `data.uri`) to `sessionNotFound`; the
fallback that starts a fresh query checks the same code, so it behaves
as before.
- **`RequestError.invalidParams` (`-32602`)** for an unknown config
option, an invalid value (including fast mode and ultracode) and an
unknown or unavailable mode. A failure inside the CLI's
`setPermissionMode` stays `-32603`, since it is an internal error.
- **`session/close` of an unknown session still succeeds.**
agentclientprotocol/agent-client-protocol#2575 makes close idempotent,
and a new test pins this.

This also changes v1 responses, which now carry the codes the v1 schema
defines instead of `-32603`.
2026-10-28 13:34:31 +01:00
Ben Brandt
9f9aebf44b fix: update ACP SDK to 2.1.0 (schema v1.27.0 / v2.0.0-alpha.10)
v2 available commands now carry the required `type: "command"` tag;
SDK 2.1.0 receivers drop commands without it. Read tool locations clamp
`offset: 0` to line 1 since v2 lines are 1-based, and the session_message
casts are gone now that the SDK types senderMessageId/recipientMessageId.
2026-10-28 13:28:03 +01:00
Levin Rickert
2f1f4d1441 feat: Add thinking display configuration (#1081)
Opus 5 and Sonnet 5 are hiding thinking display by default. This PR adds
an env variable to allow users to override this default.

Fixes https://github.com/zed-industries/zed/issues/56375
2026-10-28 07:42:44 +00:00
acp-release-bot[bot]
c90f856c11 chore(main): release 0.98.0 (#1442) 2026-10-27 19:48:23 +01:00
dependabot[bot]
059d76e14a ci(deps): Bump @modelcontextprotocol/sdk from 1.32.1 to 1.32.2 in the minor group (#1449)
Bumps the minor group with 1 update:
[@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk).

Updates `@modelcontextprotocol/sdk` from 1.32.1 to 1.32.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/modelcontextprotocol/typescript-sdk/releases">@​modelcontextprotocol/sdk's
releases</a>.</em></p>
<blockquote>
<h2>1.32.2</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: add explicit | undefined to Transport optional properties (v1.x
backport of <a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/issues/1766">#1766</a>)
by <a
href="https://github.com/kishormorol"><code>@​kishormorol</code></a> in
<a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/pull/2814">modelcontextprotocol/typescript-sdk#2814</a></li>
<li>[v1.x] fix: allow registering after connect() when the capability
was declared by <a
href="https://github.com/claude"><code>@​claude</code></a>[bot] in <a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/pull/2958">modelcontextprotocol/typescript-sdk#2958</a></li>
<li>chore: bump version to 1.32.2 by <a
href="https://github.com/claude"><code>@​claude</code></a>[bot] in <a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/pull/3089">modelcontextprotocol/typescript-sdk#3089</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/kishormorol"><code>@​kishormorol</code></a>
made their first contribution in <a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/pull/2814">modelcontextprotocol/typescript-sdk#2814</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/modelcontextprotocol/typescript-sdk/compare/1.32.1...1.32.2">https://github.com/modelcontextprotocol/typescript-sdk/compare/1.32.1...1.32.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3b73021a0a"><code>3b73021</code></a>
chore: bump version to 1.32.2 (<a
href="https://redirect.github.com/modelcontextprotocol/typescript-sdk/issues/3089">#3089</a>)</li>
<li><a
href="fa56d815ac"><code>fa56d81</code></a>
[v1.x] fix: allow registering after connect() when the capability was
declare...</li>
<li><a
href="6919e9526a"><code>6919e95</code></a>
fix: add explicit | undefined to Transport optional properties (v1.x
backport...</li>
<li>See full diff in <a
href="https://github.com/modelcontextprotocol/typescript-sdk/compare/1.32.1...1.32.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@modelcontextprotocol/sdk&package-manager=npm_and_yarn&previous-version=1.32.1&new-version=1.32.2)](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>
2026-10-27 17:05:30 +01:00
Mark Tkachenko
4324dce4be fix: publish Monitor async tasks as monitors (#1452)
Since #1249 the adapter hides every async task started by the `Monitor`
tool, so Air cannot link the task to the Monitor row and counts the
session as having no background work.

Monitor tasks (and SDK `local_monitor` tasks) are published again, but
as monitors instead of shells: `taskType: "monitor"`, `showInTranscript:
false`, `canStop: false`. That keeps the two reasons from #1249: no
empty shell card, and no Stop that only breaks the agent's wait.
Background Bash, `mcp` and subagent tasks are unchanged.
2026-10-27 16:40:16 +01:00
Ben Brandt
c7c80f7b26 fix(v2): leave the tool call status out of permission subjects (#1447)
## Summary

A v2 `session/request_permission` sends its subject as the tool call
that `renderer.permissionToolCall` builds. For a client that isn't AIR,
that is the upstream shape, which includes `status: "pending"`.

The call is already emitted before the request
(`ensureToolCallEmitted`), and its status belongs to the call's own
updates. The v2 spec describes the subject as "a `toolCall` update
containing details about the operation". A client that applies it as an
update, as that shape invites, would see a call that already reported
progress go back to `pending`. `permissionToolCall` now leaves `status`
out for v2 clients; v1 clients keep the upstream shape.

## Changes

- `src/tool-calls/renderer.ts`: no `status` in a v2 client's permission
tool call.
- v2 golden files: each permission request's subject loses
`"status":"pending"`. Nothing else changes.

## Testing

- `npx vitest run`: 3379 passed, 30 skipped.
- `npm run check`: clean.

Found while adding participant approval of permission requests to Delta,
which applies a tool call subject as an update to its call.
2026-10-27 13:07:48 +00:00
Ben Brandt
8f43ac8089 fix: settle the tool call of a SandboxNetworkAccess permission request (#1448)
## Summary

`SandboxNetworkAccess` is a permission-only callback. The SDK asks under
a synthetic tool use id and never sends a `tool_result` for it.
`ensureToolCallEmitted` emits a `tool_call` for that id before asking,
so clients have the call the request refers to (#851). But nothing ever
ended that call, so clients showed a tool call stuck at `pending`
(related: #1061).

Once the request is answered, the adapter now sends a `tool_call_update`
with the call's final status: `completed` when the network access was
allowed, `failed` otherwise. Delivery follows the same routing as the
call's other updates: the native subagent deliverer when there is one,
otherwise the call's session.

A request withdrawn by a cancel, rather than answered, still leaves the
call unsettled, as before.

## Changes

- `src/acp-agent.ts`: `settleSandboxNetworkCall`, called after the
permission response is decoded.
- `src/tests/acp-scenarios/compare.ts`: documents the new update as an
allowed difference from origin/main for plain and Zed clients.
- `network-permission` golden files (v2 and AIR) gain the one settling
update.

## Testing

- `npx vitest run`: 3379 passed, 30 skipped.
- `npm run check`: clean.

Independent of #1447. The two touch the same v2 golden file, so
whichever lands second needs a snapshot refresh.
2026-10-27 13:55:40 +01:00
Slapoguzov Alexander
0ce42f2c31 feat: add batch session metadata lookup (#1408)
A session browser may load only the 50 most recent conversations, while
pinned sessions and saved folders refer to much older ones. After a
restart, the client still has those IDs but needs their current titles,
locations and archive state to rebuild the saved views. Finding a
handful of retained sessions by paging through the entire history
becomes increasingly expensive as that history grows.

Add `_session/batch_get` to retrieve metadata for known session IDs in
one request; the client chooses how many IDs to send per request.
Successful results use the same row format as `session/list`, including
archived sessions and sessions in worktrees, so the client can merge
them with its recent page and apply its usual display filters.

The response contains the sessions whose metadata is available, in
request order. Other IDs are simply omitted. The client can refresh the
rows it receives while keeping its saved references, so pinned and
folder views stay independent of the recent-list window.

---------

Co-authored-by: Aleksandr Slapoguzov <aleksandr.slapoguzov@jetbrains.com>
2026-10-27 00:44:18 +01:00
dependabot[bot]
782e463ff4 ci(deps): Bump the minor group with 3 updates (#1440)
Bumps the minor group with 3 updates:
[on-finished](https://github.com/jshttp/on-finished),
[rolldown](https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown)
and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).

Updates `on-finished` from 2.4.1 to 2.4.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jshttp/on-finished/releases">on-finished's
releases</a>.</em></p>
<blockquote>
<h2>2.4.2</h2>
<h2>Highlights</h2>
<h3>🔧 Node.js deprecation</h3>
<ul>
<li><code>isFinished(res)</code> now reads <code>writableEnded</code>
instead of the deprecated <code>finished</code> property (<a
href="https://nodejs.org/api/deprecations.html#DEP0136">DEP0136</a>),
with a fallback to <code>finished</code> on older Node.js versions. No
behavior change for consumers. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/54">#54</a>)</li>
</ul>
<h3>📦 Packaging</h3>
<ul>
<li><code>HISTORY.md</code> is no longer shipped in the npm tarball. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/109">#109</a>)</li>
<li><code>package.json</code> now declares <code>&quot;type&quot;:
&quot;commonjs&quot;</code> explicitly. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/108">#108</a>)</li>
</ul>
<h3>🧪 Testing and CI</h3>
<ul>
<li>Added edge case tests for <code>isFinished</code> with invalid
input. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/88">#88</a>)</li>
<li>Node.js 19 through 26 added to the test matrix. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/53">#53</a>,
<a
href="https://redirect.github.com/jshttp/on-finished/issues/106">#106</a>)</li>
<li><code>mocha</code> bumped to 12.0.3 and the CI pipeline refreshed.
(<a
href="https://redirect.github.com/jshttp/on-finished/issues/103">#103</a>)</li>
<li>OSSF Scorecard enabled and security best practices applied to the
workflows. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/47">#47</a>,
<a
href="https://redirect.github.com/jshttp/on-finished/issues/60">#60</a>)</li>
<li>Dependabot now groups GitHub Actions updates. (<a
href="https://redirect.github.com/jshttp/on-finished/issues/104">#104</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update node 8 version and pin nyc. Also added missing 9.x to
ci-tests… by <a
href="https://github.com/carpasse"><code>@​carpasse</code></a> in <a
href="https://redirect.github.com/jshttp/on-finished/pull/49">jshttp/on-finished#49</a></li>
<li>Add OSSF scorecard by <a
href="https://github.com/carpasse"><code>@​carpasse</code></a> in <a
href="https://redirect.github.com/jshttp/on-finished/pull/47">jshttp/on-finished#47</a></li>
<li>build: Node.js 19+ by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/53">jshttp/on-finished#53</a></li>
<li>ci: apply OSSF Scorecard security best practices by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/60">jshttp/on-finished#60</a></li>
<li>chore: add funding to package.json by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/76">jshttp/on-finished#76</a></li>
<li>use writableEnd instead finished by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/54">jshttp/on-finished#54</a></li>
<li>build(deps): bump ossf/scorecard-action from 2.0.6 to 2.4.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/85">jshttp/on-finished#85</a></li>
<li>build(deps): bump actions/upload-artifact from 3.1.3 to 4.6.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/64">jshttp/on-finished#64</a></li>
<li>build(deps): bump actions/checkout from 3.6.0 to 5.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/84">jshttp/on-finished#84</a></li>
<li>build(deps): bump actions/download-artifact from 4.3.0 to 5.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/82">jshttp/on-finished#82</a></li>
<li>test: add edge case tests for isFinished with invalid input by <a
href="https://github.com/Ayoub-Mabrouk"><code>@​Ayoub-Mabrouk</code></a>
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/88">jshttp/on-finished#88</a></li>
<li>build(deps): bump actions/checkout from 5.0.0 to 6.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/93">jshttp/on-finished#93</a></li>
<li>build(deps): bump actions/upload-artifact from 4.6.2 to 5.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/92">jshttp/on-finished#92</a></li>
<li>build(deps): bump actions/download-artifact from 5.0.0 to 6.0.0 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/91">jshttp/on-finished#91</a></li>
<li>build(deps): bump github/codeql-action from 2.23.2 to 4.31.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/90">jshttp/on-finished#90</a></li>
<li>build(deps): bump coverallsapp/github-action from 2.3.6 to 2.3.7 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/89">jshttp/on-finished#89</a></li>
<li>chore: group github actions updates by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/104">jshttp/on-finished#104</a></li>
<li>bump Mocha to version 12.0.3 by <a
href="https://github.com/rafapaezbas"><code>@​rafapaezbas</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/103">jshttp/on-finished#103</a></li>
<li>ci: add Node 25 and 26 to workflow by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/106">jshttp/on-finished#106</a></li>
<li>build(deps): bump the github-actions group with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/105">jshttp/on-finished#105</a></li>
<li>chore: add explicit type commonjs to package.json by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/108">jshttp/on-finished#108</a></li>
<li>chore: remove HISTORY.md from being packaged on publish by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/109">jshttp/on-finished#109</a></li>
<li>chore(ci): npm-publish via stage publish by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/jshttp/on-finished/pull/111">jshttp/on-finished#111</a></li>
<li>Release: 2.4.2 by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/jshttp/on-finished/pull/110">jshttp/on-finished#110</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/carpasse"><code>@​carpasse</code></a>
made their first contribution in <a
href="https://redirect.github.com/jshttp/on-finished/pull/49">jshttp/on-finished#49</a></li>
<li><a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
made their first contribution in <a
href="https://redirect.github.com/jshttp/on-finished/pull/60">jshttp/on-finished#60</a></li>
<li><a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
made their first contribution in <a
href="https://redirect.github.com/jshttp/on-finished/pull/85">jshttp/on-finished#85</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jshttp/on-finished/blob/master/HISTORY.md">on-finished's
changelog</a>.</em></p>
<blockquote>
<h1>2.4.2</h1>
<ul>
<li>Use <code>writableEnded</code> instead of deprecated
<code>finished</code>, with a fallback for older Node.js versions (<a
href="https://redirect.github.com/jshttp/on-finished/issues/54">#54</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9a73f9c1c0"><code>9a73f9c</code></a>
2.4.2 (<a
href="https://redirect.github.com/jshttp/on-finished/issues/110">#110</a>)</li>
<li><a
href="0ec28ddc1d"><code>0ec28dd</code></a>
chore(ci): npm-publish via stage publish (<a
href="https://redirect.github.com/jshttp/on-finished/issues/111">#111</a>)</li>
<li><a
href="4b8ac4669c"><code>4b8ac46</code></a>
chore: remove HISTORY.md from being packaged on publish (<a
href="https://redirect.github.com/jshttp/on-finished/issues/109">#109</a>)</li>
<li><a
href="9c5f7b805b"><code>9c5f7b8</code></a>
chore: add explicit type commonjs to package.json (<a
href="https://redirect.github.com/jshttp/on-finished/issues/108">#108</a>)</li>
<li><a
href="8c33090d5b"><code>8c33090</code></a>
build(deps): bump the github-actions group with 10 updates (<a
href="https://redirect.github.com/jshttp/on-finished/issues/105">#105</a>)</li>
<li><a
href="1ca32c56e7"><code>1ca32c5</code></a>
ci: add Node 25 and 26 to workflow (<a
href="https://redirect.github.com/jshttp/on-finished/issues/106">#106</a>)</li>
<li><a
href="4ea365ed85"><code>4ea365e</code></a>
deps: bump mocha to v12.0.3 and update CI pipeline (<a
href="https://redirect.github.com/jshttp/on-finished/issues/103">#103</a>)</li>
<li><a
href="673ff9fbb2"><code>673ff9f</code></a>
chore: group github actions updates (<a
href="https://redirect.github.com/jshttp/on-finished/issues/104">#104</a>)</li>
<li><a
href="500c84cec0"><code>500c84c</code></a>
build(deps): bump coverallsapp/github-action from 2.3.6 to 2.3.7 (<a
href="https://redirect.github.com/jshttp/on-finished/issues/89">#89</a>)</li>
<li><a
href="1dbd9cd615"><code>1dbd9cd</code></a>
build(deps): bump github/codeql-action from 2.23.2 to 4.31.6 (<a
href="https://redirect.github.com/jshttp/on-finished/issues/90">#90</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/jshttp/on-finished/compare/v2.4.1...v2.4.2">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 on-finished since your current version.</p>
</details>
<br />

Updates `rolldown` from 1.2.14 to 1.2.15
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">rolldown's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.15</h2>
<h2>[1.2.15] - 2026-10-21</h2>
<h3>🚀 Features</h3>
<ul>
<li>rolldown_plugin_vite_resolve: add <code>isBundled</code> to pick the
browser-external stub format (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11260">#11260</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>dev: mark entry modules in the HMR module graph (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11252">#11252</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>mark module starts with <code>// &lt;id&gt;</code> comments instead
of <code>#region</code> pairs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11235">#11235</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>plugin: stop <code>this.resolve</code> recursion between plugins
with <code>skipSelf: true</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11181">#11181</a>)
by <a href="https://github.com/anzal1"><code>@​anzal1</code></a></li>
<li>rolldown_plugin_vite_resolve: handle external <code>require</code>
for <code>ssr.target: 'webworker'</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11205">#11205</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>rolldown_plugin_vite_build_import_analysis: only wrap dynamic
imports with static specifiers in __vitePreload (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10879">#10879</a>)
by <a href="https://github.com/smeng9"><code>@​smeng9</code></a></li>
<li>strict-execution-order: wrap eager modules that forward wrapped
definers (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11186">#11186</a>)
by <a href="https://github.com/hyfdev"><code>@​hyfdev</code></a></li>
<li>chunk-import-map: keep common chunk keys unique when enabling inline
common chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11230">#11230</a>)
by <a href="https://github.com/hyfdev"><code>@​hyfdev</code></a></li>
<li>only register signal-exit handler when a trace subscriber is active
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11208">#11208</a>)
by <a
href="https://github.com/DaniilIsupov"><code>@​DaniilIsupov</code></a></li>
</ul>
<h3>🧪 Testing</h3>
<ul>
<li>fold hand-unrolled Rust test tables (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11212">#11212</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
</ul>
<h3>⚙️ Miscellaneous Tasks</h3>
<ul>
<li>deps: upgrade oxc to 0.155.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11228">#11228</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>rollup-tests: retriage failed tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11251">#11251</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>deps: update rollup submodule for tests to v4.64.12 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11249">#11249</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
<li>deps: update test262 submodule for tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11242">#11242</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
</ul>
<h3>❤️ New Contributors</h3>
<ul>
<li><a href="https://github.com/anzal1"><code>@​anzal1</code></a> made
their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/11181">#11181</a></li>
<li><a href="https://github.com/smeng9"><code>@​smeng9</code></a> made
their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/10879">#10879</a></li>
<li><a
href="https://github.com/DaniilIsupov"><code>@​DaniilIsupov</code></a>
made their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/11208">#11208</a></li>
</ul>
<p>Co-authored-by: shulaoda <a
href="mailto:165626830+shulaoda@users.noreply.github.com">165626830+shulaoda@users.noreply.github.com</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/blob/main/CHANGELOG.md">rolldown's
changelog</a>.</em></p>
<blockquote>
<h2>[1.2.15] - 2026-10-21</h2>
<h3>🚀 Features</h3>
<ul>
<li>rolldown_plugin_vite_resolve: add <code>isBundled</code> to pick the
browser-external stub format (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11260">#11260</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>dev: mark entry modules in the HMR module graph (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11252">#11252</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>mark module starts with <code>// &lt;id&gt;</code> comments instead
of <code>#region</code> pairs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11235">#11235</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>plugin: stop <code>this.resolve</code> recursion between plugins
with <code>skipSelf: true</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11181">#11181</a>)
by <a href="https://github.com/anzal1"><code>@​anzal1</code></a></li>
<li>rolldown_plugin_vite_resolve: handle external <code>require</code>
for <code>ssr.target: 'webworker'</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11205">#11205</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>rolldown_plugin_vite_build_import_analysis: only wrap dynamic
imports with static specifiers in __vitePreload (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10879">#10879</a>)
by <a href="https://github.com/smeng9"><code>@​smeng9</code></a></li>
<li>strict-execution-order: wrap eager modules that forward wrapped
definers (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11186">#11186</a>)
by <a href="https://github.com/hyfdev"><code>@​hyfdev</code></a></li>
<li>chunk-import-map: keep common chunk keys unique when enabling inline
common chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11230">#11230</a>)
by <a href="https://github.com/hyfdev"><code>@​hyfdev</code></a></li>
<li>only register signal-exit handler when a trace subscriber is active
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11208">#11208</a>)
by <a
href="https://github.com/DaniilIsupov"><code>@​DaniilIsupov</code></a></li>
</ul>
<h3>🧪 Testing</h3>
<ul>
<li>fold hand-unrolled Rust test tables (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11212">#11212</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
</ul>
<h3>⚙️ Miscellaneous Tasks</h3>
<ul>
<li>deps: upgrade oxc to 0.155.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11228">#11228</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>rollup-tests: retriage failed tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11251">#11251</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>deps: update rollup submodule for tests to v4.64.12 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11249">#11249</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
<li>deps: update test262 submodule for tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11242">#11242</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
</ul>
<h3>❤️ New Contributors</h3>
<ul>
<li><a href="https://github.com/anzal1"><code>@​anzal1</code></a> made
their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/11181">#11181</a></li>
<li><a href="https://github.com/smeng9"><code>@​smeng9</code></a> made
their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/10879">#10879</a></li>
<li><a
href="https://github.com/DaniilIsupov"><code>@​DaniilIsupov</code></a>
made their first contribution in <a
href="https://redirect.github.com/rolldown/rolldown/pull/11208">#11208</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="89d821ca07"><code>89d821c</code></a>
release: v1.2.15 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11261">#11261</a>)</li>
<li><a
href="3b0841ef80"><code>3b0841e</code></a>
feat(rolldown_plugin_vite_resolve): add <code>isBundled</code> to pick
the browser-exter...</li>
<li><a
href="a973d41247"><code>a973d41</code></a>
chore(deps): upgrade oxc to 0.155.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11228">#11228</a>)</li>
<li><a
href="5bfb74e2b0"><code>5bfb74e</code></a>
feat(dev): mark entry modules in the HMR module graph (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11252">#11252</a>)</li>
<li><a
href="fb0a8db6f1"><code>fb0a8db</code></a>
fix(rolldown_plugin_vite_resolve): handle external <code>require</code>
for `ssr.target:...</li>
<li><a
href="547a5a733c"><code>547a5a7</code></a>
fix(rolldown_plugin_vite_build_import_analysis): only wrap dynamic
imports wi...</li>
<li><a
href="648591f0eb"><code>648591f</code></a>
feat: mark module starts with <code>// \&lt;id&gt;</code> comments
instead of <code>#region</code> pairs (...</li>
<li><a
href="3f0374181d"><code>3f03741</code></a>
fix(chunk-import-map): keep common chunk keys unique when enabling
inline com...</li>
<li><a
href="33a987d8bf"><code>33a987d</code></a>
fix: only register signal-exit handler when a trace subscriber is active
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/11">#11</a>...</li>
<li>See full diff in <a
href="https://github.com/rolldown/rolldown/commits/v1.2.15/packages/rolldown">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 8.3.4 to 8.3.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v8.3.5</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> handle missing chunk import maps (<a
href="https://redirect.github.com/vitejs/vite/issues/23777">#23777</a>)
(<a
href="8d7623d9b5">8d7623d</a>)</li>
<li><strong>build:</strong> include preload dependencies to chunk import
map hashing (<a
href="https://redirect.github.com/vitejs/vite/issues/23793">#23793</a>)
(<a
href="4d16e7c14d">4d16e7c</a>)</li>
<li><strong>bundled-dev:</strong> apply <code>server.origin</code> to
asset URLs (<a
href="https://redirect.github.com/vitejs/vite/issues/23683">#23683</a>)
(<a
href="52ba877693">52ba877</a>)</li>
<li><strong>bundled-dev:</strong> declare utf-8 charset in fallback page
(<a
href="https://redirect.github.com/vitejs/vite/issues/23846">#23846</a>)
(<a
href="197daf6c4c">197daf6</a>)</li>
<li><strong>bundled-dev:</strong> ignore build.chunkImportMap (<a
href="https://redirect.github.com/vitejs/vite/issues/23685">#23685</a>)
(<a
href="cfe917d67d">cfe917d</a>)</li>
<li><strong>bundled-dev:</strong> include user
<code>import.meta.env.*</code> defines in the
<code>import.meta.env</code> object (<a
href="https://redirect.github.com/vitejs/vite/issues/23713">#23713</a>)
(<a
href="6019aa7997">6019aa7</a>)</li>
<li><strong>bundled-dev:</strong> map forwarded error stacks through the
bundle source maps (<a
href="https://redirect.github.com/vitejs/vite/issues/23741">#23741</a>)
(<a
href="11fe1e46ff">11fe1e4</a>)</li>
<li><strong>bundled-dev:</strong> normalize served output files (<a
href="https://redirect.github.com/vitejs/vite/issues/23684">#23684</a>)
(<a
href="df4e02cc83">df4e02c</a>)</li>
<li><strong>bundled-dev:</strong> stop hmr propagation at modules that
are no longer imported (<a
href="https://redirect.github.com/vitejs/vite/issues/23839">#23839</a>)
(<a
href="7471e8c3b3">7471e8c</a>)</li>
<li><strong>css:</strong> don't fail on a literal <code>%</code> in
<code>url()</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23676">#23676</a>)
(<a
href="50b382d318">50b382d</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23731">#23731</a>)
(<a
href="cf80aa552c">cf80aa5</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23826">#23826</a>)
(<a
href="77d8373510">77d8373</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23733">#23733</a>)
(<a
href="43f0d052e5">43f0d05</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23827">#23827</a>)
(<a
href="c2e04187e4">c2e0418</a>)</li>
<li><strong>optimizer:</strong> fall back to yarn.lock for lockfile hash
(<a
href="https://redirect.github.com/vitejs/vite/issues/23667">#23667</a>)
(<a
href="f20609630f">f206096</a>)</li>
<li><strong>optimizer:</strong> scan <code>oxc.jsxInject</code> imports
(<a
href="https://redirect.github.com/vitejs/vite/issues/23758">#23758</a>)
(<a
href="38f3cb45b1">38f3cb4</a>)</li>
<li><strong>optimizer:</strong> skip patches dir check for yarn.lock
fallback (<a
href="https://redirect.github.com/vitejs/vite/issues/23725">#23725</a>)
(<a
href="8e890e1ce4">8e890e1</a>)</li>
<li><strong>optimizer:</strong> skip patches dir hash for nub lockfile
(<a
href="https://redirect.github.com/vitejs/vite/issues/23761">#23761</a>)
(<a
href="a7d56967ef">a7d5696</a>)</li>
<li><strong>resolve:</strong> support subpath imports with multiple
<code>#</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23631">#23631</a>)
(<a
href="0d86b3485f">0d86b34</a>)</li>
<li><strong>resolve:</strong> update resolve.exports to 2.0.4 (<a
href="https://redirect.github.com/vitejs/vite/issues/23739">#23739</a>)
(<a
href="4ea708a3ae">4ea708a</a>)</li>
<li><strong>server:</strong> handle socket errors on upgrade requests
not taken by the HMR server (<a
href="https://redirect.github.com/vitejs/vite/issues/23728">#23728</a>)
(<a
href="c2c9ef4dcd">c2c9ef4</a>)</li>
<li><strong>server:</strong> only strip base when path matches a full
segment (<a
href="https://redirect.github.com/vitejs/vite/issues/23641">#23641</a>)
(<a
href="3dfde87b90">3dfde87</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<ul>
<li><strong>ssr:</strong> extract inline source maps without a regexp
(<a
href="https://redirect.github.com/vitejs/vite/issues/23775">#23775</a>)
(<a
href="6a0278055d">6a02780</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li>migrate to Oxlint (<a
href="https://redirect.github.com/vitejs/vite/issues/23746">#23746</a>)
(<a
href="5b73935881">5b73935</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li><strong>deps:</strong> replace <code>@vercel/detect-agent</code>
with <code>std-env</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23744">#23744</a>)
(<a
href="ce4293f15c">ce4293f</a>)</li>
<li><strong>dev:</strong> consolidate moduleURL to file path utilities
(<a
href="https://redirect.github.com/vitejs/vite/issues/23828">#23828</a>)
(<a
href="df7b40ee9d">df7b40e</a>)</li>
<li><strong>dev:</strong> separate import resolution from URL conversion
(<a
href="https://redirect.github.com/vitejs/vite/issues/23791">#23791</a>)
(<a
href="611668983d">6116689</a>)</li>
<li><strong>dev:</strong> share ModuleURL and BrowserURL conversions (<a
href="https://redirect.github.com/vitejs/vite/issues/23790">#23790</a>)
(<a
href="446954c69c">446954c</a>)</li>
<li><strong>dev:</strong> use ModuleURLs throughout HTML transforms (<a
href="https://redirect.github.com/vitejs/vite/issues/23815">#23815</a>)
(<a
href="a16299092c">a162990</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v8.3.4...v8.3.5">8.3.5</a>
(2026-10-21)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>build:</strong> handle missing chunk import maps (<a
href="https://redirect.github.com/vitejs/vite/issues/23777">#23777</a>)
(<a
href="8d7623d9b5">8d7623d</a>)</li>
<li><strong>build:</strong> include preload dependencies to chunk import
map hashing (<a
href="https://redirect.github.com/vitejs/vite/issues/23793">#23793</a>)
(<a
href="4d16e7c14d">4d16e7c</a>)</li>
<li><strong>bundled-dev:</strong> apply <code>server.origin</code> to
asset URLs (<a
href="https://redirect.github.com/vitejs/vite/issues/23683">#23683</a>)
(<a
href="52ba877693">52ba877</a>)</li>
<li><strong>bundled-dev:</strong> declare utf-8 charset in fallback page
(<a
href="https://redirect.github.com/vitejs/vite/issues/23846">#23846</a>)
(<a
href="197daf6c4c">197daf6</a>)</li>
<li><strong>bundled-dev:</strong> ignore build.chunkImportMap (<a
href="https://redirect.github.com/vitejs/vite/issues/23685">#23685</a>)
(<a
href="cfe917d67d">cfe917d</a>)</li>
<li><strong>bundled-dev:</strong> include user
<code>import.meta.env.*</code> defines in the
<code>import.meta.env</code> object (<a
href="https://redirect.github.com/vitejs/vite/issues/23713">#23713</a>)
(<a
href="6019aa7997">6019aa7</a>)</li>
<li><strong>bundled-dev:</strong> map forwarded error stacks through the
bundle source maps (<a
href="https://redirect.github.com/vitejs/vite/issues/23741">#23741</a>)
(<a
href="11fe1e46ff">11fe1e4</a>)</li>
<li><strong>bundled-dev:</strong> normalize served output files (<a
href="https://redirect.github.com/vitejs/vite/issues/23684">#23684</a>)
(<a
href="df4e02cc83">df4e02c</a>)</li>
<li><strong>bundled-dev:</strong> stop hmr propagation at modules that
are no longer imported (<a
href="https://redirect.github.com/vitejs/vite/issues/23839">#23839</a>)
(<a
href="7471e8c3b3">7471e8c</a>)</li>
<li><strong>css:</strong> don't fail on a literal <code>%</code> in
<code>url()</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23676">#23676</a>)
(<a
href="50b382d318">50b382d</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23731">#23731</a>)
(<a
href="cf80aa552c">cf80aa5</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23826">#23826</a>)
(<a
href="77d8373510">77d8373</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23733">#23733</a>)
(<a
href="43f0d052e5">43f0d05</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/23827">#23827</a>)
(<a
href="c2e04187e4">c2e0418</a>)</li>
<li><strong>optimizer:</strong> fall back to yarn.lock for lockfile hash
(<a
href="https://redirect.github.com/vitejs/vite/issues/23667">#23667</a>)
(<a
href="f20609630f">f206096</a>)</li>
<li><strong>optimizer:</strong> scan <code>oxc.jsxInject</code> imports
(<a
href="https://redirect.github.com/vitejs/vite/issues/23758">#23758</a>)
(<a
href="38f3cb45b1">38f3cb4</a>)</li>
<li><strong>optimizer:</strong> skip patches dir check for yarn.lock
fallback (<a
href="https://redirect.github.com/vitejs/vite/issues/23725">#23725</a>)
(<a
href="8e890e1ce4">8e890e1</a>)</li>
<li><strong>optimizer:</strong> skip patches dir hash for nub lockfile
(<a
href="https://redirect.github.com/vitejs/vite/issues/23761">#23761</a>)
(<a
href="a7d56967ef">a7d5696</a>)</li>
<li><strong>resolve:</strong> support subpath imports with multiple
<code>#</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23631">#23631</a>)
(<a
href="0d86b3485f">0d86b34</a>)</li>
<li><strong>resolve:</strong> update resolve.exports to 2.0.4 (<a
href="https://redirect.github.com/vitejs/vite/issues/23739">#23739</a>)
(<a
href="4ea708a3ae">4ea708a</a>)</li>
<li><strong>server:</strong> handle socket errors on upgrade requests
not taken by the HMR server (<a
href="https://redirect.github.com/vitejs/vite/issues/23728">#23728</a>)
(<a
href="c2c9ef4dcd">c2c9ef4</a>)</li>
<li><strong>server:</strong> only strip base when path matches a full
segment (<a
href="https://redirect.github.com/vitejs/vite/issues/23641">#23641</a>)
(<a
href="3dfde87b90">3dfde87</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<ul>
<li><strong>ssr:</strong> extract inline source maps without a regexp
(<a
href="https://redirect.github.com/vitejs/vite/issues/23775">#23775</a>)
(<a
href="6a0278055d">6a02780</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li>migrate to Oxlint (<a
href="https://redirect.github.com/vitejs/vite/issues/23746">#23746</a>)
(<a
href="5b73935881">5b73935</a>)</li>
</ul>
<h3>Code Refactoring</h3>
<ul>
<li><strong>deps:</strong> replace <code>@vercel/detect-agent</code>
with <code>std-env</code> (<a
href="https://redirect.github.com/vitejs/vite/issues/23744">#23744</a>)
(<a
href="ce4293f15c">ce4293f</a>)</li>
<li><strong>dev:</strong> consolidate moduleURL to file path utilities
(<a
href="https://redirect.github.com/vitejs/vite/issues/23828">#23828</a>)
(<a
href="df7b40ee9d">df7b40e</a>)</li>
<li><strong>dev:</strong> separate import resolution from URL conversion
(<a
href="https://redirect.github.com/vitejs/vite/issues/23791">#23791</a>)
(<a
href="611668983d">6116689</a>)</li>
<li><strong>dev:</strong> share ModuleURL and BrowserURL conversions (<a
href="https://redirect.github.com/vitejs/vite/issues/23790">#23790</a>)
(<a
href="446954c69c">446954c</a>)</li>
<li><strong>dev:</strong> use ModuleURLs throughout HTML transforms (<a
href="https://redirect.github.com/vitejs/vite/issues/23815">#23815</a>)
(<a
href="a16299092c">a162990</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="981f07fa45"><code>981f07f</code></a>
release: v8.3.5 (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23856">#23856</a>)</li>
<li><a
href="0d86b3485f"><code>0d86b34</code></a>
fix(resolve): support subpath imports with multiple <code>#</code> (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23631">#23631</a>)</li>
<li><a
href="6a0278055d"><code>6a02780</code></a>
perf(ssr): extract inline source maps without a regexp (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23775">#23775</a>)</li>
<li><a
href="8e890e1ce4"><code>8e890e1</code></a>
fix(optimizer): skip patches dir check for yarn.lock fallback (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23725">#23725</a>)</li>
<li><a
href="197daf6c4c"><code>197daf6</code></a>
fix(bundled-dev): declare utf-8 charset in fallback page (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23846">#23846</a>)</li>
<li><a
href="4d16e7c14d"><code>4d16e7c</code></a>
fix(build): include preload dependencies to chunk import map hashing (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23793">#23793</a>)</li>
<li><a
href="df7b40ee9d"><code>df7b40e</code></a>
refactor(dev): consolidate moduleURL to file path utilities (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23828">#23828</a>)</li>
<li><a
href="a16299092c"><code>a162990</code></a>
refactor(dev): use ModuleURLs throughout HTML transforms (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23815">#23815</a>)</li>
<li><a
href="611668983d"><code>6116689</code></a>
refactor(dev): separate import resolution from URL conversion (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23791">#23791</a>)</li>
<li><a
href="446954c69c"><code>446954c</code></a>
refactor(dev): share ModuleURL and BrowserURL conversions (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/23790">#23790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v8.3.5/packages/vite">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>
2026-10-26 16:18:48 +01:00
Ben Brandt
c9374abfc7 test: fix integration-run failures in provider, model and session-index tests (#1439)
## Summary

Fixes the 23 failures seen with `RUN_INTEGRATION_TESTS=true`. All are
test-side; no adapter changes.

- **Provider tests (20 failures):** since #1340, `providers/set` lists
the gateway's models with `fetch` in the test process.
`vitest.config.ts` only loaded `setup-no-network.ts` outside integration
runs, so tests that point at `https://gateway.example.com` made real
requests and failed with `fetch failed`. The stub now loads in every
run. `gateway-models.ts` is the only in-process `fetch` caller, and no
integration test targets a real gateway.
- **"the real SDK exposes one plain Opus row":** the CLI catalogue now
also lists pinned older models (`claude-opus-5`, `claude-opus-4-8`, …),
and the alias row's display name is "Opus 5.5". The check now covers
only alias rows (`value` starting with `opus`) and still asserts there
is a single row with no 1M variant.
- **session-index `ENOTEMPTY` in teardown:** integration runs leave
`CLAUDE_CODE_EXECUTABLE` unset, so initialize ran the real `claude auth
status` in the background. It wrote into the temp `CLAUDE_CONFIG_DIR`
while `afterEach` removed it. The file now stubs the executable to
`/usr/bin/false`, as the scenario tests do.
- **session-index relocation test:** the wait ended on any removal from
the old path's subscription. That removal can name the other path's
session, so the test now waits for the relocated session's own id.

## Testing

- The six previously failing files pass with
`RUN_INTEGRATION_TESTS=true` (454 tests).
- `session-index.test.ts` passed 3× with integration on and 1× without
(173 tests). Those failures were intermittent, so a few clean runs don't
fully prove the fix.
- `npx vitest run` (3360 tests) and `npm run check` pass.
- The full integration suite was not rerun end to end.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:15:14 +01:00
acp-release-bot[bot]
d52c7c64d8 chore(main): release 0.97.0 (#1437)
🤖 I have created a release *beep* *boop*
---


##
[0.97.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.96.0...v0.97.0)
(2026-10-26)


### Features

* add ultracode session config option
([#1438](https://github.com/agentclientprotocol/claude-agent-acp/issues/1438))
([de96cef](de96cef382))


### Bug Fixes

* bump body-parser from 2.3.0 to 2.4.0
([#1436](https://github.com/agentclientprotocol/claude-agent-acp/issues/1436))
([c26e640](c26e640915))
* distinguish model options with duplicate labels
([#1286](https://github.com/agentclientprotocol/claude-agent-acp/issues/1286))
([434be13](434be13a33))
* keep the usage-limit error when it arrives after compaction
([#1377](https://github.com/agentclientprotocol/claude-agent-acp/issues/1377))
([2a1525e](2a1525e57c))
* pass --settings before the auth status subcommand
([#1392](https://github.com/agentclientprotocol/claude-agent-acp/issues/1392))
([6a33ded](6a33dedcbb))
* preserve foreground fork reports until launch completes
([#1431](https://github.com/agentclientprotocol/claude-agent-acp/issues/1431))
([e369479](e369479f93))
* settle advisor tool results before completing turns
([#1247](https://github.com/agentclientprotocol/claude-agent-acp/issues/1247))
([bed795d](bed795d261))
* settle live steers folded into autonomous cycles
([#1185](https://github.com/agentclientprotocol/claude-agent-acp/issues/1185))
([a4957d1](a4957d17c1))

---
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>
2026-10-26 13:46:13 +01:00
Ben Brandt
de96cef382 feat: add ultracode session config option (#1438)
Adds an **Ultracode** session config option. It turns on Claude Code's
dynamic workflows for every task, at any effort level. Clients that
support boolean config options get a native switch; the rest get an
On/Off select. This follows the Fast mode pattern.

## How the CLI handles ultracode (2.1.310, checked against the bundled
binary and a live session)

- **Which models:** ultracode works on exactly the models that support
`xhigh` effort. The CLI uses the same check for both. So Opus 4.7+,
Sonnet 5+, Haiku 5.5 and Fable qualify; Opus 4.6 and Haiku 4.5 don't.
Effort level no longer matters.
- **Workflows:** dynamic workflows must be enabled. They are off by
default on Pro plans, and org or managed policy can turn them off.
- **State reporting:** `query.getSettings()` returns
`applied.ultracodeAvailable` and `applied.ultracodeRequested`. The CLI's
schema says hosts should "offer an Ultracode control only while this is
true". This method exists at runtime but is missing from the SDK's
`.d.ts`, so the adapter reads it with a cast and falls back to settings
and model data when it is absent.
- **Unsupported requests:** `applyFlagSettings({ultracode})` is never
validated. The request is stored and stays in place across model
switches; it takes effect whenever the model supports it.
- **Effort changes:** an `effortLevel` change sent without an
`ultracode` key turns ultracode off.

## Behavior

- **Visibility:** at session start, one `getSettings()` call decides
availability and the starting value. Model switches combine the
workflows result learned at start with the new model's
`supportedEffortLevels`, so the switch path makes no extra control
request.
- **Value:** the session tracks the user's choice, like
`fastModeEnabled`. The option hides on unsupported models and comes back
with the same value.
- **Effort applies:** `effortFlagSettings` now carries the session's
toggle instead of `settings.ultracode`. This applies at the initial
apply, on model switch, and from the effort picker, so effort changes no
longer turn ultracode off.
- **Clear context:** the clear-context restart restores the toggle, as
it does Fast mode.

## Known limitation

If a user types `/effort ultracode on` as a prompt, the toggle does not
update. The adapter already doesn't pick up effort changes typed as
`/effort` commands.

## Testing

- New `src/tests/ultracode-config.test.ts`. It covers the helpers and
session behavior: seeding from the CLI's report, hiding while workflows
are off, following the model's support, keeping the value across effort
and model switches, and rejected applies.
- New clear-context restore test. `effortFlagSettings` tests updated.
- Live probe against CLI 2.1.310 confirmed the `getSettings` fields, the
`xhigh` proxy (`claude-opus-4-6` reports unavailable while the request
stays in place), and the effort/key behavior.
- `npm run check`, `npm run build`, `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>
2026-10-26 12:34:13 +00:00
Sean McCullough
a4957d17c1 fix: settle live steers folded into autonomous cycles (#1185)
#1233 fixed queued prompts folded into task-notification cycles. One
case remains: a steer sent to the active prompt can be folded into an
autonomous cycle, leaving `session/prompt` pending when the result names
only the steer UUID.

Keep queued prompt matching and also match the active turn's steer UUID.
Exclude settled turns and turns held open for background tasks. Preserve
plural UUID precedence.

The six new settlement cases fail on main and pass with the fix; all 11
new tests pass. Build, lint and format checks pass. The full default
suite reports 2,497 passed and 30 skipped with an identical Node binary
at a separate path. At `/usr/bin/node`, it reports 2,496 passed, one
failed and 30 skipped: an unchanged path-normalization test also fails
on main. No test was weakened. No live-provider tests were run.

Co-authored-by: Sean McCullough <sean@mcculloughs.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 11:35:31 +01:00
Nacho Lopez
bed795d261 fix: settle advisor tool results before completing turns (#1247)
When Claude uses the advisor server tool, a completed turn can fail with
`ACP error -32603: Internal error: Claude ended the turn without
returning results for tool calls: srvtoolu_...`. `server_tool_use` is
tracked as a foreground call, but the matching `advisor_tool_result` is
discarded, leaving that call unfinished at settlement.

Route advisor results through the existing tool-result handler so they
clear the outstanding call and publish a terminal update. Plaintext
advice is displayed, redacted advice gets a readable completion message
without exposing ciphertext, and advisor error results publish a failed
tool status with the error code. Release the advisor's hook callback
immediately because the server tool has no later client-side
`PostToolUse` hook.

Addresses the result-lifecycle part of #914.

Validation:

- Regression coverage through the real prompt consumer for plaintext,
redacted, and error results, including the complete assistant message
that dispatches the advisor call. All three cases fail without the fix.
A dispatched advisor call without a result still fails with
`incomplete_tool_call`.
- `npm run test:run -- src/tests/incomplete-tools.test.ts`: 59 passed.
- `npm run test:run`: 3,055 passed, 30 skipped.
- `npm run build` and `npm run check`: passed.
- Exact-head Linux CI passed in the fork:
https://github.com/mrmans0n/claude-agent-acp/actions/runs/38595756003.

The upstream PR workflows are waiting for maintainer approval to run.
The required upstream `Build` check remains pending; the fork CI result
does not satisfy that merge gate.
2026-10-26 11:28:57 +01:00
Jasper
434be13a33 fix: distinguish model options with duplicate labels (#1286)
Fixes #1246.

When `availableModels` includes aliases and pinned IDs that inherit the
same SDK display name, name-only model pickers show indistinguishable
rows. Append the selectable model ID to names that remain duplicated
after the existing version-label normalization. For example, the four
`Opus 5.5` rows become `Opus 5.5 (opus)`, `Opus 5.5 (opus[1m])`, and
their explicit-ID equivalents.

Keep every option and its ID, description, selection, and recommendation
behavior. Existing distinct labels stay unchanged. Regression tests
exercise the real allowlist transformation and cover duplicate custom
labels too.

Validation:

- Linux, locked dependencies and the repository's `lts/*` Node
configuration: `npm run check`, `npm run build`, and the full test suite
passed at ba37dd8: **2,491 passed, 30 skipped** ([fix
job](https://github.com/jipeng6036-del/claude-agent-acp/actions/runs/37923655565/job/113797353655)).
- Exact baseline 908c994: **2,489 passed, 30 skipped**; both new
regression tests fail against the original implementation. All existing
test outcomes match ([baseline
verification](https://github.com/jipeng6036-del/claude-agent-acp/actions/runs/37923859076)).
- Windows/Node 24: build and 105 focused tests passed. The full suite
has the same 48 failures on baseline and fix; only the two new tests add
passes. Authenticated SDK and JetBrains UI integration were not run.

Signed-off-by: Jasper <241105014+jipeng6036-del@users.noreply.github.com>
Co-authored-by: Jasper <241105014+jipeng6036-del@users.noreply.github.com>
2026-10-26 10:27:07 +00:00
Kerekes Dániel
2a1525e57c fix: keep the usage-limit error when it arrives after compaction (#1377)
Fixes #1370

After a compaction in the same turn, the synthetic usage-limit frame is
dropped by the stale-frame suppression before its failure details are
read. The prompt then ends with a generic internal error instead of the
quota error, so clients can't show the limit UI.

The suppression branch now records the usage-limit title and error
before it skips the frame. Only recognized usage-limit frames are read
there, so a stale replayed frame still can't overwrite the failure of
the current turn.

Added a test for a usage-limit frame after a completed compaction, for
both the legacy `errorKind` and the typed session failure, plus a stale
frame arriving after it. It fails without the change. `npm run check`,
`npm run build` and `npm run test:run` pass.
2026-10-26 10:26:18 +00:00
PerryLink
54f1e21bc4 test: retry the acp-v2 teardown rm on Windows (#1373)
## What breaks

On Windows, `npm run test:run` fails inside `src/tests/acp-v2.test.ts`,
and every failure is raised by the teardown rather than by the behaviour
the test checks. Measured on Windows 11 build 26200, Node 22.22.3,
vitest 5.0.4, two runs:

```
$ node node_modules/vitest/vitest.mjs run src/tests/acp-v2.test.ts
 Test Files  1 failed (1)
      Tests  45 failed | 91 passed (136)
   Duration  11.75s (tests 91%, transform 7%, import 2%)
```

```
 Test Files  1 failed (1)
      Tests  53 failed | 83 passed (136)
   Duration  11.79s (tests 96%, import 2%, transform 2%)
```

Exit code 1 both times. Every failure is the same error, and it comes
from `afterEach`, after the test body has finished:

```
 FAIL  src/tests/acp-v2.test.ts > ACP v2 prompts > does not report the next turn as waiting for a question that its client never settled
Error: EBUSY: resource busy or locked, rmdir 'C:\Users\...\Temp\acp-v2-TNkkd2\project-J9PwxJ'

 FAIL  src/tests/acp-v2.test.ts > ACP v2 prompts > the tool calls that a cancel stops > ends a stopped tool call and its subagent's as cancelled, before it reports idle cancelled
Error: EBUSY: resource busy or locked, rmdir 'C:\Users\...\Temp\acp-v2-O3wrrr\project-NgBUMz'
```

The string `EBUSY` occurs 45 and 53 times in those two logs, matching
the two failure counts. The failing set moves between runs; which test
loses the race depends on when Windows releases each directory handle.

CI runs the suite on `ubuntu-latest` only
(`.github/workflows/ci.yml:20`, `:45`), so CI cannot show this. Anyone
who runs the documented `npm run test:run` on Windows gets a red file
and a non-zero exit.

## Why

The per-test teardown at `src/tests/acp-v2.test.ts:156` is:

```ts
await rm(tempDir, { recursive: true, force: true });
```

`force: true` suppresses ENOENT only; it does not retry EBUSY. Each
test's cwd is a child of `tempDir` (`cwd = await
mkdtemp(path.join(tempDir, "project-"))`, line 147), and Windows keeps a
directory busy briefly after the process holding it exits, so `rmdir`
returns EBUSY, `afterEach` rejects, and vitest marks the test failed.

## The fix

```diff
-  await rm(tempDir, { recursive: true, force: true });
+  await rm(tempDir, { recursive: true, force: true, maxRetries: 10 });
```

`maxRetries` is Node's own retry loop for `fs.rm`, covering EBUSY,
EMFILE, ENFILE, ENOTEMPTY and EPERM with a backoff between attempts. The
repository already uses it for the same class of problem in
`src/tests/session-hooks.test.ts:150`:

```ts
// The CLI may still write the transcript of the last session after dispose.
if (root) await rm(root, { recursive: true, force: true, maxRetries: 10 });
```

## Measured after

```
$ node node_modules/vitest/vitest.mjs run src/tests/acp-v2.test.ts
 Test Files  1 passed (1)
      Tests  136 passed (136)
   Duration  16.16s (tests 96%, transform 2%, import 2%)
```

A second run after restoring the patched file: `Tests 136 passed (136)`,
`Duration 16.56s`, exit code 0, `EBUSY` count 0. `npm run check` (eslint
plus `prettier --check .`) also passes, and `npm run build` (`tsc`,
whose `include` covers `src/**/*.ts` so the test file is typechecked
too) exits 0.

## Scope

1 file changed, 1 insertion, 1 deletion: `src/tests/acp-v2.test.ts`.

Deliberately not changed:

- No other test file, and no teardown other than this one call.
`session-hooks.test.ts` is untouched.
- No assertion, fixture, mock or production code, so the 136 tests still
check exactly what they checked before.
- No CI change. Adding a Windows job is a separate call about CI time
and runner budget, and this fix can be verified on Windows without one.

## Unproven

- I have one Windows 11 machine. On `ubuntu-latest` the teardown did not
fail here before or after the patch, and I did not run the job on Linux;
the Linux side is "no behaviour change" by inspection, not something I
measured.
- `maxRetries: 10` covers a handle that closes shortly after the process
exits. A handle held for longer than the whole retry window would still
surface EBUSY; I did not observe one.
- I ran only this file, not the full suite, so I cannot say whether
other files fail on Windows for other reasons.
2026-10-26 10:25:48 +00:00
dependabot[bot]
01b686713d ci(deps): Bump actions/create-github-app-token from 3.2.0 to 3.3.0 (#1383)
Bumps
[actions/create-github-app-token](https://github.com/actions/create-github-app-token)
from 3.2.0 to 3.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/releases">actions/create-github-app-token's
releases</a>.</em></p>
<blockquote>
<h2>v3.3.0</h2>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.2.0...v3.3.0">3.3.0</a>
(2026-10-12)</h2>
<h3>Features</h3>
<ul>
<li>update permission inputs (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/399">#399</a>)
(<a
href="a47236f79f">a47236f</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump nested undici from 6.24.0 to 6.29.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/403">#403</a>)
(<a
href="12461a3a79">12461a3</a>)</li>
<li><strong>deps:</strong> bump the production-dependencies group across
1 directory with 3 updates (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/395">#395</a>)
(<a
href="a69c19ddfd">a69c19d</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md">actions/create-github-app-token's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.2.0...v3.3.0">3.3.0</a>
(2026-10-12)</h2>
<h3>Features</h3>
<ul>
<li>update permission inputs (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/399">#399</a>)
(<a
href="a47236f79f">a47236f</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump nested undici from 6.24.0 to 6.29.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/403">#403</a>)
(<a
href="12461a3a79">12461a3</a>)</li>
<li><strong>deps:</strong> bump the production-dependencies group across
1 directory with 3 updates (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/395">#395</a>)
(<a
href="a69c19ddfd">a69c19d</a>)</li>
</ul>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0">3.2.0</a>
(2026-05-12)</h2>
<h3>Features</h3>
<ul>
<li>add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)
(<a
href="952a2a7073">952a2a7</a>)</li>
<li>support full repository names in <code>repositories</code> input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)
(<a
href="85eb8dd414">85eb8dd</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump <code>@​actions/core</code> from 3.0.0
to 3.0.1 in the production-dependencies group (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/364">#364</a>)
(<a
href="43e5c345bf">43e5c34</a>)</li>
<li>validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)
(<a
href="f24bbd8964">f24bbd8</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="64ab729a62"><code>64ab729</code></a>
chore(main): release 3.3.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/402">#402</a>)</li>
<li><a
href="f3a828e4b9"><code>f3a828e</code></a>
ci: create signed dist commits in release workflow (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/404">#404</a>)</li>
<li><a
href="655017ffa7"><code>655017f</code></a>
docs: clarify when owner grants access to all repositories (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/378">#378</a>)</li>
<li><a
href="12461a3a79"><code>12461a3</code></a>
fix(deps): bump nested undici from 6.24.0 to 6.29.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/403">#403</a>)</li>
<li><a
href="a47236f79f"><code>a47236f</code></a>
feat: update permission inputs (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/399">#399</a>)</li>
<li><a
href="a69c19ddfd"><code>a69c19d</code></a>
fix(deps): bump the production-dependencies group across 1 directory
with 3 u...</li>
<li><a
href="d5cb7ae41d"><code>d5cb7ae</code></a>
build(deps): bump actions/checkout from 6 to 7 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/384">#384</a>)</li>
<li><a
href="575da866bd"><code>575da86</code></a>
build(deps): bump stefanzweifel/git-auto-commit-action from 7.1.0 to
7.2.0 in...</li>
<li><a
href="e85f677b44"><code>e85f677</code></a>
test: omit stack traces from snapshots (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/401">#401</a>)</li>
<li><a
href="9419396df4"><code>9419396</code></a>
build(deps-dev): bump c8 from 11.0.0 to 12.0.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/400">#400</a>)</li>
<li>Additional commits viewable in <a
href="bcd2ba4921...64ab729a62">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/create-github-app-token&package-manager=github_actions&previous-version=3.2.0&new-version=3.3.0)](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>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-10-26 10:24:25 +00:00
KlausUllrich
6a33dedcbb fix: pass --settings before the auth status subcommand (#1392)
`runCliAuthProbe` builds `["auth", "status", "--json"]` and, when the
session has a settings
override, appends `--settings <json>`. `--settings` is a global option
of the Claude Code CLI;
the `auth status` subcommand only accepts `--json` and `--text` (see
`claude auth status --help`).
So every probe with an override fails with `error: unknown option
'--settings'` and an empty
stdout. The catch branch logs `claude auth status failed: …` on every
call and returns
`undefined`. For sessions that pass `_meta.claudeCode.options.settings`,
the session-creation and
per-prompt refreshes therefore never update the auth status. Only the
initialize probe, which has
no override, still works.

This moves `--settings <json>` in front of the subcommand. Without an
override the argv is
unchanged.

**Reproduce** (no credentials needed; an empty config dir answers "not
logged in"):

```sh
d=$(mktemp -d)
env -i HOME="$d" PATH="$PATH" CLAUDE_CONFIG_DIR="$d/.claude" \
  claude auth status --json --settings '{}'
# error: unknown option '--settings'
env -i HOME="$d" PATH="$PATH" CLAUDE_CONFIG_DIR="$d/.claude" \
  claude --settings '{}' auth status --json
# {"loggedIn": false, "authMethod": "none", ...}
```

The option is applied in the global position, not just parsed:
`claude --settings '{"apiKeyHelper":"/bin/echo rt-fake-key"}' auth
status --json` reports
`"loggedIn": true, "authMethod": "api_key_helper"`.

Tested with 10 native Claude Code binaries (linux-x64), each in an empty
`HOME`:

| Claude Code | `auth status --json --settings '{}'` | `--settings '{}'
auth status --json` |
|---|---|---|
| 2.1.286 | unknown option | JSON |
| 2.1.294 | unknown option | JSON |
| 2.1.295, 2.1.296, 2.1.299 | unknown option | JSON |
| 2.1.300, 2.1.301, 2.1.302 | unknown option | JSON |
| 2.1.303 (the SDK's `claudeCodeVersion`) | unknown option | JSON |
| 2.1.304 | unknown option | JSON |

The existing test in `auth-status.test.ts` pinned the old order; it now
expects `--settings`
first. Its mock and `statusCalls()` matched probes by `args[1] ===
"status"`, which a leading
global option shifts, so both now locate the subcommand instead.

`npm run build`, `format:check`, `lint` and `test:run` pass, apart from
one test that fails
identically on `main` without this change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:24:11 +00:00
Slapoguzov Alexander
e369479f93 fix: preserve foreground fork reports until launch completes (#1431)
A foreground fork can stream a copied `tool_result` for the Agent call
that launched it. That placeholder completed the call and removed its
tracked state, so the real report arriving later was dropped. Ignore
only results whose tool ID equals the message's parent tool ID, keeping
the launching call open for its actual report.

Keep other results in the same message and discard the message-level
`tool_use_result` sidecar when its launch result was removed, so it
cannot be attributed to a remaining tool result. Regression coverage
exercises plain and Air clients, root and nested launch results, and a
mixed message containing a genuine nested Agent result.

Fixes #1389.

Validation:

- [Linux
CI](https://github.com/agentclientprotocol/claude-agent-acp/actions/runs/40217593917)
passes the full suite: 3,304 passed, 32 skipped (57 test files passed, 4
skipped). All PR checks are green.
- `npm run check` and `npm run build` pass.
- `vitest run src/tests/acp-agent.test.ts --maxWorkers=1`: 712 passed,
10 skipped. The four new regression cases fail on the original code and
pass with the fix.
- Eight actual-SDK runs against localhost fake gateways complete
successfully without model calls: plain and Air foreground forks, native
Air child sessions, nested forks with the background option omitted or
false, the SDK's fork-inside-fork rejection, a background launch
receipt, and history replay. Foreground reports arrive exactly once and
no launch-result tracking warning remains.

The local full-suite run encountered 19 failures in
`session-index.test.ts` and then stopped progressing, so it was
interrupted. A standalone recursive-file-watcher probe also emits
`EMFILE` on this machine. The successful Linux CI run above provides the
complete-suite verification.

Review dependencies (updated 26 October):

| Related proposal | Relationship to this PR |
| --- | --- |
|
[#1408](https://github.com/agentclientprotocol/claude-agent-acp/pull/1408)
— targeted session lookup | Independent. Both orders merge cleanly onto
the checked main and produce the same tree. |
|
[#1422](https://github.com/agentclientprotocol/claude-agent-acp/pull/1422)
— resumed subagents | Complementary, with no dependency on this PR. It
now conflicts with upstream main in `src/acp-agent.ts`; its author needs
to resolve that conflict, integrate the [separately shared AIR routing
patch](https://github.com/agentclientprotocol/claude-agent-acp/pull/1422#issuecomment-6401008862)
and complete its validation. |

<details>
<summary>Additional local integration validation — 25 October</summary>

A local combination of the recorded main, #1408, #1431, #1422 and the
already-reviewed routing patch passed 199 focused
foreground/resume/native-subagent checks (647 other cases filtered or
skipped), TypeScript build, and two replays of previously captured SDK
frames for AIR and standard subagents. The two captured-frame replays
were independently repeated. No new model calls were made.

The combination is a private test tree, not a published change. It does
not replace full CI for #1422 or live Air acceptance. The routing patch
needed three-way application against that recorded main because its
surrounding source had moved; it applied cleanly that way. The
current-main conflict in #1422 means this historical result does not
establish today’s combined compatibility.

Checked main `739dde3`, #1408 `e3b3aa4`, #1431 `8f96f46`, #1422
`dd3bf2c`. The two orders of #1408 and #1431 produce tree `1716d7e`; the
full local test tree is `61c0447`. Refresh this evidence if these inputs
change.

</details>

Co-authored-by: Codex <codex@openai.com>
2026-10-26 11:20:15 +01:00
dependabot[bot]
c26e640915 fix: bump body-parser from 2.3.0 to 2.4.0 (#1436)
Bumps [body-parser](https://github.com/expressjs/body-parser) from 2.3.0
to 2.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/releases">body-parser's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.0</h2>
<h2>Important: Security</h2>
<ul>
<li>Security fix for <a
href="https://www.cve.org/CVERecord?id=CVE-2026-111181">CVE-2026-111181</a>
(<a
href="https://github.com/expressjs/body-parser/security/advisories/GHSA-27r3-jxmh-p828">GHSA-27r3-jxmh-p828</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump actions/checkout from 6.0.3 to 7.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/737">expressjs/body-parser#737</a></li>
<li>chore: group github actions updates by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/743">expressjs/body-parser#743</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/745">expressjs/body-parser#745</a></li>
<li>test: fix 'presist' typo in test descriptions by <a
href="https://github.com/maxtaran2010"><code>@​maxtaran2010</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/749">expressjs/body-parser#749</a></li>
<li>docs: include security fix in 2.3.0 changes by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/748">expressjs/body-parser#748</a></li>
<li>fix: avoid mutating read options when reading body by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/742">expressjs/body-parser#742</a></li>
<li>refactor: move getCharset helper to only usage by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/707">expressjs/body-parser#707</a></li>
<li>build(deps): bump the github-actions group with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/753">expressjs/body-parser#753</a></li>
<li>refactor: switch to neostandard by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/751">expressjs/body-parser#751</a></li>
<li>perf(json): avoid first-char regex for direct JSON bodies by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/744">expressjs/body-parser#744</a></li>
<li>docs: fix entity verify error description by <a
href="https://github.com/Rawal27"><code>@​Rawal27</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/755">expressjs/body-parser#755</a></li>
<li>feat: zstandard support by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/586">expressjs/body-parser#586</a></li>
<li>test: remove unused experimentalZstd option by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/757">expressjs/body-parser#757</a></li>
<li>ci: run tests on windows and macos matrix by <a
href="https://github.com/kilisamemarisaaa"><code>@​kilisamemarisaaa</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/759">expressjs/body-parser#759</a></li>
<li>deps: qs@^6.16.0 by <a
href="https://github.com/krzysdz"><code>@​krzysdz</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/762">expressjs/body-parser#762</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/763">expressjs/body-parser#763</a></li>
<li>ci: trigger npm publish on release published and set dist-tag by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/768">expressjs/body-parser#768</a></li>
<li>build(deps): bump the github-actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/771">expressjs/body-parser#771</a></li>
<li>fix(test): pin supertest to 7.3.0 to avoid 7.3.1 regression by <a
href="https://github.com/saadkhantareen"><code>@​saadkhantareen</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/773">expressjs/body-parser#773</a></li>
<li>perf: inline content stream setup by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/750">expressjs/body-parser#750</a></li>
<li>docs: note that req.body may be undefined in v2 by <a
href="https://github.com/Hashim1999164"><code>@​Hashim1999164</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/752">expressjs/body-parser#752</a></li>
<li>docs: fix strict option description by <a
href="https://github.com/Rawal27"><code>@​Rawal27</code></a> in <a
href="https://redirect.github.com/expressjs/body-parser/pull/756">expressjs/body-parser#756</a></li>
<li>docs: fix depth error description by <a
href="https://github.com/johnnyhuirilef"><code>@​johnnyhuirilef</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/776">expressjs/body-parser#776</a></li>
<li>chore: update dependencies by <a
href="https://github.com/Phillip9587"><code>@​Phillip9587</code></a> in
<a
href="https://redirect.github.com/expressjs/body-parser/pull/778">expressjs/body-parser#778</a></li>
<li>Release: 2.4.0 by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/body-parser/pull/777">expressjs/body-parser#777</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/maxtaran2010"><code>@​maxtaran2010</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/749">expressjs/body-parser#749</a></li>
<li><a href="https://github.com/Rawal27"><code>@​Rawal27</code></a> made
their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/755">expressjs/body-parser#755</a></li>
<li><a
href="https://github.com/kilisamemarisaaa"><code>@​kilisamemarisaaa</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/759">expressjs/body-parser#759</a></li>
<li><a
href="https://github.com/saadkhantareen"><code>@​saadkhantareen</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/773">expressjs/body-parser#773</a></li>
<li><a
href="https://github.com/Hashim1999164"><code>@​Hashim1999164</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/752">expressjs/body-parser#752</a></li>
<li><a
href="https://github.com/johnnyhuirilef"><code>@​johnnyhuirilef</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/body-parser/pull/776">expressjs/body-parser#776</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0">https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/body-parser/blob/master/HISTORY.md">body-parser's
changelog</a>.</em></p>
<blockquote>
<h1>2.4.0</h1>
<ul>
<li>Security fix for <a
href="https://github.com/expressjs/body-parser/security/advisories/GHSA-27r3-jxmh-p828">GHSA-27r3-jxmh-p828</a>
(CVE-2026-111181)</li>
<li>feat: add <code>zstd</code> content-encoding support (<a
href="https://redirect.github.com/expressjs/body-parser/issues/586">#586</a>)
<ul>
<li>Requires Node.js <code>^22.15.0</code> or <code>&gt;=23.8.0</code>;
older versions keep returning <code>415</code> for <code>zstd</code>
encoded bodies</li>
</ul>
</li>
<li>fix: avoid mutating the parser options and the request object while
reading the body (<a
href="https://redirect.github.com/expressjs/body-parser/issues/742">#742</a>,
<a
href="https://redirect.github.com/expressjs/body-parser/issues/750">#750</a>)</li>
<li>perf(json): avoid first-char regex for direct JSON bodies (<a
href="https://redirect.github.com/expressjs/body-parser/issues/744">#744</a>)</li>
<li>deps:
<ul>
<li>content-type@^2.1.0</li>
<li>iconv-lite^0.7.3</li>
<li>qs@^6.16.1</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b4744d739e"><code>b4744d7</code></a>
2.4.0 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/777">#777</a>)</li>
<li><a
href="f7075eae3a"><code>f7075ea</code></a>
fix: prevent process crash from trailing bytes after a compressed
body</li>
<li><a
href="185ff1d555"><code>185ff1d</code></a>
chore: update dependencies (<a
href="https://redirect.github.com/expressjs/body-parser/issues/778">#778</a>)</li>
<li><a
href="73713f76c4"><code>73713f7</code></a>
docs: fix depth error description (<a
href="https://redirect.github.com/expressjs/body-parser/issues/776">#776</a>)</li>
<li><a
href="2dcc826c6d"><code>2dcc826</code></a>
docs: improve readability (<a
href="https://redirect.github.com/expressjs/body-parser/issues/756">#756</a>)</li>
<li><a
href="402dbdc6f1"><code>402dbdc</code></a>
docs: note that req.body may be undefined in v2 (<a
href="https://redirect.github.com/expressjs/body-parser/issues/752">#752</a>)</li>
<li><a
href="9883a1eeb4"><code>9883a1e</code></a>
perf: inline content stream setup (<a
href="https://redirect.github.com/expressjs/body-parser/issues/750">#750</a>)</li>
<li><a
href="02101da415"><code>02101da</code></a>
fix(test): pin supertest to 7.3.0 to avoid 7.3.1 regression (<a
href="https://redirect.github.com/expressjs/body-parser/issues/773">#773</a>)</li>
<li><a
href="822a0fd462"><code>822a0fd</code></a>
build(deps): bump the github-actions group with 3 updates (<a
href="https://redirect.github.com/expressjs/body-parser/issues/771">#771</a>)</li>
<li><a
href="aa9889bc35"><code>aa9889b</code></a>
ci: trigger npm publish on release published and set dist-tag (<a
href="https://redirect.github.com/expressjs/body-parser/issues/768">#768</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/expressjs/body-parser/compare/v2.3.0...v2.4.0">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 body-parser since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=body-parser&package-manager=npm_and_yarn&previous-version=2.3.0&new-version=2.4.0)](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)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/agentclientprotocol/claude-agent-acp/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-26 11:19:09 +01:00
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>
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)
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)
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)
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.
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.
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.
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>
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.
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>
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/`.
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>
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.
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.
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.
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.
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>
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>
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>
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>
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.
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>
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>
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>
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>
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`.
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.
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>
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>
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>
2026-10-16 21:07:25 +02:00
187 changed files with 18193 additions and 1880 deletions

View File

@@ -27,6 +27,7 @@ jobs:
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: "lts/*"
check-latest: true
cache: "npm"
- name: Install dependencies

View File

@@ -36,7 +36,7 @@ jobs:
# Generating a GitHub token, so that PRs and tags created by
# the release-please-action can trigger actions workflows.
- name: Generate GitHub token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
id: generate-token
with:
# GitHub App ID secret name
@@ -77,6 +77,7 @@ jobs:
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: "lts/*"
check-latest: true
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build
@@ -132,6 +133,7 @@ jobs:
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: "lts/*"
check-latest: true
registry-url: "https://registry.npmjs.org"
# Ahead of the version bump, so this still validates the committed lockfile.
- run: npm ci
@@ -254,7 +256,7 @@ jobs:
exit 1
- name: Generate token scoped to the registry repo
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
id: registry-token
with:
app-id: ${{ secrets.REGISTRY_UPDATER_APP_ID }}

View File

@@ -1,3 +1,3 @@
{
".": "0.92.0"
".": "0.100.0"
}

View File

@@ -16,13 +16,85 @@ npm run format # prettier --write
CI runs `format:check`, `lint`, `build` and `test:run`. Run `npm run check` before
opening a PR.
## Code comments
## Coding guidelines
Keep comments straightforward, concise, and concrete. Say what the code does and
why, in plain terms: name the actual state, call, or failure involved instead of
alluding to it. Prefer a short concrete example over an abstract description.
Don't use vague phrases like "keeps it honest" or "the residual case" that make
the reader reconstruct the meaning.
- Keep edits consistent with the surrounding code's formatting, naming and idioms.
- Keep comments concise and concrete. Name the actual state, call or failure
instead of alluding to it, prefer a short example over an abstract
description, and avoid vague phrases like "keeps it honest". A comment or doc
must be true on every code path it describes, and a doc that lists what a
result omits or rejects must name the actual cases.
- When a request can't be served, throw a `RequestError` whose message says what
happened and what to do (e.g. "Try again when the turn ends"). Don't drop the
request silently.
- Don't report success for work you start without awaiting it. If the detached
call can still refuse and its `.catch` only logs, check the refusal condition
first or report the failure to the client.
- A result must not depend on what an earlier call happened to cache. A request
right after a restart, with cold caches, must return the same thing as one
after a warm-up.
- If code relies on an ordering or call sequence that nothing enforces (e.g. an
echo arriving before the result), say so in a short comment.
## Architecture and design fit
- Before coding, read the code the change plugs into and find the module that
owns the state or concept. Build on that module, not beside it.
- Don't add new features or components to `acp-agent.ts`. If no existing module fits the new
code cleanly, create a dedicated one that owns the feature's state and logic,
and keep the change in `acp-agent.ts` to the calls that wire it in.
- Prefer extending an existing mechanism over adding a new one, even if it takes
a small refactor (moving a helper, generalizing a function). Add a parallel
path, special case or flag only when the existing structure can't support the
change.
- Before writing new logic, check whether it already exists in a sibling handler
of the same flow or in a helper that works with the same data, and reuse it.
Paths that handle the same input must apply the same rules through the same
code: the live stream and the `session/load` replay, the command list and
command dispatch, `prompt` and `steer`. Two copies of one check drift apart.
- Put guards, filters and cleanup in the module that owns the state, so every
caller gets them. If guards, flags or fallbacks pile up in one place,
restructure instead of adding another.
- If a result is meant to match another request's output, reuse that request's
logic.
- The adapter serves ACP v1 and v2. A change that is not meant for one protocol
version only must work correctly in both. `src/v2/` converts v2 requests to v1
and reuses the v1 logic; keep v2-only differences in the conversion.
- A session runs one turn at a time. "Claude is working" means `startTurn`'s
check (an unsettled turn in `turnQueue`, or `foregroundWork` for the session);
don't write a narrower one. Anything that sends `session/update` must go
through the turn queue or be refused while the session is busy, or its output
lands inside another turn. Keep the session marked busy across awaited network
calls.
- Don't branch on a local copy of state that Claude Code or a remote service
owns: it goes stale when the other side changes. Have a command set the state
the user asks for instead of toggling it.
- A command or name the adapter adds must not shadow one that Claude Code or the
user's own commands and skills already provide.
- New optional request fields and `_meta` keys must not reject requests that
were valid before.
- If a workaround or new path is unavoidable (e.g. a Claude Code or SDK
limitation), say why in a short code comment and in the PR description.
## Testing guidelines
- Before adding a test, search `src/tests/` for existing tests of the same API
and scenario, and add to their file and `describe` block. Use the helpers in
`helpers.ts` and `session-doubles.ts` instead of copying setup.
- Don't add cases that take the same code path. Tests that differ only in input
and expected output belong in one `it.each`. Don't repeat another test's
assertions; assert only what this test is about.
- Reach a state the shortest way (e.g. set `session.tornDown = true`) instead of
driving a full flow with extra mocks, unless the flow is what you test.
- Build test doubles from what the bundled Claude Code CLI really sends,
including fields like `origin` it echoes back and fields it omits. A double
simpler than the real message hides bugs. If unsure, check against the real
CLI (`npm run test:integration`).
- Cover both ACP v1 and v2 when a change affects what a client receives
(updates, responses, replay). A change to internal logic only does not need
its tests duplicated per version.
- For each behavior the docs or comments promise, including what is omitted or
rejected, have a test that would fail if it broke.
## Pull requests

View File

@@ -1,5 +1,160 @@
# Changelog
## [0.100.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.1...v0.100.0) (2026-11-02)
### Features
* list v2 background tool calls ([#1511](https://github.com/agentclientprotocol/claude-agent-acp/issues/1511)) ([4d79b9f](https://github.com/agentclientprotocol/claude-agent-acp/commit/4d79b9ffbce95c23928a5dd771d6c87476d37160))
* report subagent working directories ([#1496](https://github.com/agentclientprotocol/claude-agent-acp/issues/1496)) ([eb78afa](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb78afa8556b8fc4082495e48e3fd1762fb6a50f))
* report v2 background work ([#1510](https://github.com/agentclientprotocol/claude-agent-acp/issues/1510)) ([5565259](https://github.com/agentclientprotocol/claude-agent-acp/commit/5565259771cb74e3a9bdbacfcbce43e061d81422))
* report why Claude Code could not start a session ([#1508](https://github.com/agentclientprotocol/claude-agent-acp/issues/1508)) ([38cf656](https://github.com/agentclientprotocol/claude-agent-acp/commit/38cf656fc9d80fec2d1a16c472779803e2c802d2))
### Bug Fixes
* align session/cancel, session/close, and v2 setup with the ACP v2 spec ([#1494](https://github.com/agentclientprotocol/claude-agent-acp/issues/1494)) ([f50598a](https://github.com/agentclientprotocol/claude-agent-acp/commit/f50598a39c5dc3908d4b98a7213e01d63ea867d5))
* forward v2 elicitation values and read dismissed v2 permission prompts as a refusal ([#1493](https://github.com/agentclientprotocol/claude-agent-acp/issues/1493)) ([3bc6711](https://github.com/agentclientprotocol/claude-agent-acp/commit/3bc671193c6456516fe05a0eaa53c17bdfb6a190))
* keep native Windows paths in v2 git patches ([#1492](https://github.com/agentclientprotocol/claude-agent-acp/issues/1492)) ([c3428de](https://github.com/agentclientprotocol/claude-agent-acp/commit/c3428de1bf411c513b0703ddd56bb6c5fdff583c))
* preserve live session settings across provider refreshes ([#1474](https://github.com/agentclientprotocol/claude-agent-acp/issues/1474)) ([e01a288](https://github.com/agentclientprotocol/claude-agent-acp/commit/e01a28838499dd6dd7c31e35708090b938e852c6))
* reject unreadable skill manifests ([#1482](https://github.com/agentclientprotocol/claude-agent-acp/issues/1482)) ([99a6e76](https://github.com/agentclientprotocol/claude-agent-acp/commit/99a6e76158b9164e51bd62e3b89113beadea5ce2))
* report the fresh context's usage when ExitPlanMode clears context ([#1484](https://github.com/agentclientprotocol/claude-agent-acp/issues/1484)) ([2fd46c8](https://github.com/agentclientprotocol/claude-agent-acp/commit/2fd46c866d4ff2939fe1b1245b15a536d6de7c0b))
* update to @agentclientprotocol/sdk 2.2.0 ([#1491](https://github.com/agentclientprotocol/claude-agent-acp/issues/1491)) ([1d2b92f](https://github.com/agentclientprotocol/claude-agent-acp/commit/1d2b92feae5eeccde3ac738edca1f1d71250762e))
* update to @agentclientprotocol/sdk 2.4.0 ([#1509](https://github.com/agentclientprotocol/claude-agent-acp/issues/1509)) ([720bbb3](https://github.com/agentclientprotocol/claude-agent-acp/commit/720bbb3e4c65874775a0bb9418799baf4b6e3680))
* update to Claude Agent SDK 0.3.316 (Claude Code 2.1.316) ([#1507](https://github.com/agentclientprotocol/claude-agent-acp/issues/1507)) ([1d03064](https://github.com/agentclientprotocol/claude-agent-acp/commit/1d0306468b632c6a56b4fb076ad7a19f23d9e7b5))
* validate session, prompt, config, and auth requests per the latest ACP spec ([#1495](https://github.com/agentclientprotocol/claude-agent-acp/issues/1495)) ([d2ef490](https://github.com/agentclientprotocol/claude-agent-acp/commit/d2ef490a318033049af00cb9164a39ac2dd1eb95))
## [0.99.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.0...v0.99.1) (2026-10-29)
### Bug Fixes
* add approval id to plan ([#1469](https://github.com/agentclientprotocol/claude-agent-acp/issues/1469)) ([4b4b2c4](https://github.com/agentclientprotocol/claude-agent-acp/commit/4b4b2c4de7c492070f6260cacea5680b2e045a5a))
* allow forking sessions into a different directory ([#1483](https://github.com/agentclientprotocol/claude-agent-acp/issues/1483)) ([8bd0bc9](https://github.com/agentclientprotocol/claude-agent-acp/commit/8bd0bc9b03fb4a30e0b1755128baad12a214a58d))
* detect "Run /login" login-expired messages as authRequired ([#1481](https://github.com/agentclientprotocol/claude-agent-acp/issues/1481)) ([fccaebc](https://github.com/agentclientprotocol/claude-agent-acp/commit/fccaebcf87e8f37144ec1a64920c0c53997edabe))
* mark plugin, bundled and nested Claude skills as skills for AIR ([#1473](https://github.com/agentclientprotocol/claude-agent-acp/issues/1473)) ([52c241e](https://github.com/agentclientprotocol/claude-agent-acp/commit/52c241e71512ec19c4dd79c95c51ce67f04d7aac))
* title subagent sessions by their task ([#1479](https://github.com/agentclientprotocol/claude-agent-acp/issues/1479)) ([7dcb629](https://github.com/agentclientprotocol/claude-agent-acp/commit/7dcb62923d38ac24e386b0626fe123334341c445))
## [0.99.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.98.0...v0.99.0) (2026-10-28)
### Features
* Add thinking display configuration ([#1081](https://github.com/agentclientprotocol/claude-agent-acp/issues/1081)) ([2f1f4d1](https://github.com/agentclientprotocol/claude-agent-acp/commit/2f1f4d144196c7518a7c072f9ae0e70ac9b91a44))
* send embedded blob resources in prompts to Claude ([#1460](https://github.com/agentclientprotocol/claude-agent-acp/issues/1460)) ([d0ee308](https://github.com/agentclientprotocol/claude-agent-acp/commit/d0ee30813fbc79dbe122f7455c8e1746e09be458))
* tell the client when an MCP server fails to start ([#1464](https://github.com/agentclientprotocol/claude-agent-acp/issues/1464)) ([54b35ee](https://github.com/agentclientprotocol/claude-agent-acp/commit/54b35ee0f4d765efc5bc39107a524891266c42ac))
* update to Claude Agent SDK 0.3.312 ([#1471](https://github.com/agentclientprotocol/claude-agent-acp/issues/1471)) ([4cb8906](https://github.com/agentclientprotocol/claude-agent-acp/commit/4cb8906d32a618011cfeba3260e4eb1db2fd829f))
* **v2:** send the current session info on session/resume ([#1462](https://github.com/agentclientprotocol/claude-agent-acp/issues/1462)) ([f6ed4a4](https://github.com/agentclientprotocol/claude-agent-acp/commit/f6ed4a4f7d9dd5b5e7ed4e9973d9661becb9f583))
### Bug Fixes
* deny a background permission request that the client cancels ([#1466](https://github.com/agentclientprotocol/claude-agent-acp/issues/1466)) ([f646c82](https://github.com/agentclientprotocol/claude-agent-acp/commit/f646c821f1b05df471aac8f1018ea08e2a793f6e))
* don't report decline when the client can't render an MCP elicitation ([#1465](https://github.com/agentclientprotocol/claude-agent-acp/issues/1465)) ([54b869c](https://github.com/agentclientprotocol/claude-agent-acp/commit/54b869c990ad3efa25c976e8eb3ff5a37808a6a3))
* list the current model among the model config option's values ([#1463](https://github.com/agentclientprotocol/claude-agent-acp/issues/1463)) ([8e6fe92](https://github.com/agentclientprotocol/claude-agent-acp/commit/8e6fe92a98dab8cec868a8a782e0ded55c94e571))
* report EnterPlanMode tool calls with the switch_mode kind ([#1467](https://github.com/agentclientprotocol/claude-agent-acp/issues/1467)) ([93c1f43](https://github.com/agentclientprotocol/claude-agent-acp/commit/93c1f43519a6a542dc55a39985d0abd12195129d))
* return ACP error codes for unknown sessions and invalid config values ([#1458](https://github.com/agentclientprotocol/claude-agent-acp/issues/1458)) ([c2e85cb](https://github.com/agentclientprotocol/claude-agent-acp/commit/c2e85cb58067d9b00e6609ce9e90d4ec3a5d0feb))
* update ACP SDK to 2.1.0 (schema v1.27.0 / v2.0.0-alpha.10) ([9f9aebf](https://github.com/agentclientprotocol/claude-agent-acp/commit/9f9aebf44bdecab887b03871fee8a2885ceac19a))
* **v2:** keep clients in sync with config changes and replayed tool calls ([#1461](https://github.com/agentclientprotocol/claude-agent-acp/issues/1461)) ([2a0c188](https://github.com/agentclientprotocol/claude-agent-acp/commit/2a0c188a0aebd0ea03fb449f72dfa53f98c7622d))
* **v2:** reject MCP transports and prompt content the agent doesn't advertise ([#1459](https://github.com/agentclientprotocol/claude-agent-acp/issues/1459)) ([2480888](https://github.com/agentclientprotocol/claude-agent-acp/commit/2480888b90498158054266bdb3bd7f22822e1b73))
## [0.98.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.97.0...v0.98.0) (2026-10-27)
### Features
* add batch session metadata lookup ([#1408](https://github.com/agentclientprotocol/claude-agent-acp/issues/1408)) ([0ce42f2](https://github.com/agentclientprotocol/claude-agent-acp/commit/0ce42f2c312d190f78024d8b4b1ae5f00d9e5939))
### Bug Fixes
* publish Monitor async tasks as monitors ([#1452](https://github.com/agentclientprotocol/claude-agent-acp/issues/1452)) ([4324dce](https://github.com/agentclientprotocol/claude-agent-acp/commit/4324dce4be877c55385b3389cebac1c63d08f456))
* settle the tool call of a SandboxNetworkAccess permission request ([#1448](https://github.com/agentclientprotocol/claude-agent-acp/issues/1448)) ([8f43ac8](https://github.com/agentclientprotocol/claude-agent-acp/commit/8f43ac808931a3f14719bbed37b9b153c9933139))
* **v2:** leave the tool call status out of permission subjects ([#1447](https://github.com/agentclientprotocol/claude-agent-acp/issues/1447)) ([c7c80f7](https://github.com/agentclientprotocol/claude-agent-acp/commit/c7c80f7b26f8d90f7f1c828036db64bc46dddddf))
## [0.97.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.96.0...v0.97.0) (2026-10-26)
### Features
* add ultracode session config option ([#1438](https://github.com/agentclientprotocol/claude-agent-acp/issues/1438)) ([de96cef](https://github.com/agentclientprotocol/claude-agent-acp/commit/de96cef382d8daf27301f1f90ae4b5e7f665f73a))
### Bug Fixes
* bump body-parser from 2.3.0 to 2.4.0 ([#1436](https://github.com/agentclientprotocol/claude-agent-acp/issues/1436)) ([c26e640](https://github.com/agentclientprotocol/claude-agent-acp/commit/c26e640915739dc7a3cd0664f8f5879098e0fd83))
* distinguish model options with duplicate labels ([#1286](https://github.com/agentclientprotocol/claude-agent-acp/issues/1286)) ([434be13](https://github.com/agentclientprotocol/claude-agent-acp/commit/434be13a33141af8655c6f7c30f574841bf2279c))
* keep the usage-limit error when it arrives after compaction ([#1377](https://github.com/agentclientprotocol/claude-agent-acp/issues/1377)) ([2a1525e](https://github.com/agentclientprotocol/claude-agent-acp/commit/2a1525e57cd89246d1a50993fd6ba335c4e634ca))
* pass --settings before the auth status subcommand ([#1392](https://github.com/agentclientprotocol/claude-agent-acp/issues/1392)) ([6a33ded](https://github.com/agentclientprotocol/claude-agent-acp/commit/6a33dedcbb90e7bf9db91207f0edbf19ca6d705e))
* preserve foreground fork reports until launch completes ([#1431](https://github.com/agentclientprotocol/claude-agent-acp/issues/1431)) ([e369479](https://github.com/agentclientprotocol/claude-agent-acp/commit/e369479f93b34f84a11103323d097c44df42e02e))
* settle advisor tool results before completing turns ([#1247](https://github.com/agentclientprotocol/claude-agent-acp/issues/1247)) ([bed795d](https://github.com/agentclientprotocol/claude-agent-acp/commit/bed795d261620e6d5113e427a7196d94e66e7034))
* settle live steers folded into autonomous cycles ([#1185](https://github.com/agentclientprotocol/claude-agent-acp/issues/1185)) ([a4957d1](https://github.com/agentclientprotocol/claude-agent-acp/commit/a4957d17c1e1a925ba3b72b29c5dcde4faabbe17))
## [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](https://github.com/agentclientprotocol/claude-agent-acp/commit/739dde3ae5bb848bdb90e2c9150e2cc4687d480c))
* report the model of each native subagent ([#1432](https://github.com/agentclientprotocol/claude-agent-acp/issues/1432)) ([c319e5e](https://github.com/agentclientprotocol/claude-agent-acp/commit/c319e5e542aabe353bb2c5e177e4c5853392b8c4))
### Bug Fixes
* do not hold an AIR load replay until the CLI starts ([#1433](https://github.com/agentclientprotocol/claude-agent-acp/issues/1433)) ([eb9d63e](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb9d63e5fde9b8fd8fe692dfb3280faba6626a42))
* refine RFD child models after a load, and retry a failed model update ([#1434](https://github.com/agentclientprotocol/claude-agent-acp/issues/1434)) ([9a8bf71](https://github.com/agentclientprotocol/claude-agent-acp/commit/9a8bf71d454fa85d03f55d4101a77e3ea7a149d0))
* send subagents' current state after a load response ([#1423](https://github.com/agentclientprotocol/claude-agent-acp/issues/1423)) ([17a967b](https://github.com/agentclientprotocol/claude-agent-acp/commit/17a967b4dab78bbb9cf9fedae60e6821578dc5cb))
* update to Claude Code 2.1.310 ([#1435](https://github.com/agentclientprotocol/claude-agent-acp/issues/1435)) ([2f3c6bb](https://github.com/agentclientprotocol/claude-agent-acp/commit/2f3c6bb7b3451a00cd77d7bb31c09e690ca220b3))
## [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](https://github.com/agentclientprotocol/claude-agent-acp/commit/9b3a5c891c75e78b985adb31f4e4b59a16ad2ebc))
* report read-only subagent configuration ([#1417](https://github.com/agentclientprotocol/claude-agent-acp/issues/1417)) ([6ab670b](https://github.com/agentclientprotocol/claude-agent-acp/commit/6ab670baaefb30aaac2606d8ee6524e08243e5b5))
* report RFD delegations as linked session messages ([#1405](https://github.com/agentclientprotocol/claude-agent-acp/issues/1405)) ([59e292e](https://github.com/agentclientprotocol/claude-agent-acp/commit/59e292e29f773cc6f03c0999993cbe8a2d97a7e3))
* serve experimental ACP v2 ([#1400](https://github.com/agentclientprotocol/claude-agent-acp/issues/1400)) ([4d378f9](https://github.com/agentclientprotocol/claude-agent-acp/commit/4d378f93f36d37ba5ef64af87b617706dd9c6e82))
* show messages typed in other Remote Control clients ([#1371](https://github.com/agentclientprotocol/claude-agent-acp/issues/1371)) ([5b11f47](https://github.com/agentclientprotocol/claude-agent-acp/commit/5b11f470b1c05b114c876b3b6e5b4df9441b1087))
* update to Claude Code 2.1.309 ([#1413](https://github.com/agentclientprotocol/claude-agent-acp/issues/1413)) ([a1521b6](https://github.com/agentclientprotocol/claude-agent-acp/commit/a1521b6196c58dbc5065f9c19fc9694273d3d435))
### Bug Fixes
* bump fast-uri from 3.1.8 to 3.1.9 ([#1410](https://github.com/agentclientprotocol/claude-agent-acp/issues/1410)) ([0fe22ae](https://github.com/agentclientprotocol/claude-agent-acp/commit/0fe22ae5a172b228a574665bb7136b900746041d))
* 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](https://github.com/agentclientprotocol/claude-agent-acp/commit/ecaa2c935b9f2b63c1846ca17c6c3398390fb0ef))
* bump the minor group across 1 directory with 2 updates ([#1416](https://github.com/agentclientprotocol/claude-agent-acp/issues/1416)) ([f7d6030](https://github.com/agentclientprotocol/claude-agent-acp/commit/f7d6030c04af23171058772480d55f6d2aee5298))
* IJAI-3744 keep a session running while its background tasks are open ([#1399](https://github.com/agentclientprotocol/claude-agent-acp/issues/1399)) ([e9f986b](https://github.com/agentclientprotocol/claude-agent-acp/commit/e9f986b8a941f5c1789078a39e474f247bd0db60))
* publish the session title as soon as the CLI has it ([#1406](https://github.com/agentclientprotocol/claude-agent-acp/issues/1406)) ([50cd76e](https://github.com/agentclientprotocol/claude-agent-acp/commit/50cd76e78dd9bb1ab0e4850a8d95431175ab5947))
* 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](https://github.com/agentclientprotocol/claude-agent-acp/commit/a1d0eeeaad3d67a0765f4b89e3d81c1af6ca0dbb))
## [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](https://github.com/agentclientprotocol/claude-agent-acp/commit/1420994ced20919aad127a8bd0294de022449e35))
### Bug Fixes
* match OpenRouter model ids in the gateway model listing ([#1365](https://github.com/agentclientprotocol/claude-agent-acp/issues/1365)) ([bcf74c2](https://github.com/agentclientprotocol/claude-agent-acp/commit/bcf74c2fe8e32bd24714d45122529dcf2f99a279))
* 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](https://github.com/agentclientprotocol/claude-agent-acp/commit/424db8753581bfaf4c5bf278f8df8c089eec35da))
* update to ACP SDK 2.0.0 ([#1386](https://github.com/agentclientprotocol/claude-agent-acp/issues/1386)) ([ff01e06](https://github.com/agentclientprotocol/claude-agent-acp/commit/ff01e06579225e00643914692994cd60da4d6225))
* update to Claude Code 2.1.303 ([#1384](https://github.com/agentclientprotocol/claude-agent-acp/issues/1384)) ([2dab922](https://github.com/agentclientprotocol/claude-agent-acp/commit/2dab92264414d133fd6774f7a90241ea5192ba32))
## [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](https://github.com/agentclientprotocol/claude-agent-acp/commit/db9f50f02145d088c0f6080672969a0673ba6359))
### 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](https://github.com/agentclientprotocol/claude-agent-acp/commit/f5967fd6b49ce4821cc829c26902ccf46f671992))
## [0.92.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.91.0...v0.92.0) (2026-10-16)

View File

@@ -17,6 +17,7 @@ This tool implements an ACP agent by using the official [Claude Agent SDK](https
- Interactive (and background) terminals
- Custom [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands)
- Client MCP servers
- An Ultracode session config option, which turns on Claude Code's dynamic workflows for every task. It appears when dynamic workflows are enabled and the model supports ultracode (any model with `xhigh` effort)
- `/mcp` in the chat: the MCP server status as a list. The adapter runs `/mcp reconnect`, `/mcp enable`, and `/mcp disable` through 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](docs/air-extensions.md#goal) under `_meta.jetbrains.air.goal`
- Structured errors, recovery, and warnings through the opt-in [session failure extension](docs/air-extensions.md#session-failure)
@@ -47,9 +48,26 @@ Task subagent as its own child session, following the [subagents RFD](https://gi
`end_turn` or `cancelled` when the SDK gives the reason. A failure is `idle` with the `error`
stop reason and the SDK's error text as its `error`. The child keeps its session for every
delegation to it.
- The prompt of the Agent or Task call that launches the child, and the message of each
SendMessage call that resumes it, is a `session_message` in the child's transcript. The tool
call stays a tool call of the session that made it.
- A child that runs outside its parent's working directory, such as an Agent call with
`isolation: "worktree"`, gets its `cwd` on `subagent_update` before its first tool call. The
adapter takes it from the `cwd` of the child's own hooks: its SubagentStart hook runs before its
first model request. It sends `cwd: null` when the child is back in its parent's directory. A
child in its parent's directory gets no `cwd`, and no child gets `additionalDirectories`: Claude
Code gives a subagent the session's additional directories, so it inherits them. A replay
announces a child with the `cwd` that its transcript recorded, and clears it after the response
when that directory no longer exists.
- Session messages replace the Agent or Task call that launches the child, which its parent is
not shown:
- 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 the
draft RFD's `recipientMessageId` and `senderMessageId`.
- A foreground child's answer, from the launch's result, is the child's `session_message` to
the parent. In Claude Code's hand-back mode, the child's `SubagentHandback` call is its
message to the parent instead, in both transcripts and linked the same way.
- A launch whose child is never exposed is shown as a tool call after all, with its final
status.
- The message of a SendMessage call that resumes the child is a `session_message` in the child's
transcript. The SendMessage call stays a tool call of the session that made it.
- `session/cancel` on the parent interrupts Claude Code, which stops the foreground children that
the turn waits on. Background children keep running, as the RFD prefers; a client stops one with
its own `cancel`. Each stopped child reports `idle` with `cancelled` from Claude Code's own
@@ -65,8 +83,8 @@ Task subagent as its own child session, following the [subagents RFD](https://gi
exits is `idle` with no stop reason.
- A child whose parent is not yet known when it ends is not exposed, since the RFD forbids
guessing its parent.
- A replay restores each child under its live session id, from its own transcript: its prompt and
each SendMessage message are `session_message`s, and its end is an `idle` before the response.
- A replay restores each child under its live session id, from its own transcript, with the same
session messages as live, and its end is an `idle` before the response.
A launch without Claude Code's metadata file for the child stays an ordinary tool call of the
parent, and a notice, for a client that takes notices, says that subagent history is missing.
- After a load or resume, with or without replay, a SendMessage that resumes an agent from before
@@ -95,10 +113,13 @@ root session. Clients that use the historical `_meta["subagent-transcript"]` cap
### Experimental ACP v2
Started with `CLAUDE_AGENT_ACP_EXPERIMENTAL_V2=1`, the adapter also serves the
[draft ACP v2](https://agentclientprotocol.com/protocol/v2/overview): a client that asks for
protocol version 2 or later in `initialize` gets v2, and every other client gets v1, as before.
The draft can still change, and this surface changes with it.
The adapter also serves the [draft ACP v2](https://agentclientprotocol.com/protocol/v2/overview):
a client that asks for protocol version 2 or later in `initialize` gets v2, and every other client
gets v1. The draft can still change, and this surface changes with it.
`CLAUDE_AGENT_ACP_PROTOCOL` limits the adapter to one version. With `v1`, a client that asks
for v2 is negotiated down to v1. With `v2`, the adapter rejects the `initialize` of a v1 client.
`auto`, the default, serves both.
On v2 the adapter serves the session baseline (`session/new`, `session/list`, `session/resume`
with replay, `session/close`, `session/prompt`, `session/cancel`), `session/delete`,

View File

@@ -27,8 +27,10 @@ It describes only this adapter.
- [Session failure](#session-failure)
- [Native subagent sessions](#native-subagent-sessions)
- [Session index](#session-index)
- [Background subagents](#background-subagents)
- [Context compaction](#context-compaction)
- [Question custom answers](#question-custom-answers)
- [Load replay end](#load-replay-end)
- [Session fork point](#session-fork-point)
- [Presentation hints](#presentation-hints)
- [Removed keys](#removed-keys)
@@ -124,7 +126,8 @@ The `initialize` response of an AIR client carries the agent side of the extensi
"asyncTasks",
"recommendedValue",
"diffPatch",
"planFile"
"planFile",
"backgroundSubagents"
],
"goal": {
"version": 1,
@@ -138,8 +141,8 @@ The `initialize` response of an AIR client carries the agent side of the extensi
}
```
The agent list does not depend on the capabilities that AIR declares, except `sessionIndex`: the agent lists it, together with `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`, only for a client that declared `sessionIndex`.
An extension is active only when the client declared its capability. The client does not declare `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`.
The agent list does not depend on the capabilities that AIR declares, except `sessionIndex`: the agent lists it, together with `sessionBatchGet`, `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`, only for a client that declared `sessionIndex`.
An extension is active only when the client declared its capability. The client does not declare `sessionBatchGet`, `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`.
The response to a client that is not AIR has no `_meta.jetbrains` key.
### Capabilities
@@ -153,11 +156,14 @@ The response to a client that is not AIR has no `_meta.jetbrains` key.
| `agentFileChangeReport` | yes | Accepts a report request on `session/prompt` and sends the changed file list. | [Agent file-change report](#agent-file-change-report) |
| `nativeSubagentSessions` | yes | Reports an Agent or Task subagent as a native ACP child session. | [Native subagent sessions](#native-subagent-sessions) |
| `planFile` | yes | Sends the path of the plan file in place of the plan text of an ExitPlanMode. | [Plan file](#plan-file) |
| `backgroundSubagents` | yes | Ends a prompt turn at its result while background subagents still run. | [Background subagents](#background-subagents) |
| `sessionIndex` | on request | Pages, orders and annotates `session/list`; adds rename, archive and a list subscription. | [Session index](#session-index) |
| `sessionBatchGet` | with `sessionIndex` | Supports `_session/batch_get` for metadata of known session IDs. | [Batch get](#batch-get) |
| `sessionArchive` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/archive` and `_session/unarchive`. | [Archive](#archive) |
| `sessionRename` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/rename`. | [Rename](#rename) |
| `sessionListSubscribe` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/list/subscribe`. | [List subscription](#list-subscription) |
| `sessionWorktrees` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that rows carry `agentWorktree` for a worktree Claude Code made. | [List response](#list-response) |
| `loadReplayEnd` | no | Sends one `session_info_update` after the whole history replay of a loaded session. | [Load replay end](#load-replay-end) |
| `rawInputRendering` | no | Sends no display copy of readable input in `content`. The client renders `rawInput`. | [Tool call contract](#tool-call-contract) |
The adapter ignores `planContentDelta`.
@@ -193,6 +199,7 @@ The adapter sends these keys only to an AIR client. "AIR" in the Gate column mea
| `kind` | session mode `_meta.jetbrains.air` and mode config option value `_meta.jetbrains.air` | `standard`, `plan`, `auto_review`, or `full_access` | AIR |
| `kind` | available command `_meta.jetbrains.air` of a skill | `skill` | AIR, when the skill's `SKILL.md` is found; not for a built-in command or an MCP prompt |
| `skillPath` | available command `_meta.jetbrains.air` of a skill | absolute path of the skill's `SKILL.md` | AIR, together with `kind: "skill"` |
| `loadReplayEnd` | `session_info_update._meta.jetbrains.air` | `true` | `loadReplayEnd` |
| `fork` | `session/fork` request `_meta.jetbrains.air` (client to agent) | `{version: 1, messageId, messageFingerprint?, messageOccurrence?}` | none |
## JetBrains shared keys
@@ -508,6 +515,17 @@ While the adapter does not know the path, a report carries no `planFilePath` key
The adapter never sends a blank path, because AIR reads a present non-file value as a retraction.
Without `rawInputRendering`, a `planFile` client gets no copy of the plan in `content` either.
### Approved plan identity
For an AIR client with plan operations, the approved plan ID is `plan-mode:<tool-call ID>`.
Claude uses the SDK tool-call ID of ExitPlanMode.
Each review keeps its own plan. Repeated updates of that review keep the same plan ID.
AIR joins the plan to its review by ID, even when the text includes user edits.
This link needs no extra metadata.
The live result, history replay, and clear-context approval use the same identity.
The identity does not depend on `planFile`: it also applies when the request carries inline plan text.
Other clients keep the `plan-mode` ID.
### Fallback
The plan text goes out as before in these cases:
@@ -701,7 +719,7 @@ A non-empty plan also gets one clear-context choice with the same priority.
After that choice, the adapter stops the old private Claude query and hides that internal rejection.
It starts a new private query under the same ACP session.
It continues the turn with `Implement the following plan:` and the complete plan.
The mode, model, agent, effort, Fast mode, and turn usage stay the same.
The mode, model, agent, effort, Fast mode, ultracode, and turn usage stay the same.
The SDK cannot apply `allowedPrompts` again, so those hints do not survive the handoff.
The adapter does not allow a callback only because the session mode is `bypassPermissions`.
@@ -991,6 +1009,20 @@ When an announced task leaves that list, the adapter reports `stopped`.
A later terminal event can correct that state to `completed` or `failed`.
At shutdown, the adapter finishes each open task as `failed` or `stopped`.
### Autonomous turn
Claude Code can start a turn of its own after the prompt ended, for example to answer the notification of a finished background task.
ACP v1 has no session state, so the adapter publishes such a turn as an async task.
The task has 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.
The task starts at the SDK `running` state while no prompt is active or queued.
When the state stays `running`, as it does while background agents run, it starts at the first root output with no prompt open.
It ends `completed` at the result of the turn or at the SDK `idle` state, and `failed` at an error result.
A coalesced placeholder result (`num_turns: 0`) does not end it.
A prompt that the turn takes in ends the task, because that prompt owns the turn now.
A stop request interrupts the turn instead of stopping an SDK task.
It returns `{ "stopped": false }` while a prompt waits for its turn, because the interrupt would drop that prompt.
### Stop request
The client sends `_session/async_task/stop`:
@@ -1262,8 +1294,8 @@ The session index makes `session/list` a bounded, ordered page with row metadata
AIR declares `sessionIndex` in `_meta.jetbrains.air.capabilities`.
Unlike the other capabilities, the agent advertises `sessionIndex` only to a client that declared it, and never under ACP v2.
Exactly when it advertises `sessionIndex`, the agent also advertises `sessionArchive` (it supports `_session/archive` and `_session/unarchive`), `sessionRename` (it supports `_session/rename`), `sessionListSubscribe` (it supports `_session/list/subscribe` and `_session/list/unsubscribe`) and `sessionWorktrees` (rows carry `agentWorktree`, see [List response](#list-response)).
The client does not declare these four; declaring them without `sessionIndex` enables nothing.
Exactly when it advertises `sessionIndex`, the agent also advertises `sessionBatchGet` (it supports `_session/batch_get`), `sessionArchive` (it supports `_session/archive` and `_session/unarchive`), `sessionRename` (it supports `_session/rename`), `sessionListSubscribe` (it supports `_session/list/subscribe` and `_session/list/unsubscribe`) and `sessionWorktrees` (rows carry `agentWorktree`, see [List response](#list-response)).
The client does not declare these five; declaring them without `sessionIndex` enables nothing.
Everything in this section applies only to such a client, except where [Delete and close](#delete-and-close) says otherwise.
A client without the capability gets the `session/list` of before: the SDK `listSessions` call, pages of 1000 by file mtime, `offset:N` cursors, and no row `_meta`.
It gets no new notification, and the adapter starts no watcher for it.
@@ -1353,6 +1385,7 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
- a session this connection runs: the SDK `session_state_changed` state; `error` instead of `idle` when its last turn failed (an error result, `is_error` or an `error_*` subtype, that was not a cancel). A session whose query failed here (not a cancel) is reported as any other session, but `error` until its transcript shows a prompt or a turn end after the failure, unless a busy or waiting interactive CLI holds it;
- a session no live Claude Code process holds: `idle`, or `error` when its last turn ended with an API error (an assistant record with `isApiErrorMessage: true`, also when the CLI's turn-end records follow it, with no prompt after it);
- a session that an interactive CLI holds (not an `sdk-*` entrypoint) whose registry status is newer than the transcript: `busy` and `shell` are `running`, `waiting` is `requires_action`, `idle` is `idle`, or `error` after such an API error;
- any other held session whose last turn ended with an open background task: `running`, also after 10 minutes, until the registry status is `idle`. Claude Code wakes the session again when the task ends, so the session gets one `idle` at the last turn end, not one for each task. A background shell command (`toolUseResult.backgroundTaskId`) or an async sub-agent (`isAsync` with an `agentId`) opens a task. Its `<task-notification>` with a `<status>`, as a user record or a `queue-operation` `remove`, or the result of a `TaskStop` call closes it. A notification without a `<status>`, such as the check-in of a quiet shell or a `Monitor` event, keeps it open. Sidechain records open no task. The tail gives the tasks on top of those of an earlier scan when the file only grew within the tail; else the whole transcript is read, and only for such a held session. A background task that a sub-agent starts is not seen: it is only in the transcript of the sub-agent;
- any other held session: `idle` after a finished turn (an assistant `end_turn`, a user interrupt, or the CLI turn-end records), `error` after a turn that ended with an API error, `running` for an unfinished turn written in the last 10 minutes, and no state otherwise.
- The live processes come from `<config>/sessions/<pid>.json`, with the liveness rules of Claude Desktop: `kill(pid, 0)` (or `EPERM`), a matching `procStart`, no foreign `pidDomain`, and records older than 24 hours only with a matching `procStart`.
The adapter never reads the `.key` files there and never changes the directory.
@@ -1421,6 +1454,71 @@ In all of the session index methods and in `session/delete`, a session id matche
Loading an archived session does not unarchive it.
The CLI cleanup still deletes old transcripts (`cleanupPeriodDays`), archived or not.
### Batch get
`_session/batch_get` refreshes metadata for retained session IDs, such as pinned
sessions and folder members outside the recent list page. Check for
`sessionBatchGet` in `initialize.result._meta.jetbrains.air.capabilities` before
calling it.
The params object must contain only `sessionIds`: an array of distinct,
non-blank strings. IDs match exactly, without trimming or case normalization.
Malformed params, duplicates or extra fields return `Invalid params` (`-32602`).
The agent sets no limit on the number of IDs; the client chooses the batch size
for the latency it accepts. Archived sessions and sessions in other projects
or worktrees are eligible.
```json
{
"jsonrpc": "2.0",
"id": 7,
"method": "_session/batch_get",
"params": { "sessionIds": ["oldA", "oldB", "oldC"] }
}
```
```json
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"sessions": [
{
"sessionId": "oldA",
"cwd": "/project",
"title": "Fix the build",
"updatedAt": "2026-01-01T00:00:00.000Z",
"_meta": { "jetbrains": { "air": { "version": 1, "archived": false } } }
}
]
}
}
```
The example IDs are placeholders for stored Claude session UUIDs. `sessions`
contains the same full rows as `session/list` without a project filter, including
archived sessions and `_meta.jetbrains.air`. Copies use the same
newest-readable-copy selection as the list. Returned sessions preserve their
relative request order. Non-UUID IDs and IDs without a listable transcript are
omitted: absent, empty, unreadable or malformed transcripts, sidechains, sessions
whose continuation has history, and transcripts whose cwd cannot be recovered.
A sidecar alone does not supply a row.
`{ "sessionIds": [] }` returns `{ "sessions": [] }` without reading the store.
A store-wide failure, such as an unreadable projects root, rejects the request.
Individual results reflect the store as each ID is read.
Lookup normally addresses only the requested transcript filenames in each project
and reads their metadata. A transcript without a cwd can use a sibling's cwd,
as the list does. This fallback enumerates and stats sibling transcripts only
in the directories of rows still missing a cwd, then reads at most 64 siblings
per directory, newest mtime first. A failed recovery is cached for 60 seconds
unless the directory mtime changes. Other project histories are not enumerated.
The agent looks up 100 IDs at a time; the client may request any number of IDs.
Merge returned rows with the recent page by `sessionId`. A batch read does not
expand an existing list subscription's scope; refresh retained IDs outside that
scope explicitly.
### List subscription
`_session/list/subscribe { "cwd": "…" }` returns `{ "subscriptionId": "…" }`.
@@ -1444,6 +1542,7 @@ From then on the agent sends `_session/list/changes` notifications for the sessi
- `cwd` is required and must be an absolute path; a missing, non-string or relative `cwd` is `-32602`. Other parameters are ignored.
- The scope is the sessions of `cwd` and of the same subdirectory in every existing linked worktree of its repository, as a list with `includeWorktrees: true` resolves it, in any archive state. Sidechain and subagent transcripts are left out, as in the list.
- There is no page or recent-session limit: changes to older pinned or folder sessions within that scope are reported even outside the page AIR loaded. Pinned IDs outside the subscribed project and worktrees are not tracked; `_session/batch_get` does not add them to the subscription. AIR must subscribe to their project scope or refresh them explicitly.
- `sessions` holds the full current row of each session in scope that appeared, or whose `title`, `lastPromptAt`, `state`, `lastTurnEndedAt`, `cost`, `model`, `forkedFrom`, `agentWorktree` or `archived` changed since the last row sent for it on this subscription. A row is exactly the row of `session/list`, `_meta.jetbrains.air` included.
- After subscribe, the first change of each session sends its row in full, even when only `updatedAt` changed. From then on, a change of `updatedAt` alone sends nothing; the new `updatedAt` comes with the next change.
- `removed` holds the ids of sessions that left the scope: deleted (the transcript is gone), or moved to a path outside it. It may name a session the client does not list (one it never listed, one the list hides, such as a session continued in another one when that one changes); the client ignores such an id.
@@ -1470,7 +1569,7 @@ It is idempotent, and an unknown id also returns `{}`.
`session/delete`:
- a `sessionIndex` client: deletes every transcript of the session (empty ones too) and every `<sessionId>/` directory, also one left without a transcript by an earlier failed delete. Anything left behind fails the request. A session that runs here is closed first. As with the CLI, the session is deleted also when another Claude Code process has it open. An unknown session is `-32002`.
- an AIR client without `sessionIndex`: archives the session instead of deleting it (the title records of [Archive](#archive)), because AIR uses delete for "Done" and can reopen the session. A session that runs here is closed first. The list hides archived sessions from such a client too: those whose custom title, as the SDK list reports it, is archived. A session without a transcript fails with the error of the SDK delete, as before.
- an AIR client without `sessionIndex`: archives the session instead of deleting it (the title records of [Archive](#archive)), because AIR uses delete for "Done" and can reopen the session. A session that runs here is closed first. The list hides archived sessions from such a client too: those whose custom title, as the SDK list reports it, is archived. A session without a transcript has nothing to archive, so the delete succeeds, as ACP asks of a session that does not exist.
A `sessionIndex` client sees such a session as archived (`archived: true`), as AIR's native Claude path does.
- any other client: the SDK delete. A session without a transcript (unknown, deleted before, or
never prompted) succeeds, as ACP asks.
@@ -1496,6 +1595,7 @@ Names and semantics follow them; the transport differs, and so do the `archived`
| capability `sessionIndex` (`_meta.jetbrains.air`) | `sessionCapabilities.list.limit` |
| capability `sessionArchive` (`_meta.jetbrains.air`) | `sessionCapabilities.archive` (#2161) |
| capability `sessionRename` (`_meta.jetbrains.air`) | `sessionCapabilities.setTitle` (#1987) |
| capability `sessionBatchGet`, `_session/batch_get` | none |
| `_meta.jetbrains.air.list.limit` | `session/list` `limit` |
| `_meta.jetbrains.air.list.includeWorktrees` | `session/list` `includeWorktrees` |
| `_meta.jetbrains.air.list.archived` (`"unarchived"`, `"archived"`, `"all"`) | `session/list` `archived` (#2161), a boolean there |
@@ -1521,6 +1621,45 @@ It differs in transport, until the RFD lands in the SDK, in the list filter, and
- the list parameter is `_meta.jetbrains.air.list.archived`, and the state is `_meta.jetbrains.air.archived` on rows and in `session_info_update`, not the `archived` fields;
- an AIR client without `sessionIndex` still archives with `session/delete` (see [Delete and close](#delete-and-close)), which the RFD asks clients not to do.
## Background subagents
A prompt turn can start a background Agent or Task subagent.
A SendMessage tool call can also resume a subagent that ended.
Claude Code sends the result of the turn when the main agent stops, also while such a subagent still runs.
### Hold without the capability
A client that does not declare `backgroundSubagents` gets the hold of issues #864 and #866.
The adapter keeps the `session/prompt` request open while a subagent that the turn started or resumed runs.
The subagent output, its permission requests and the followup summary of the model then go out inside the turn.
The turn ends at the followup result, at an `idle` with no live subagent, at `session/cancel`, or when the next prompt starts.
The hold protects a client that stops listening at the prompt response.
### Behavior with the capability
AIR declares `backgroundSubagents` in `initialize.clientCapabilities._meta.jetbrains.air.capabilities`.
The capability applies to ACP v1 only. ACP v2 keeps the hold, and the agent does not advertise the capability to ACP v2.
- The turn ends at its result with the stop reason of the result, usually `end_turn`.
- The subagent updates, permission requests and elicitations go out after the prompt response.
- The routing does not change: the native child session, else the root session with the parent tool call in `_meta.claudeCode.parentToolUseId`.
- The followup cycle after a subagent ends is an autonomous cycle. Its updates go out of a turn. Its result does not end a turn.
- A steer with no turn in flight starts a new turn, or returns `promptRequired` when the client asks for it.
- `session/cancel` with no turn works as before: the adapter interrupts the SDK query and reports each native subagent session as cancelled.
- The tool call of a running subagent does not fail the incomplete tool call check of the turn.
- The [agent file-change report](#agent-file-change-report) of the turn does not include an edit that a subagent makes after the result.
- An open permission request of a subagent counts as an open request for the next turn and for the steer priority.
### Plan approval out of a turn
The followup cycle can call ExitPlanMode. AIR then gets the permission request out of a turn.
The request has no clear-context option, because the fresh query must continue an ACP turn.
"No, keep planning" stops only the followup cycle. There is no turn to end.
A prompt turn owns the request when the turn is active, or when Claude Code asks before the echo of its prompt.
Then the request has the clear-context option.
A turn that took over a followup cycle also owns the request of that cycle, and the accepted plan continues in that turn.
## Context compaction
The adapter implements the ACP session compaction RFD for a client that declares `clientCapabilities.session.compaction`.
@@ -1576,6 +1715,32 @@ For AIR, the companion field also carries the same value under the root key `_me
Released AIR versions read only that key.
A client that is not AIR gets neither key.
## Load replay end
ACP has no signal that the history replay of `session/load` is over, other than the response.
The adapter replays the transcript while the Claude Code CLI starts, so the history can arrive long before the response.
The client declares `loadReplayEnd` in its capabilities. The agent does not advertise it.
After the history replay of the loaded session, the agent then sends one more update for that session:
```json
{
"sessionId": "<loaded session>",
"update": {
"sessionUpdate": "session_info_update",
"_meta": { "jetbrains": { "air": { "version": 1, "loadReplayEnd": true } } }
}
}
```
- The update has no `title` and no `updatedAt`. A `null` there would clear the value.
- It comes after every replayed update, the subagent history that the replay sends to the child sessions included, and before the `session/load` response.
- It comes also for an empty history.
- It comes for `session/load` of a new or an already live session, and for the ACP v2 `session/resume` with `replayFrom: {type: "start"}`.
- It does not come when the replay stops or fails. The replay stops when the CLI start fails during the replay.
- It does not come for a live update, and it does not come to a client without the capability.
- The `session/load` can still fail after it, for example when the CLI start fails after the replay. The event means that the recorded history arrived whole. The client decides what to do with the failed load.
## Session fork point
AIR can fork a session at one agent message.

View File

@@ -82,10 +82,10 @@ import {
client as acpClient,
methods,
ndJsonStream,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
// Type-only: Node's type stripping does not elide value imports, and these two
// exist only in the .d.ts — importing them normally is a runtime SyntaxError.
import type { AnyMessage, Stream } from "@agentclientprotocol/sdk";
import type { AnyMessage, Stream } from "@agentclientprotocol/sdk/unstable";
// ---------------------------------------------------------------- steering

View File

@@ -46,7 +46,7 @@ import {
ndJsonStream,
type PromptRequest,
type PromptResponse,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
/** The steering extension method, per the ACP steering wire protocol. */
const STEERING_METHOD = "_session/steering";

708
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.92.0",
"version": "0.100.0",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -65,26 +65,25 @@
"node": ">=22"
},
"dependencies": {
"@agentclientprotocol/sdk": "1.9.0",
"@anthropic-ai/claude-agent-sdk": "0.3.300",
"@agentclientprotocol/sdk": "2.4.0",
"@anthropic-ai/claude-agent-sdk": "0.3.316",
"diff": "9.0.0",
"zod": "4.6.7"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.134.0",
"@anthropic-ai/sdk": "1.3.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.6",
"@types/node": "26.6.5",
"@typescript-eslint/eslint-plugin": "8.71.2",
"@typescript-eslint/parser": "8.71.2",
"@types/node": "26.6.6",
"@typescript-eslint/eslint-plugin": "8.72.1",
"@typescript-eslint/parser": "8.72.1",
"ajv": "8.20.0",
"ajv-formats": "3.0.1",
"eslint": "10.12.0",
"eslint": "10.14.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.13.0",
"globals": "17.14.0",
"prettier": "3.9.10",
"ts-node": "10.9.2",
"typescript": "6.0.3",
"vitest": "5.0.4"
"vitest": "5.0.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ import type {
ClientCapabilities,
SessionCapabilities,
SessionNotification,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
import {
AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY,
clientSupportsAirCapability,
@@ -17,7 +17,8 @@ export { AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY } from "./air-extension.js";
* - `rfd`: the subagents RFD (agentclientprotocol/agent-client-protocol#1992,
* as revised): `subagent_update` and `session_message`, which the SDK types.
* A client that declares `clientCapabilities.subagents` and is not AIR gets
* it. (ACP v2 clients do not get subagents yet.)
* it. (An ACP v2 client gets it without declaring anything, since subagents
* are part of v2's baseline.)
* - `air`: the earlier draft of that RFD, which AIR implements:
* `subagent_spawned` and `subagent_state_update`, typed below. AIR gets it,
* whichever capability it declares (`docs/air-extensions.md`).

View File

@@ -1,5 +1,10 @@
export const AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY = "nativeSubagentSessions";
export const AIR_ASYNC_TASKS_CAPABILITY = "asyncTasks";
/**
* A prompt turn ends at its result also while its background subagents run.
* The subagent updates and requests then go out after the prompt response.
*/
export const AIR_BACKGROUND_SUBAGENTS_CAPABILITY = "backgroundSubagents";
export const AIR_SESSION_FAILURE_CAPABILITY = "sessionFailure";
export const AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY = "recommendedValue";
export const AIR_CUSTOM_INSTRUCTIONS_CAPABILITY = "customInstructions";
@@ -17,6 +22,9 @@ export const AIR_PLAN_FILE_CAPABILITY = "planFile";
* subscription. Advertised only to a client that declares it.
*/
export const AIR_SESSION_INDEX_CAPABILITY = "sessionIndex";
/** Metadata lookup of known ids with `_session/batch_get`. Advertised
* exactly when {@link AIR_SESSION_INDEX_CAPABILITY} is negotiated. */
export const AIR_SESSION_BATCH_GET_CAPABILITY = "sessionBatchGet";
/**
* The agent supports `_session/archive` and `_session/unarchive`. Advertised
* exactly when {@link AIR_SESSION_INDEX_CAPABILITY} is; the client does not
@@ -40,6 +48,14 @@ export const AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY = "sessionListSubscribe";
* {@link AIR_SESSION_INDEX_CAPABILITY} is; the client does not declare it.
*/
export const AIR_SESSION_WORKTREES_CAPABILITY = "sessionWorktrees";
/**
* The client wants to know when the history replay of a loaded session is
* whole. After the last replayed update, the agent sends one
* `session_info_update` with `_meta.jetbrains.air.loadReplayEnd: true` (see
* {@link AIR_LOAD_REPLAY_END_KEY}). The client declares it; the agent does not
* advertise it.
*/
export const AIR_LOAD_REPLAY_END_CAPABILITY = "loadReplayEnd";
/** The `_meta.jetbrains.air` keys that the ACP tool call contract defines. */
export const AIR_COMMAND_TITLE_KEY = "commandTitle";
@@ -53,6 +69,8 @@ export const AIR_KIND_KEY = "kind";
export const AIR_SKILL_PATH_KEY = "skillPath";
export const AIR_PERMISSION_KEY = "permission";
export const AIR_CUSTOM_ANSWER_KEY = "customAnswer";
/** The `_meta.jetbrains.air` key of the update that ends a whole history replay. */
export const AIR_LOAD_REPLAY_END_KEY = "loadReplayEnd";
/**
* The root `_meta` spelling of {@link AIR_CUSTOM_ANSWER_KEY} that released
* AIR versions read. Remove it when the ACP registry no longer serves AIR

View File

@@ -1,4 +1,5 @@
import type { ClientCapabilities } from "@agentclientprotocol/sdk";
import { randomUUID } from "node:crypto";
import type { ClientCapabilities } from "@agentclientprotocol/sdk/unstable";
import type { AcpSessionNotification, AsyncTaskState } from "./acp-subagents.js";
import { AIR_ASYNC_TASKS_CAPABILITY, clientSupportsAirCapability } from "./air-extension.js";
import { noticeOrTranscriptUpdate } from "./session-notices.js";
@@ -14,6 +15,8 @@ type AsyncTask = {
taskType: string;
description: string;
showInTranscript: boolean;
/** A Monitor tool call or an SDK `local_monitor` task: published as a monitor the client cannot stop. */
monitor: boolean;
outputFilePath?: string;
toolCallId?: string;
/**
@@ -39,6 +42,12 @@ type AsyncTask = {
terminalSource?: TerminalSource;
/** The optional fields that the client holds now, as JSON. */
published: Map<string, string>;
/**
* Whether the task stands for a model turn that Claude Code started on its
* own, see {@link AsyncTaskRuntime.autonomousTurnStarted}. The SDK knows no
* such task, so its background task list never ends it.
*/
autonomousTurn: boolean;
};
type TaskIdentity = { taskId?: unknown; task_id?: unknown };
@@ -121,6 +130,10 @@ export class AsyncTaskRuntime {
* The oldest entries are dropped.
*/
private readonly outputPathsByToolCall = new Map<string, string[]>();
/** The task of the autonomous turn that runs now. */
private autonomousTurn?: AsyncTask;
/** The summary of the last task notification, which usually starts the next autonomous turn. */
private lastNotificationSummary?: string;
constructor(
readonly enabled: boolean,
@@ -131,7 +144,8 @@ export class AsyncTaskRuntime {
* the tasks that a tool call in another session started, for example in
* a native subagent session. `toolNameOf`: the name of the tool of a tool
* call, or `undefined` when the tool call is unknown. A task that a
* `Monitor` tool call started is ignored. */
* `Monitor` tool call started is published as a monitor: the SDK reports
* it as `local_bash`, so the adapter names it a monitor. */
private readonly options: {
notices?: boolean;
routeOf?: (toolCallId: string) => Route | undefined;
@@ -144,7 +158,7 @@ export class AsyncTaskRuntime {
const taskId = taskIdOf(message);
if (!taskId) return;
const task = this.task(taskId);
if (isSubagentTask(message) || isMonitorTask(message)) {
if (isSubagentTask(message)) {
this.ignore(task);
return;
}
@@ -175,7 +189,7 @@ export class AsyncTaskRuntime {
const taskId = taskIdOf(message);
if (!taskId) return;
const task = this.task(taskId);
if (isSubagentTask(message) || isMonitorTask(message)) {
if (isSubagentTask(message)) {
this.ignore(task);
return;
}
@@ -300,6 +314,7 @@ export class AsyncTaskRuntime {
const state = taskState(message.status);
if (!taskId || !state || state === "running" || state === "paused") return;
this.lastNotificationSummary = nonBlankString(message.summary) ?? this.lastNotificationSummary;
const task = this.task(taskId);
const wasTerminal = isTerminal(task.state);
const correctsLevelState = wasTerminal && task.terminalSource === "level";
@@ -343,7 +358,7 @@ export class AsyncTaskRuntime {
const task = this.task(taskId);
if (task.ignored || isTerminal(task.state)) continue;
const levelTaskType = field(item, "taskType", "task_type");
if (levelTaskType === "local_agent" || levelTaskType === "local_monitor") {
if (levelTaskType === "local_agent") {
this.ignore(task);
continue;
}
@@ -363,6 +378,7 @@ export class AsyncTaskRuntime {
if (
(task.announced || task.held) &&
!task.ignored &&
!task.autonomousTurn &&
!isTerminal(task.state) &&
!live.has(task.id)
) {
@@ -378,6 +394,7 @@ export class AsyncTaskRuntime {
}
async finishAll(state: Extract<AsyncTaskState, "failed" | "stopped">): Promise<void> {
this.autonomousTurn = undefined;
const errors: unknown[] = [];
for (const task of this.tasks.values()) {
if ((task.announced || task.held) && !task.ignored && !isTerminal(task.state)) {
@@ -394,7 +411,13 @@ export class AsyncTaskRuntime {
canStop(taskId: string): boolean {
const task = this.tasks.get(taskId);
return task?.announced === true && !task.ignored && !task.stopping && !isTerminal(task.state);
return (
task?.announced === true &&
!task.monitor &&
!task.ignored &&
!task.stopping &&
!isTerminal(task.state)
);
}
claimStop(taskId: string): boolean {
@@ -412,6 +435,7 @@ export class AsyncTaskRuntime {
const task = this.tasks.get(taskId);
if (!task || !task.announced || task.ignored || task.stopAnnounced) return;
task.stopAnnounced = true;
if (this.autonomousTurn === task) this.autonomousTurn = undefined;
// No terminal summary: a stopped task leaves the Async Tasks panel at once,
// so anything said there is said to nobody.
await this.finish(task, "stopped", undefined, "event");
@@ -438,6 +462,54 @@ export class AsyncTaskRuntime {
);
}
/**
* Publishes a model turn that Claude Code started with no prompt open, for
* example to answer a task notification. ACP v1 has no session state, and
* the client already got the stop reason of its last prompt, so this task is
* the only sign that the agent works. The tool calls of the turn are in the
* transcript already, so the task is not shown there.
*
* [cause] names what started the turn when Claude Code says so, for example
* "A scheduled wakeup fired." It replaces the description of a task that is
* already open; without it, an open task is left as it is.
*/
async autonomousTurnStarted(cause?: string): Promise<void> {
if (!this.enabled) return;
if (this.autonomousTurn) {
if (cause) await this.publishProgress(this.autonomousTurn, { description: cause });
return;
}
const task = this.task(`autonomous-turn-${randomUUID()}`);
task.autonomousTurn = true;
task.startedObserved = true;
task.name = "Claude is working";
task.taskType = "turn";
task.description =
cause ?? this.lastNotificationSummary ?? "Claude Code started a turn without a prompt.";
task.showInTranscript = false;
this.lastNotificationSummary = undefined;
this.autonomousTurn = task;
await this.announce(task, { withoutToolCall: true });
}
/** Whether the task of an autonomous turn is open. */
get autonomousTurnOpen(): boolean {
return this.autonomousTurn !== undefined;
}
/** Ends the task of the autonomous turn that runs now, if there is one. */
async autonomousTurnEnded(state: "completed" | "failed"): Promise<void> {
const task = this.autonomousTurn;
if (!task) return;
this.autonomousTurn = undefined;
await this.finish(task, state, undefined, "event");
}
/** Whether the task stands for an autonomous turn, which a stop interrupts. */
isAutonomousTurn(taskId: string): boolean {
return this.tasks.get(taskId)?.autonomousTurn === true;
}
/** Whether a task of the tool call went to the background, so the client gets it. */
isBackgroundedToolCall(toolCallId: string): boolean {
for (const task of this.tasks.values()) {
@@ -488,6 +560,8 @@ export class AsyncTaskRuntime {
clear(): void {
this.tasks.clear();
this.outputPathsByToolCall.clear();
this.autonomousTurn = undefined;
this.lastNotificationSummary = undefined;
}
/**
@@ -510,6 +584,7 @@ export class AsyncTaskRuntime {
taskType: "task",
description: "Background task",
showInTranscript: true,
monitor: false,
announced: false,
held: false,
deferred: [],
@@ -520,6 +595,7 @@ export class AsyncTaskRuntime {
stopAnnounced: false,
state: "running",
published: new Map(),
autonomousTurn: false,
};
this.tasks.set(taskId, task);
return task;
@@ -529,6 +605,7 @@ export class AsyncTaskRuntime {
task.startedObserved = true;
const rawTaskType = field(message, "taskType", "task_type");
if (rawTaskType !== undefined) task.taskType = friendlyTaskType(rawTaskType);
if (rawTaskType === "local_monitor") task.monitor = true;
const description = nonBlankString(message.description);
if (description) task.description = description;
else if (task.description === "Background task")
@@ -586,6 +663,7 @@ export class AsyncTaskRuntime {
private mergeLevel(task: AsyncTask, level: BackgroundTaskLevel): void {
const rawTaskType = field(level, "taskType", "task_type");
if (rawTaskType !== undefined) task.taskType = friendlyTaskType(rawTaskType);
if (rawTaskType === "local_monitor") task.monitor = true;
const description = nonBlankString(level.description);
if (description) {
task.description = description;
@@ -610,25 +688,29 @@ export class AsyncTaskRuntime {
* ends first, or when the prompt result ends the model turn
* ({@link releaseHeld}). No guess by the command text binds a task.
*
* A task that a `Monitor` tool call started is ignored: Monitor streams its
* output to the model only, so the task has nothing for the client. The
* tool_use streams before the tool runs, so the tool name is known when the
* spawn has the tool call id.
* A task that a `Monitor` tool call started is published as a monitor. The
* SDK reports it as `local_bash`, so the adapter names it a monitor instead of
* a shell. It has no transcript card because Monitor streams its output to
* the model only, and no Stop because stopping it only ends the agent's wait
* (the agent's own TaskStop still works). The tool_use streams before the
* tool runs, so the tool name is known when the spawn has the tool call id.
*/
private async announce(
task: AsyncTask,
options: { withoutToolCall?: boolean } = {},
): Promise<void> {
if (task.announced || task.ignored) return;
const toolName = task.toolCallId ? this.options.toolNameOf?.(task.toolCallId) : undefined;
if (toolName !== undefined && HIDDEN_TASK_TOOLS.has(toolName)) {
this.ignore(task);
return;
}
if (!task.toolCallId && !options.withoutToolCall && !isTerminal(task.state)) {
task.held = true;
return;
}
if (task.toolCallId && this.options.toolNameOf?.(task.toolCallId) === "Monitor") {
task.monitor = true;
}
if (task.monitor) {
task.taskType = "monitor";
task.showInTranscript = false;
}
if (task.toolCallId) task.route ??= this.options.routeOf?.(task.toolCallId);
await this.send(task, {
sessionUpdate: "async_task_spawned",
@@ -637,7 +719,7 @@ export class AsyncTaskRuntime {
taskType: task.taskType,
description: task.description,
showInTranscript: task.showInTranscript,
canStop: true,
canStop: !task.monitor,
...(task.outputFilePath ? { outputFilePath: task.outputFilePath } : {}),
...(task.toolCallId ? { toolCallId: task.toolCallId } : {}),
});
@@ -985,13 +1067,6 @@ function isBackgroundTask(isBackgrounded: unknown, taskType: unknown): boolean {
return taskType !== "local_bash" && taskType !== "local_agent";
}
/** The tools whose async tasks the client never sees. */
const HIDDEN_TASK_TOOLS = new Set(["Monitor"]);
function isMonitorTask(message: AsyncTaskStarted): boolean {
return field(message, "taskType", "task_type") === "local_monitor";
}
function isSubagentTask(message: AsyncTaskStarted): boolean {
return (
field(message, "subagentType", "subagent_type") !== undefined ||

131
src/background-output.ts Normal file
View File

@@ -0,0 +1,131 @@
/**
* The output of a command that went to the background, read from the file
* that Claude Code writes it to. Claude Code streams no output of such a
* command, so its display terminal would show only the placeholder of the
* Bash result ("Command running in background with ID …") until the command
* exits. ACP v2 asks an Agent to keep a listed terminal's output current
* whenever it can read it (the v2 prompt lifecycle, "Background work").
*
* The file is polled: watching a file is not reliable on every platform, and
* a missing file only means that nothing was written yet.
*/
import { open } from "node:fs/promises";
import { StringDecoder } from "node:string_decoder";
/** How often a running command's output file is read. */
export const BACKGROUND_OUTPUT_POLL_MS = 500;
/** The most bytes one read takes, so a burst of output does not block the loop. */
export const BACKGROUND_OUTPUT_READ_BYTES = 256 * 1024;
/**
* The most unread bytes that are still sent. A command that writes faster
* than the polls read falls behind. Past this, the reads skip to the file's
* last bytes, as a snapshot: a terminal shows the latest output, and a
* client keeps a bounded amount of it anyway.
*/
export const BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES = 1024 * 1024;
/**
* Text that the file adds. A snapshot replaces the terminal's output so far:
* the first one replaces the placeholder of the Bash result, and a later one
* follows a skip or a file that shrank. Other text appends to it.
*/
export type BackgroundOutput = { text: string; replaces: boolean };
/**
* Follows the output file `path` of one background command and hands each
* piece of new text to `publish`, in order. Reads never overlap. A read that
* fails, for example because the file does not exist yet, is tried again at
* the next poll. `publish` must not reject: the next read waits for it.
*/
export class BackgroundOutputTail {
private offset = 0;
private decoder = new StringDecoder("utf8");
/** Whether the next text replaces the terminal's output. */
private replaces = true;
private stopped = false;
private reading: Promise<void> = Promise.resolve();
private readonly timer: ReturnType<typeof setInterval>;
constructor(
private readonly path: string,
private readonly publish: (output: BackgroundOutput) => Promise<void>,
) {
this.timer = setInterval(() => void this.read(false), BACKGROUND_OUTPUT_POLL_MS);
// A command that runs on must not keep the agent process alive.
this.timer.unref();
}
/**
* Reads the rest of the file and stops. The command ended, so what the file
* holds is its whole output, and it goes out before the terminal's exit.
*/
async finish(): Promise<void> {
clearInterval(this.timer);
await this.read(true);
this.stopped = true;
}
/** Stops without reading more: the session ended. */
stop(): void {
clearInterval(this.timer);
this.stopped = true;
}
/**
* Reads what the file adds, one read of at most
* {@link BACKGROUND_OUTPUT_READ_BYTES} at a time, and publishes each read's
* text before the next. `toEnd` reads until the end of the file, and ends a
* character that the command cut off with a replacement character.
*/
private read(toEnd: boolean): Promise<void> {
this.reading = this.reading.then(async () => {
if (this.stopped) return;
let file;
try {
file = await open(this.path, "r");
} catch {
// Nothing written yet, or the file is gone: try again at the next
// poll. At the end, the terminal keeps the output it has.
return;
}
try {
const { size } = await file.stat();
// A file that shrank is a new one: its output starts over. Past the
// backlog, the reads skip to the file's last bytes.
const start = size < this.offset ? 0 : this.offset;
const skipTo = Math.max(start, size - BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES);
if (skipTo !== this.offset) {
this.offset = skipTo;
this.decoder = new StringDecoder("utf8");
this.replaces = true;
}
const buffer = Buffer.alloc(BACKGROUND_OUTPUT_READ_BYTES);
let bytesRead: number;
do {
({ bytesRead } = await file.read(buffer, 0, buffer.length, this.offset));
this.offset += bytesRead;
const text = this.decoder.write(buffer.subarray(0, bytesRead));
if (text !== "") await this.send(text);
} while (toEnd && bytesRead > 0);
if (toEnd) {
const rest = this.decoder.end();
// A command that wrote nothing still replaces the placeholder.
if (rest !== "" || this.replaces) await this.send(rest);
}
} catch {
// The read failed: try again at the next poll.
} finally {
await file.close();
}
});
return this.reading;
}
private async send(text: string): Promise<void> {
const replaces = this.replaces;
this.replaces = false;
await this.publish({ text, replaces });
}
}

627
src/background-work.ts Normal file
View File

@@ -0,0 +1,627 @@
/**
* The background work of a session: the work that Claude Code keeps running
* independently of the session's foreground work, which ACP v2 lists on every
* `state_update` of the session as `background` (the v2 prompt lifecycle,
* "Background work").
*
* The lists are built from Claude Code's live background task set
* (`background_tasks_changed`), without the tasks it marks `ambient`, which
* are housekeeping that it started on its own. The root session and each
* announced subagent session (the subagents RFD form) has its own list. Each
* task is listed once, in the list of the session that runs it:
*
* - A command that went to the background: its display terminal, from the
* Bash tool result until its task ends (`Session.backgroundTerminals`).
* - A detached subagent: its child session, in its parent's list, while it
* works, and while it is idle with background work of its own.
* - A task that a tool call started, such as a `Monitor`, a `Workflow` or a
* backgrounded MCP call: that tool call, kept `in_progress` (or `paused`)
* from its tool result until its task ends (`Session.backgroundToolCalls`).
* - Any other task, once its `task_started` came and the tool call that
* started it, if any, returned: a `_claude/task` item with the task's id
* and description. For example, a task whose tool call ended before the
* task joined the level.
*
* Only a session whose interrupt spares background tasks reports its lists
* (`Session.reportsBackground`): `session/cancel` must leave listed work
* running.
*/
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { AcpSessionNotification } from "./acp-subagents.js";
import type { NativeSubagent } from "./native-subagents.js";
import { type AcpToolCallRenderer, V2_CANCELLED, V2_PAUSED } from "./tool-calls/renderer.js";
import { type BackgroundOutput, BackgroundOutputTail } from "./background-output.js";
/** An item of a `background` list. */
export type BackgroundItem =
| { type: "terminal"; terminalId: string }
| { type: "tool_call"; toolCallId: string }
| { type: "subagent"; sessionId: string }
| { type: "_claude/task"; taskId: string; description: string };
/** The display terminal of a command that went to the background (see `Session.backgroundTerminals`). */
export interface BackgroundTerminal {
/** The Bash tool call, whose id names the terminal. */
toolCallId: string;
/** The subagent tool call that ran the command, if any. */
parentToolUseId?: string;
/** The file that Claude Code writes the command's output to, from the Bash result. */
outputFilePath?: string;
/** Reads that file into the terminal while the command runs. */
output?: BackgroundOutputTail;
}
/** A tool call whose task runs on in the background (see `Session.backgroundToolCalls`). */
export interface BackgroundToolCall {
toolCallId: string;
/** The tool use of the call, which Claude Code no longer caches after its result. */
toolUse: { name: string; input: unknown };
/** The subagent tool call that made the call, if any. */
parentToolUseId?: string;
paused?: boolean;
}
/** How a background task ended, as Claude Code reports it. Undefined when it did not say. */
export type BackgroundTaskEnd = "completed" | "failed" | "stopped" | undefined;
/** The live background task set of `background_tasks_changed`, by task id. */
export type BackgroundLevel = Map<
string,
{ ambient: boolean; taskType: string; description: string; parentTaskId?: string }
>;
/** The work state of a session, as its `state_update` names it. */
export type WorkStateName = "idle" | "running" | "requires_action" | "unknown";
/** A new list of the session `sessionId`, with what its repeated state needs. */
export interface BackgroundReport {
sessionId: string;
items: BackgroundItem[];
/** The session's work state now, or undefined when it has none yet. */
state: WorkStateName | undefined;
/** The parent of a subagent session, which mirrors the child's state. */
parentSessionId?: string;
}
/** Where the agent reports the lists (see the agent's `background` option). */
export interface BackgroundSink {
/** The list of a session changed, or is reported for the first time. */
changed(report: BackgroundReport): void;
/** The session ended: its list is no longer reported. */
forget(sessionId: string): void;
}
/**
* The state of a session that other modules own, and that the background
* work reads: Claude Code's tasks as the consumer tracks them, the subagent
* sessions, and the query that stops a task.
*/
export interface BackgroundOwner {
liveBackgroundTasks: Map<string, { parentToolUseId?: string; isSubagent: boolean }>;
nativeSubagentsByTaskId?: Map<string, NativeSubagent>;
query: { stopTask(taskId: string): Promise<void> };
}
/** A task of a `background_tasks_changed` payload. */
export type LevelTask = {
task_id: string;
task_type: string;
description: string;
parent_task_id?: string;
ambient?: boolean;
};
/** The most returned tool calls that {@link SessionBackgroundWork} remembers. */
const MAX_RETURNED_CALLS = 1024;
/** The tools whose structured result names the task that the call started. */
const TASK_TOOLS = new Set(["Monitor", "Workflow"]);
/**
* The background work of one root session, and of its subagent sessions: the
* terminals and tool calls of the tasks that run in the background, and what
* the client is told of them.
*
* Terminals are kept for every client that renders them, as before the
* background lists. The rest is kept only when `reported`.
*/
export class SessionBackgroundWork {
/** Claude Code's live background task set, from its last `background_tasks_changed`. */
private level: BackgroundLevel = new Map();
/** The terminals of the commands that went to the background, by task id. */
private readonly terminals = new Map<string, BackgroundTerminal>();
/** The tool calls whose tasks went on in the background, by task id. */
private readonly toolCalls = new Map<string, BackgroundToolCall>();
/**
* The tool calls that returned, oldest first. A task that a call started
* is part of that call until it returns, and the foreground work waits on
* it. The task's `task_started` can come after the call returned. Bounded
* by {@link MAX_RETURNED_CALLS}: a task joins the level around the time its
* call returns.
*/
private readonly returned = new Set<string>();
/**
* `reported`: whether the client gets the lists. Only when Claude Code's
* interrupt spares background tasks, as `perTaskStopAffordance` makes it.
*/
constructor(
private readonly sessionId: string,
readonly reported: boolean,
) {}
/**
* Keeps the display terminal of the command `taskId` running after its Bash
* result, until its task ends (see `withRunningTerminal`).
*/
terminalStarted(taskId: string, terminal: BackgroundTerminal): void {
this.terminals.set(taskId, terminal);
}
/**
* Reads what a tool result message tells of the background tasks:
*
* - The returned calls no longer hold the tasks that they started.
* - Each call whose task goes on in the background is registered: a
* `Monitor` or `Workflow` call, whose structured result names its task,
* or a call that a live task names as its tool use, such as a
* backgrounded MCP call. The task must be in the level: one that ended
* already, or that Claude Code marks `ambient`, leaves its call as it is.
*
* Returns the ids of the registered calls, whose result then reports
* `in_progress` (see `withRunningToolCall`).
*/
toolResults(
owner: BackgroundOwner,
content: unknown,
toolUseResult: unknown,
toolUses: Record<string, { name: string; input: unknown }>,
parentToolUseId: string | null,
): Set<string> {
const registered = new Set<string>();
if (!this.reported || !Array.isArray(content)) return registered;
const results = content.filter(
(block): block is { type: "tool_result"; tool_use_id: string; is_error?: boolean } =>
typeof block === "object" &&
block !== null &&
block.type === "tool_result" &&
typeof block.tool_use_id === "string",
);
for (const { tool_use_id: toolCallId } of results) {
this.returned.add(toolCallId);
if (this.returned.size > MAX_RETURNED_CALLS) {
this.returned.delete(this.returned.values().next().value!);
}
}
const succeeded = results.filter((block) => block.is_error !== true);
const structuredTaskId =
succeeded.length === 1 &&
typeof toolUseResult === "object" &&
toolUseResult !== null &&
typeof (toolUseResult as { taskId?: unknown }).taskId === "string"
? (toolUseResult as { taskId: string }).taskId
: undefined;
for (const { tool_use_id: toolCallId } of succeeded) {
const toolUse = toolUses[toolCallId];
if (!toolUse) continue;
const taskId =
(TASK_TOOLS.has(toolUse.name) ? structuredTaskId : undefined) ??
[...owner.liveBackgroundTasks].find(
([, task]) => task.parentToolUseId === toolCallId && !task.isSubagent,
)?.[0];
const task = taskId === undefined ? undefined : this.level.get(taskId);
if (
!task ||
task.ambient ||
task.taskType === "local_bash" ||
task.taskType === "local_agent"
) {
continue;
}
this.toolCalls.set(taskId!, {
toolCallId,
toolUse,
...(parentToolUseId ? { parentToolUseId } : {}),
});
registered.add(toolCallId);
}
return registered;
}
/**
* Starts reading the output of the background command `taskId` into its
* terminal, when the client gets the lists and the Bash result named the
* command's output file. `send` sends an update of the terminal's tool
* call, routed as the call's other updates. Called once the Bash result's
* own updates are sent: the first output replaces the placeholder that they
* gave the terminal.
*/
followOutput(taskId: string, send: Send): void {
const terminal = this.terminals.get(taskId);
if (!this.reported || !terminal?.outputFilePath || terminal.output) return;
terminal.output = new BackgroundOutputTail(terminal.outputFilePath, (output) =>
send(terminalOutputUpdate(this.sessionId, terminal, output)),
);
}
/**
* The task `taskId` ended. Reads the rest of its command's output, then
* exits its terminal, and gives a tool call whose task it was its final
* status: `cancelled` for a task that Claude Code stopped, and `completed`
* for one that ended without saying how, such as one that the level
* dropped: it no longer runs, and reported no failure. Claude Code reports
* no exit code for a command, and `completed` does not mean 0: Claude Code
* accepts some non-zero codes as a success, such as grep's 1.
*/
async taskEnded(taskId: string, end: BackgroundTaskEnd, send: Send): Promise<void> {
const terminal = this.terminals.get(taskId);
if (terminal) {
await terminal.output?.finish();
this.terminals.delete(taskId);
await send(terminalExit(this.sessionId, terminal));
}
const toolCall = this.toolCalls.get(taskId);
if (toolCall) {
this.toolCalls.delete(taskId);
await send(
toolCallStatusUpdate(
this.sessionId,
toolCall,
end === "stopped" ? V2_CANCELLED : end === "failed" ? "failed" : "completed",
),
);
}
}
/**
* The update that reports the background tool call of the task `taskId` as
* `paused`, or as `in_progress` again, when `paused` changes it. Claude Code
* pauses a workflow this way.
*/
taskPaused(taskId: string, paused: boolean): AcpSessionNotification | undefined {
const toolCall = this.toolCalls.get(taskId);
if (!toolCall || (toolCall.paused ?? false) === paused) return undefined;
toolCall.paused = paused;
return toolCallStatusUpdate(this.sessionId, toolCall, paused ? V2_PAUSED : "in_progress");
}
/**
* Takes Claude Code's new live background task set. The terminals and tool
* calls whose tasks it no longer holds end first, before a list drops them,
* as v2 requires. A terminal or tool call is registered at its tool result,
* after its task joined the level, so a later level without the task means
* that it ended. Claude Code usually sends a task's end before that level,
* which ends them first, but the SDK does not promise that order.
*/
async levelChanged(tasks: LevelTask[], send: Send): Promise<void> {
if (!this.reported) return;
const level: BackgroundLevel = new Map(
tasks.map((task) => [
task.task_id,
{
ambient: task.ambient === true,
taskType: task.task_type,
description: task.description,
...(task.parent_task_id !== undefined ? { parentTaskId: task.parent_task_id } : {}),
},
]),
);
for (const taskId of [...this.terminals.keys(), ...this.toolCalls.keys()]) {
if (!level.has(taskId)) await this.taskEnded(taskId, undefined, send);
}
this.level = level;
}
/**
* The query closed: Claude Code's background tasks end with its process.
* Stops reading output, and returns the updates that exit the root
* session's terminals and cancel its tool calls. The level empties, so the
* next lists are empty. A subagent's command or tool call is left as it
* was: it is on the child's stream, which the closed query's routing owned.
*/
queryClosed(): AcpSessionNotification[] {
for (const terminal of this.terminals.values()) terminal.output?.stop();
if (!this.reported) return [];
const updates: AcpSessionNotification[] = [];
for (const [taskId, terminal] of this.terminals) {
if (terminal.parentToolUseId) continue;
this.terminals.delete(taskId);
updates.push(terminalExit(this.sessionId, terminal));
}
for (const [taskId, toolCall] of this.toolCalls) {
if (toolCall.parentToolUseId) continue;
this.toolCalls.delete(taskId);
updates.push(toolCallStatusUpdate(this.sessionId, toolCall, V2_CANCELLED));
}
this.level = new Map();
this.returned.clear();
return updates;
}
/**
* The `background` list of the root session and of each of its announced
* subagent sessions, with what their repeated states need, deeper sessions
* first and the root last: a child's own list goes out before its parent's
* list drops it. Each list is empty while the level has not been reported:
* Claude Code sends none before the first background task starts.
*/
reports(owner: BackgroundOwner, rootState: WorkStateName): BackgroundReport[] {
const children = [...(owner.nativeSubagentsByTaskId ?? [])].filter(
([, child]) => child.announced,
);
const childByTask = new Map(children);
const childByToolUse = new Map(
children.flatMap(([, child]) =>
child.parentToolUseId ? [[child.parentToolUseId, child] as const] : [],
),
);
const lists = new Map<string, BackgroundItem[]>([
[this.sessionId, []],
...children.map(([, child]): [string, BackgroundItem[]] => [child.sessionId, []]),
]);
// A subagent's command or tool call runs on the child's stream.
const ownerOf = (parentToolUseId: string | undefined) =>
parentToolUseId ? childByToolUse.get(parentToolUseId)?.sessionId : this.sessionId;
for (const [taskId, task] of this.level) {
if (task.ambient) continue;
const terminal = this.terminals.get(taskId);
if (terminal) {
const session = ownerOf(terminal.parentToolUseId);
if (session)
lists.get(session)!.push({ type: "terminal", terminalId: terminal.toolCallId });
continue;
}
const toolCall = this.toolCalls.get(taskId);
if (toolCall) {
const session = ownerOf(toolCall.parentToolUseId);
if (session)
lists.get(session)!.push({ type: "tool_call", toolCallId: toolCall.toolCallId });
continue;
}
// A command without a terminal yet is one that foreground work waits on.
// A subagent is listed below, as its session.
if (task.taskType === "local_bash" || task.taskType === "local_agent") continue;
// A task joins the level before its `task_started`, and before the tool
// call that starts it returns. Until both, the task is unknown or part
// of that tool call.
const live = owner.liveBackgroundTasks.get(taskId);
if (!live || (live.parentToolUseId && !this.returned.has(live.parentToolUseId))) continue;
const parent = task.parentTaskId ? childByTask.get(task.parentTaskId) : undefined;
lists
.get(parent?.sessionId ?? this.sessionId)!
.push({ type: "_claude/task", taskId, description: task.description });
}
// A child is listed in its parent's list while it works in the background,
// and while it is idle with background work of its own. Deeper children
// first, so a child's list is whole before its parent's reads it.
const sessions = new Map(children.map(([, child]) => [child.sessionId, child]));
const depth = (child: NativeSubagent): number => {
const parent = sessions.get(child.parentSessionId);
return parent ? depth(parent) + 1 : 0;
};
const ordered = children.sort(([, a], [, b]) => depth(b) - depth(a));
for (const [taskId, child] of ordered) {
const task = this.level.get(taskId);
const working =
child.workState !== undefined &&
child.workState !== "idle" &&
task !== undefined &&
!task.ambient;
if (working || lists.get(child.sessionId)!.length > 0) {
lists.get(child.parentSessionId)?.push({ type: "subagent", sessionId: child.sessionId });
}
}
return [
...ordered.map(([, child]) => ({
sessionId: child.sessionId,
items: lists.get(child.sessionId)!,
state: workStateName(child.workState),
parentSessionId: child.parentSessionId,
})),
{ sessionId: this.sessionId, items: lists.get(this.sessionId)!, state: rootState },
];
}
/**
* The current state of each background tool call in `toolCallIds`: the
* call as `renderer` shows its tool use, with its status now (see
* `ClaudeAcpAgent.publishBackgroundToolCalls`).
*/
toolCallStates(
toolCallIds: string[],
renderer: AcpToolCallRenderer,
cwd: string,
): AcpSessionNotification[] {
return [...this.toolCalls.values()]
.filter((toolCall) => toolCallIds.includes(toolCall.toolCallId))
.map((toolCall) => {
const update = renderer.toolCall({ id: toolCall.toolCallId, ...toolCall.toolUse }, { cwd });
const status = toolCall.paused ? V2_PAUSED : "in_progress";
return {
sessionId: this.sessionId,
update: { ...update, status } as AcpSessionNotification["update"],
};
});
}
/**
* The task of the terminal or tool call `target` that the session
* `sessionId` lists, or undefined when it lists no such item.
*/
listedTask(
owner: BackgroundOwner,
sessionId: string,
target: BackgroundTarget,
): string | undefined {
if (!this.reported) return undefined;
const listed = this.reports(owner, "idle")
.find((report) => report.sessionId === sessionId)
?.items.some((item) =>
"terminalId" in target
? item.type === "terminal" && item.terminalId === target.terminalId
: item.type === "tool_call" && item.toolCallId === target.toolCallId,
);
if (!listed) return undefined;
const id = "terminalId" in target ? target.terminalId : target.toolCallId;
const entries = "terminalId" in target ? this.terminals : this.toolCalls;
return [...entries].find(([, entry]) => entry.toolCallId === id)?.[0];
}
}
type Send = (notification: AcpSessionNotification) => Promise<void>;
/** A terminal or tool call that `terminal/cancel` or `tool_call/cancel` names. */
export type BackgroundTarget = { terminalId: string } | { toolCallId: string };
/**
* Stops the task of a terminal or tool call that the session `sessionId`
* lists (v2 `terminal/cancel` and `tool_call/cancel`). `sessions` are the
* root sessions: a subagent session's list is built with its root's. The
* terminal exits, or the tool call gets its final status, when Claude Code
* reports the task stopped.
*/
export async function cancelBackgroundWork(
sessions: Array<BackgroundOwner & { background?: SessionBackgroundWork }>,
sessionId: string,
target: BackgroundTarget,
): Promise<void> {
const notCancellable = () =>
"terminalId" in target
? RequestError.invalidParams(
{ sessionId, terminalId: target.terminalId },
`Terminal ${target.terminalId} is not a command running in the background, so it cannot be cancelled.`,
)
: RequestError.invalidParams(
{ sessionId, toolCallId: target.toolCallId },
`Tool call ${target.toolCallId} is not running in the background, so it cannot be cancelled.`,
);
for (const session of sessions) {
const taskId = session.background?.listedTask(session, sessionId, target);
if (taskId === undefined) continue;
try {
await session.query.stopTask(taskId);
} catch (error) {
// The task ended meanwhile: it has ended on the client too, or is about to.
if (session.background?.listedTask(session, sessionId, target) === undefined) {
throw notCancellable();
}
throw error;
}
return;
}
throw notCancellable();
}
/** The update that gives the terminal of `terminal` the text `output`. */
function terminalOutputUpdate(
sessionId: string,
terminal: BackgroundTerminal,
output: BackgroundOutput,
): AcpSessionNotification {
const data = { terminal_id: terminal.toolCallId, data: output.text };
return {
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: terminal.toolCallId,
_meta: {
...(terminal.parentToolUseId
? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
: {}),
...(output.replaces ? { terminal_output: data } : { terminal_output_delta: data }),
},
},
};
}
/** The update that exits the terminal of `terminal`, with no exit code. */
function terminalExit(sessionId: string, terminal: BackgroundTerminal): AcpSessionNotification {
return {
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: terminal.toolCallId,
_meta: {
...(terminal.parentToolUseId
? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
: {}),
terminal_exit: { terminal_id: terminal.toolCallId, exit_code: null, signal: null },
},
},
};
}
function toolCallStatusUpdate(
sessionId: string,
toolCall: BackgroundToolCall,
status: NonNullable<
Extract<AcpSessionNotification["update"], { sessionUpdate: "tool_call_update" }>["status"]
>,
): AcpSessionNotification {
return {
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: toolCall.toolCallId,
status,
...(toolCall.parentToolUseId
? { _meta: { claudeCode: { parentToolUseId: toolCall.parentToolUseId } } }
: {}),
},
};
}
/**
* Publishes the lists of each session that reports them, each time one
* changes. The sink sends them to the client synchronously, so a list follows
* the updates sent before it.
*/
export class BackgroundWorkReporter {
/** The list last published for each session, as JSON, and the root session it is built with. */
private readonly published = new Map<string, { json: string; root: string }>();
constructor(private readonly sink?: BackgroundSink) {}
/** Whether the lists are reported at all. */
get enabled(): boolean {
return this.sink !== undefined;
}
/**
* Publishes each list of the root session `rootSessionId` that changed. The
* first call for a session publishes its list, also when it is empty.
* `rootState` is the root session's state now.
*/
sync(
rootSessionId: string,
session: BackgroundOwner & { background?: SessionBackgroundWork },
rootState: WorkStateName,
): void {
if (!this.sink || !session.background?.reported) return;
for (const report of session.background.reports(session, rootState)) {
const json = JSON.stringify(report.items);
if (this.published.get(report.sessionId)?.json === json) continue;
this.published.set(report.sessionId, { json, root: rootSessionId });
this.sink.changed(report);
}
}
/** The root session `rootSessionId` ended, and its subagent sessions with it. */
forget(rootSessionId: string): void {
for (const [sessionId, { root }] of this.published) {
if (root !== rootSessionId) continue;
this.published.delete(sessionId);
this.sink?.forget(sessionId);
}
}
}
/** A child's work state, when it is one that v2 names. */
function workStateName(state: string | undefined): WorkStateName | undefined {
return state === "idle" ||
state === "running" ||
state === "requires_action" ||
state === "unknown"
? state
: undefined;
}

125
src/blob-resources.ts Normal file
View File

@@ -0,0 +1,125 @@
/**
* Embedded `resource` prompt blocks whose resource is a base64 `blob`, and the
* Claude content each supported MIME type becomes.
*/
import type { BlobResourceContents } from "@agentclientprotocol/sdk/unstable";
/** Image types the Messages API accepts as a base64 `image` block. */
const IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"] as const;
type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];
/** Non-`text/*` types whose bytes are text, e.g. a `.json` or `.ts` file. */
const TEXT_APPLICATION_MIME_TYPES = new Set([
"application/json",
"application/xml",
"application/javascript",
"application/typescript",
"application/x-yaml",
"application/yaml",
"application/toml",
"application/x-sh",
"application/sql",
"application/graphql",
]);
export type BlobResourceContent =
| {
kind: "image";
block: {
type: "image";
source: { type: "base64"; media_type: ImageMimeType; data: string };
};
}
| {
kind: "document";
block: {
type: "document";
source: { type: "base64"; media_type: "application/pdf"; data: string };
};
}
/** The decoded text, to send like a text resource. */
| { kind: "text"; text: string };
/**
* The MIME type without parameters or case, e.g. `text/plain; charset=utf-8` ->
* `text/plain`. `image/jpg`, which some clients send, becomes `image/jpeg`.
*/
function baseMimeType(mimeType: string | null | undefined): string | undefined {
const base = mimeType?.split(";")[0].trim().toLowerCase();
if (base === "image/jpg") return "image/jpeg";
return base || undefined;
}
/**
* Decodes a text blob with the `charset` parameter of its MIME type, e.g.
* `text/csv; charset=iso-8859-1`, and UTF-8 when there is none or the label is
* unknown. A leading byte order mark is dropped.
*/
function decodeText(blob: string, mimeType: string | null | undefined): string {
const charset = /;\s*charset\s*=\s*"?([^";\s]+)/i.exec(mimeType ?? "")?.[1];
let decoder: TextDecoder;
try {
decoder = new TextDecoder(charset ?? "utf-8");
} catch {
decoder = new TextDecoder("utf-8");
}
return decoder.decode(Buffer.from(blob, "base64"));
}
function isImageMimeType(mimeType: string): mimeType is ImageMimeType {
return (IMAGE_MIME_TYPES as readonly string[]).includes(mimeType);
}
function isTextMimeType(mimeType: string): boolean {
return (
mimeType.startsWith("text/") ||
TEXT_APPLICATION_MIME_TYPES.has(mimeType) ||
mimeType.endsWith("+json") ||
mimeType.endsWith("+xml")
);
}
/**
* Whether {@link blobResourceToClaude} can send a blob of this MIME type. A blob
* without a MIME type is not supported: its bytes could be anything.
*/
export function isSupportedBlobMimeType(mimeType: string | null | undefined): boolean {
const base = baseMimeType(mimeType);
if (!base) return false;
return isImageMimeType(base) || base === "application/pdf" || isTextMimeType(base);
}
/**
* The Claude content for a blob resource: an `image` block for PNG, JPEG, GIF
* and WebP, a `document` block for PDF, and the decoded text for text
* types such as `text/markdown` or `application/json`. Returns `undefined` for
* any other type, e.g. `application/zip`.
*/
export function blobResourceToClaude(
resource: BlobResourceContents,
): BlobResourceContent | undefined {
const mimeType = baseMimeType(resource.mimeType);
if (!mimeType) return undefined;
if (isImageMimeType(mimeType)) {
return {
kind: "image",
block: {
type: "image",
source: { type: "base64", media_type: mimeType, data: resource.blob },
},
};
}
if (mimeType === "application/pdf") {
return {
kind: "document",
block: {
type: "document",
source: { type: "base64", media_type: "application/pdf", data: resource.blob },
},
};
}
if (isTextMimeType(mimeType)) {
return { kind: "text", text: decodeText(resource.blob, resource.mimeType) };
}
return undefined;
}

View File

@@ -1,4 +1,4 @@
import type { NewSessionRequest, SessionConfigOption } from "@agentclientprotocol/sdk";
import type { NewSessionRequest, SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
import type {
EffortLevel,
Options,
@@ -39,11 +39,14 @@ export type ClearContextSession<Turn extends ClearContextTurn = ClearContextTurn
configOptions: SessionConfigOption[];
currentAgent: string;
fastModeEnabled: boolean;
ultracodeEnabled: boolean;
effortPinnedLevel?: string;
activeTurn?: Turn | null;
turnQueue?: Turn[];
pendingExitPlanContextReset?: ClearContextReset;
contextUsedTokens?: number;
contextWindowSize: number;
sentUserUuids?: Set<string>;
input: { push(message: SDKUserMessage): unknown };
};
@@ -58,10 +61,14 @@ export type ClearContextCoordinatorHost<
options: { publicSessionId: string; permissionMode: PermissionMode },
): Promise<Session>;
applyFastMode(session: Session, enabled: boolean): Promise<void>;
applyUltracode(session: Session, enabled: boolean): Promise<void>;
/** Publishes the fresh context's mode, config options, and usage: the
* context starts empty in a window of `contextWindowSize` tokens. */
publishSessionState(
sessionId: string,
mode: PermissionMode,
configOptions: SessionConfigOption[],
contextWindowSize: number,
): Promise<void>;
continuationMessage(sessionId: string, plan: string, promptUuid: string): SDKUserMessage;
ensureConsumer(session: Session, sessionId: string): void;
@@ -145,6 +152,16 @@ export async function continuePlanInFreshContext<
}
assertRestartActive(freshSession);
}
// The fresh query seeds ultracode from settings only, so a toggle made
// during the old session would otherwise be lost.
if (oldSession.ultracodeEnabled !== freshSession.ultracodeEnabled) {
try {
await host.applyUltracode(freshSession, oldSession.ultracodeEnabled);
} catch (error) {
host.logError("Failed to restore ultracode after clearing context:", error);
}
assertRestartActive(freshSession);
}
oldSession.activeTurn = null;
oldSession.turnQueue = [];
@@ -152,12 +169,18 @@ export async function continuePlanInFreshContext<
freshSession.contextUsedTokens = 0;
try {
await host.publishSessionState(sessionId, reset.mode, freshSession.configOptions);
await host.publishSessionState(
sessionId,
reset.mode,
freshSession.configOptions,
freshSession.contextWindowSize,
);
} catch (error) {
host.logError("Failed to publish clear-context session state:", error);
}
assertRestartActive(freshSession);
(freshSession.sentUserUuids ??= new Set()).add(turn.promptUuid);
freshSession.input.push(host.continuationMessage(sessionId, reset.plan, turn.promptUuid));
assertRestartActive(freshSession);
host.ensureConsumer(freshSession, sessionId);

View File

@@ -18,7 +18,7 @@ import {
RequestError,
type SessionCapabilities,
type SystemPrompt,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
/** The `sessionCapabilities.systemPrompt` the agent advertises: both modes. */
export const SYSTEM_PROMPT_CAPABILITY = {

View File

@@ -1,4 +1,4 @@
import { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
import { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
import {
ContextCompactionMetadata,
createContextCompactionMeta,

View File

@@ -1,5 +1,5 @@
import { ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk";
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
import { ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk/unstable";
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
import { structuredPatch } from "diff";
import { constants } from "node:fs";
import { type FileHandle, open } from "node:fs/promises";
@@ -89,7 +89,7 @@ export type PatchForm = "air" | "v2";
/**
* How a git patch names its file. `git` is git's own form: the leading slash
* dropped and the `a/` and `b/` prefixes added. `absolute` is the form of an
* ACP v2 `git_patch`, whose paths must be absolute: the path itself.
* ACP v2 `git_patch`, whose paths must be absolute: the native path itself.
*/
type GitPatchPaths = "git" | "absolute";
@@ -438,11 +438,11 @@ export function gitPatchText(
}
function gitPatchHeader(filePath: string, change: FileChange, paths: GitPatchPaths): string[] {
// A Windows path gets forward slashes so the header names the same file on
// every platform.
const slashed = filePath.replaceAll("\\", "/");
// git drops one leading slash of an absolute path.
const name = paths === "git" ? slashed.replace(/^\/+/u, "") : slashed;
// git's form uses forward slashes, so the header names the same file on
// every platform, and drops one leading slash of an absolute path. An ACP v2
// patch keeps the native path so that, once unquoted, it equals the `changes`
// entry: `C:\work\App.ts` is written `"C:\\work\\App.ts"`.
const name = paths === "git" ? filePath.replaceAll("\\", "/").replace(/^\/+/u, "") : filePath;
const oldName = quoteGitPath(paths === "git" ? "a/" : "", name);
const newName = quoteGitPath(paths === "git" ? "b/" : "", name);
// git ends a ---/+++ name that contains a space with a tab, for GNU patch.

View File

@@ -1,12 +1,11 @@
import { randomUUID } from "node:crypto";
import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
import type {
CreateElicitationRequest,
ElicitationAcceptAction,
CreateElicitationResponse,
ElicitationPropertySchema,
ElicitationSchema,
EnumOption,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
import type {
AskUserQuestionInput,
@@ -75,17 +74,22 @@ export function mcpElicitationToCreateRequest(
}
/**
* Content of an accepted elicitation response.
* Content of an accepted elicitation response, or `{}` when the response is
* no accept or its `content` is no JSON object.
*
* Uses the SDK's validating guard rather than an `action === "accept"` check:
* the guard both narrows past the union's custom/future variant and validates
* the payload, so a malformed accept (right tag, ill-typed content) yields
* empty content — the same classification the SDK's wire validators apply.
* The values are not checked. v1 allows a string, number, boolean, or string
* array; v2 also allows any other JSON value, such as an object. An MCP
* server checks the content against its own `requestedSchema`, so it gets the
* content as the client sent it: before, an accept whose content held an
* object reached the server as `{ action: "accept", content: {} }`. Callers
* that read a field must check its type.
*/
function acceptedElicitationContent(
response: CreateElicitationResponse,
): NonNullable<ElicitationAcceptAction["content"]> {
return CreateElicitationResponse.isAccept(response) ? (response.content ?? {}) : {};
function acceptedElicitationContent(response: CreateElicitationResponse): Record<string, unknown> {
if (response.action !== "accept") return {};
const content: unknown = (response as { content?: unknown }).content;
return content !== null && typeof content === "object" && !Array.isArray(content)
? (content as Record<string, unknown>)
: {};
}
/**
@@ -97,7 +101,11 @@ export function createElicitationResponseToElicitResult(
): ElicitationResult {
switch (response.action) {
case "accept":
return { action: "accept", content: acceptedElicitationContent(response) };
// `ElicitResult` types the values as v1 does. The MCP server checks them.
return {
action: "accept",
content: acceptedElicitationContent(response) as ElicitationResult["content"],
};
case "decline":
return { action: "decline" };
case "cancel":
@@ -280,14 +288,37 @@ function joinMultiSelectAnswer(items: string[]): string {
* cannot make the selection disappear. Decline yields empty answers (the model
* is told the user skipped rather than the turn aborting); cancel — and any
* custom/future action we don't understand — aborts the tool call.
*
* With `reportUnanswered` set (the session's `system/init` listed
* `ask_user_question_unanswered_v1`), each question left without an answer
* also gets `annotations[question].unanswered`: `"dismissed"` for every
* question when the user declined the form, `"skipped"` for a question left
* blank in an accepted form. The model does not see this field; the tool
* result carries it for clients and transcripts. A CLI without the capability
* can refuse the whole answer over it, so it is left out there.
*/
export function applyAskElicitationResponse(
response: CreateElicitationResponse,
toolInput: Record<string, unknown>,
questions: AskUserQuestion[],
options: { reportUnanswered?: boolean } = {},
): AskUserQuestionOutcome {
// Typed against the output schema: as of SDK 0.3.316 only it lists
// `unanswered`, though the tool reads the same annotations from its input.
const annotations: NonNullable<AskUserQuestionOutput["annotations"]> = {};
const withAnnotations = (answers: AskUserQuestionOutput["answers"]) => ({
...toolInput,
answers,
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
});
if (response.action === "decline") {
return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
if (options.reportUnanswered) {
for (const question of questions) {
annotations[question.question] = { unanswered: "dismissed" };
}
}
return { action: "answered", updatedInput: withAnnotations({}) };
}
if (response.action !== "accept") {
@@ -298,18 +329,17 @@ export function applyAskElicitationResponse(
// Typed against the tool's own output schema so the answer/response shapes
// stay in sync with what the built-in tool's call() expects to read back.
const answers: AskUserQuestionOutput["answers"] = {};
const annotations: NonNullable<AskUserQuestionInput["annotations"]> = {};
questions.forEach((question, index) => {
const custom = content[questionCustomFieldKey(index)];
const customText = typeof custom === "string" ? custom.trim() : "";
// A pick is a string, number, or boolean. Other values, such as an object
// that a v2 client may send, are dropped instead of read as "[object Object]".
const value = content[questionFieldKey(index)];
const picks: string[] =
value === undefined || value === null
? []
: Array.isArray(value)
? value.filter((item) => item !== undefined && item !== null && item !== "").map(String)
: [String(value)];
const picks: string[] = (Array.isArray(value) ? value : [value])
.filter(isElicitationScalar)
.map(String)
.filter((item) => item !== "");
// A multi-select is additive, and the form offers the selection and the
// custom box as independent fields — a user who fills both means both, so
@@ -341,20 +371,27 @@ export function applyAskElicitationResponse(
}
});
return {
action: "answered",
updatedInput: {
...toolInput,
answers,
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
},
};
if (options.reportUnanswered) {
for (const question of questions) {
if (answers[question.question] === undefined) {
annotations[question.question] = { unanswered: "skipped" };
}
}
}
return { action: "answered", updatedInput: withAnnotations(answers) };
}
function isElicitationScalar(value: unknown): value is string | number | boolean {
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
/**
* Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
* The two are structurally compatible JSON Schemas; we just guarantee the
* `type: "object"` discriminator is present.
* `type: "object"` discriminator and a `properties` map are present. The v2
* schema requires both, so a client on SDK 2.2.0 or later rejects an MCP
* schema such as `{ type: "object" }` that has no `properties`.
*/
function normalizeElicitationSchema(
schema: Record<string, unknown> | undefined,
@@ -362,7 +399,8 @@ function normalizeElicitationSchema(
if (!schema || typeof schema !== "object") {
return { type: "object", properties: {} };
}
return { ...(schema as ElicitationSchema), type: "object" };
const { properties } = schema as Partial<ElicitationSchema>;
return { ...(schema as ElicitationSchema), type: "object", properties: properties ?? {} };
}
/**

View File

@@ -1,4 +1,4 @@
import type { SessionNotification } from "@agentclientprotocol/sdk";
import type { SessionNotification } from "@agentclientprotocol/sdk/unstable";
import type { SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
import {
type ClearContextCoordinatorHost,
@@ -126,10 +126,9 @@ export function isExitPlanInterruptionResult(
);
}
/** The id of the plan that an approved ExitPlanMode publishes. Claude Code
* keeps one plan file per session, which a later plan mode rewrites, so each
* approval replaces the plan before it. The TodoWrite checklist is a
* separate plan. */
/** The base ID of an approved plan. AIR appends the review ID to keep each
* approval in the transcript. Other clients replace one plan per session.
* The TodoWrite checklist is a separate plan. */
export const PLAN_MODE_PLAN_ID = "plan-mode";
/** The plan, trimmed, that a successful ExitPlanMode result approved: the plan
@@ -150,11 +149,19 @@ export function approvedPlan(input: unknown, output: unknown): string | undefine
}
/** The update that shows an approved plan to a client that supports plan
* operations (the `plan` client capability, which every v2 client has). */
export function planModeUpdate(plan: string): SessionNotification["update"] {
* operations (the `plan` client capability, which every v2 client has).
* For AIR, pass the review ID to join the plan to its approval card. */
export function planModeUpdate(
plan: string,
reviewToolCallId?: string,
): SessionNotification["update"] {
return {
sessionUpdate: "plan_update",
plan: { type: "markdown", planId: PLAN_MODE_PLAN_ID, content: plan },
plan: {
type: "markdown",
planId: reviewToolCallId ? `${PLAN_MODE_PLAN_ID}:${reviewToolCallId}` : PLAN_MODE_PLAN_ID,
content: plan,
},
};
}
@@ -178,6 +185,7 @@ export type ExitPlanRestartHost<
sessionUpdate(notification: SessionNotification): Promise<void>;
/** Whether the client takes plan operations (see planModeUpdate). */
supportsPlanOperations(): boolean;
isAirClient(): boolean;
destroyReplacement(sessionId: string, session: Session): void;
settleCancelledTurn(oldSession: Session, turnSession: Session, turn: Turn): void;
settleFailedTurn(turnSession: Session, turn: Turn, error: unknown): void;
@@ -205,7 +213,7 @@ export class ExitPlanCoordinator<
try {
const clearContextHost: ClearContextCoordinatorHost<Session, Turn> = {
...this.host,
publishSessionState: async (id, mode, configOptions) => {
publishSessionState: async (id, mode, configOptions, contextWindowSize) => {
await this.host.sessionUpdate({
sessionId: id,
update: { sessionUpdate: "current_mode_update", currentModeId: mode },
@@ -214,11 +222,23 @@ export class ExitPlanCoordinator<
sessionId: id,
update: { sessionUpdate: "config_option_update", configOptions },
});
// Otherwise the client shows the old context's usage until the fresh
// context first reports its own.
await this.host.sessionUpdate({
sessionId: id,
update: { sessionUpdate: "usage_update", used: 0, size: contextWindowSize },
});
// The approved plan becomes the session's plan once the fresh
// context that carries it out exists. Its ExitPlanMode ended with
// an error result, so toAcpNotifications did not publish it.
if (this.host.supportsPlanOperations()) {
await this.host.sessionUpdate({ sessionId: id, update: planModeUpdate(reset.plan) });
await this.host.sessionUpdate({
sessionId: id,
update: planModeUpdate(
reset.plan,
this.host.isAirClient() ? reset.toolUseId : undefined,
),
});
}
},
continuationMessage: (id, plan, promptUuid) => ({

View File

@@ -1,4 +1,8 @@
import { ForkSessionRequest, ForkSessionResponse, RequestError } from "@agentclientprotocol/sdk";
import {
ForkSessionRequest,
ForkSessionResponse,
RequestError,
} from "@agentclientprotocol/sdk/unstable";
import {
forkSession as forkClaudeSession,
getSessionMessages,
@@ -65,10 +69,7 @@ function forkPointMessageIdCandidates(messageId: string): string[] {
return protocolMessageId === messageId ? [messageId] : [messageId, protocolMessageId];
}
async function loadFullSessionHistory(
sessionId: string,
cwd: string,
): Promise<SessionStoreEntry[]> {
async function loadFullSessionHistory(sessionId: string): Promise<SessionStoreEntry[]> {
// getSessionMessages returns only the active parentUuid chain. The import keeps inactive branches that AIR can still reference.
const entries: SessionStoreEntry[] = [];
const store: SessionStore = {
@@ -77,7 +78,7 @@ async function loadFullSessionHistory(
},
load: async () => null,
};
await importSessionToStore(sessionId, store, { dir: cwd, includeSubagents: false });
await importSessionToStore(sessionId, store, { includeSubagents: false });
return entries;
}
@@ -175,14 +176,44 @@ function resolveFromFullHistory(
return occurrenceMatches.length === 1 ? occurrenceMatches[0]?.uuid : undefined;
}
/**
* Whether `error` is the Claude SDK's report that `sessionId` has no
* transcript: `forkSession` and `importSessionToStore` throw
* `Error("Session <id> not found")`, and both throw
* `"Invalid sessionId: <id>"` for an id that is not a UUID.
*/
function isUnknownSessionError(error: unknown, sessionId: string): boolean {
if (!(error instanceof Error)) return false;
return (
error.message === `Session ${sessionId} not found` ||
error.message === `Invalid sessionId: ${sessionId}`
);
}
export async function forkSession(
params: ForkSessionRequest,
dependencies: ForkSessionDependencies,
): Promise<ForkSessionResponse> {
try {
return await forkTranscript(params, dependencies);
} catch (error) {
if (isUnknownSessionError(error, params.sessionId)) {
throw RequestError.sessionNotFound(params.sessionId);
}
throw error;
}
}
async function forkTranscript(
params: ForkSessionRequest,
dependencies: ForkSessionDependencies,
): Promise<ForkSessionResponse> {
const timing = new SessionTiming(dependencies.logger, "fork", params.sessionId);
const point = forkPoint(params._meta);
// SDK `dir` scopes source lookup, not the fork's working directory. Search
// all projects: params.cwd can name a different workspace for the new session.
if (!point) {
const forked = await forkClaudeSession(params.sessionId, { dir: params.cwd });
const forked = await forkClaudeSession(params.sessionId);
timing.phase("sdk-fork", " resolution=latest");
return { sessionId: forked.sessionId };
}
@@ -191,9 +222,7 @@ export async function forkSession(
const liveUuid = candidateIds
.map((candidateId) => dependencies.liveMessageIdToUuid?.get(candidateId))
.find(Boolean);
const history = liveUuid
? undefined
: await getSessionMessages(params.sessionId, { dir: params.cwd });
const history = liveUuid ? undefined : await getSessionMessages(params.sessionId);
timing.phase("active-history", ` resolution=${liveUuid ? "live" : "pending"}`);
const messageUuid =
liveUuid ??
@@ -209,7 +238,7 @@ export async function forkSession(
const fullHistoryUuid = messageUuid
? undefined
: resolveFromFullHistory(
await loadFullSessionHistory(params.sessionId, params.cwd),
await loadFullSessionHistory(params.sessionId),
candidateIds,
point,
dependencies.messageIdForGrouping,
@@ -227,7 +256,6 @@ export async function forkSession(
}
const forked = await forkClaudeSession(params.sessionId, {
dir: params.cwd,
upToMessageId: messageUuid ?? fullHistoryUuid,
});
timing.phase("sdk-fork");

View File

@@ -1,4 +1,4 @@
import { RequestError } from "@agentclientprotocol/sdk";
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { SDKActiveGoalMessage } from "@anthropic-ai/claude-agent-sdk";
export const GOAL_EXTENSION_VERSION = 1 as const;

View File

@@ -32,7 +32,7 @@
* until the next prompt. Awaiting it would add a CLI run to every turn.
*/
import { RequestError } from "@agentclientprotocol/sdk";
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { AccountInfo, Query } from "@anthropic-ai/claude-agent-sdk";
export function shouldHideClaudeAuth(): boolean {

View File

@@ -2,7 +2,7 @@
import { claudeCliPath } from "./acp-agent.js";
import { applyManagedPolicyEnv } from "./managed-policy.js";
import { serveAcp } from "./serve.js";
import { acpProtocolsFromEnv, serveAcp } from "./serve.js";
import packageJson from "../package.json" with { type: "json" };
import { appendFileSync, mkdirSync } from "node:fs";
import { join } from "node:path";
@@ -83,9 +83,8 @@ if (process.argv.includes("--cli")) {
: undefined;
logger?.log("Claude ACP started");
// The draft ACP v2 is opt-in while it is a draft.
const acp = serveAcp(logger, {
experimentalV2: process.env.CLAUDE_AGENT_ACP_EXPERIMENTAL_V2 === "1",
protocols: acpProtocolsFromEnv(process.env.CLAUDE_AGENT_ACP_PROTOCOL),
});
async function shutdown() {

View File

@@ -1,4 +1,4 @@
import type { AvailableCommand } from "@agentclientprotocol/sdk";
import type { AvailableCommand } from "@agentclientprotocol/sdk/unstable";
import type { McpServerStatus, Query } from "@anthropic-ai/claude-agent-sdk";
import { escapeMarkdown } from "./usage-markdown.js";
import { raceTimeoutAndAbort } from "./utils.js";

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk/unstable";
import type {
PermissionMode,
PermissionResult,

View File

@@ -1,4 +1,4 @@
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import {
buildEditPermissionOptions,
buildGlobPermissionOptions,

View File

@@ -1,4 +1,4 @@
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import os from "node:os";
import path from "node:path";
import type { DurablePermissionChangeSet } from "../normalization.js";

View File

@@ -1,4 +1,4 @@
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import type { DurablePermissionChangeSet } from "../normalization.js";
export const PERMISSION_OPTION_ID = {
@@ -30,6 +30,9 @@ export interface PermissionOptionContext {
/** The mode the session left when it entered plan mode. */
prePlanMode?: string;
contextUsedPercent?: number;
/** False when no ACP turn can continue an accepted plan in a fresh
* context. Then an ExitPlanMode request offers no clear-context option. */
allowContextReset?: boolean;
}
export function allowOnce(name = "Yes"): PermissionOption {

View File

@@ -1,4 +1,4 @@
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import path from "node:path";
import type { DurablePermissionChangeSet } from "../normalization.js";
import { plainString, type PermissionOptionContext, withOptionalUpdate } from "./shared.js";

View File

@@ -1,4 +1,4 @@
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import {
allowOnce,
exactLocalAllowRule,
@@ -70,7 +70,7 @@ export function buildExitPlanModePermissionOptions(
: (["auto", "bypassPermissions"] as const)
).filter((mode) => modes.has(mode));
const preferredMode = elevatedModes[0] ?? "acceptEdits";
if (plainString(context.input.plan)) {
if (context.allowContextReset !== false && plainString(context.input.plan)) {
const usage =
context.contextUsedPercent === undefined ? "" : ` (${context.contextUsedPercent}% used)`;
options.push(

View File

@@ -1,4 +1,4 @@
import type { RequestPermissionRequest, ToolCallContent } from "@agentclientprotocol/sdk";
import type { RequestPermissionRequest, ToolCallContent } from "@agentclientprotocol/sdk/unstable";
import { AIR_PERMISSION_KEY, withAirMeta } from "../air-extension.js";
import { ClientCapabilities } from "../tool-calls/client-capabilities.js";
import { AcpToolCallRenderer } from "../tool-calls/renderer.js";

View File

@@ -1,5 +1,5 @@
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
import type { PermissionOption } from "@agentclientprotocol/sdk";
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk/unstable";
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
import type { PermissionMode, PermissionResult } from "@anthropic-ai/claude-agent-sdk";
import type { DurablePermissionChangeSet } from "./normalization.js";
import { applyClaudePermissionSelection, parseClaudePermissionSelection } from "./effects.js";

View File

@@ -1,19 +1,32 @@
import { ndJsonStream } from "@agentclientprotocol/sdk";
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
import { v1AgentApp, type ClaudeAcpAgent, type Logger } from "./acp-agent.js";
import { nodeToWebReadable, nodeToWebWritable } from "./utils.js";
import { v2AgentApp } from "./v2/agent.js";
/**
* Serves ACP on stdio.
*
* With `experimentalV2`, the connection goes through {@link acpProtocolRouter}.
* Without it, the adapter serves ACP v1 alone and never routes by protocol
* version, exactly as before.
* The ACP versions that the adapter serves: `v1` or `v2` alone, or both with
* `auto`, where the client's `initialize` picks the version.
*/
export type AcpProtocols = "auto" | "v1" | "v2";
/**
* Reads `CLAUDE_AGENT_ACP_PROTOCOL`: unset, empty, or `auto` serves both
* versions, and `v1` or `v2` serves that version alone. Throws on any other
* value, so that a typo such as `CLAUDE_AGENT_ACP_PROTOCOL=1` fails at startup
* instead of silently serving both.
*/
export function acpProtocolsFromEnv(value: string | undefined): AcpProtocols {
if (value === undefined || value === "") return "auto";
if (value === "auto" || value === "v1" || value === "v2") return value;
throw new Error(
`CLAUDE_AGENT_ACP_PROTOCOL must be "auto", "v1", or "v2", not ${JSON.stringify(value)}`,
);
}
/** Serves ACP on stdio through {@link acpProtocolRouter}. */
export function serveAcp(
logger: Logger | undefined,
options: { experimentalV2: boolean },
options: { protocols: AcpProtocols },
): { closed: Promise<void>; dispose(): Promise<void> } {
const output = nodeToWebWritable(process.stdout);
const input = nodeToWebReadable(process.stdin);
@@ -23,9 +36,9 @@ export function serveAcp(
const onAgent = (created: ClaudeAcpAgent) => {
agent = created;
};
const { closed } = options.experimentalV2
? acpProtocolRouter(logger, onAgent).connect(v2.ndJsonStream(output, input))
: v1AgentApp(logger, onAgent).connect(ndJsonStream(output, input));
const { closed } = acpProtocolRouter(logger, onAgent, options.protocols).connect(
v2.ndJsonStream(output, input),
);
return {
closed,
dispose: async () => {
@@ -36,16 +49,19 @@ export function serveAcp(
/**
* Routes a connection to the protocol version that the client negotiates in
* `initialize`: the experimental draft ACP v2 for a client that requests
* version 2 or later, and ACP v1 otherwise. `onAgent` receives the agent of
* the connection once the router has opened it.
* `initialize`. With `auto`, a client that requests version 2 or later gets
* the draft ACP v2, and every other client gets ACP v1. With `v1`, a v2
* client is negotiated down to v1. With `v2`, the router rejects the
* `initialize` of a v1 client as an unsupported protocol version. `onAgent`
* receives the agent of the connection once the router has opened it.
*/
export function acpProtocolRouter(
logger: Logger | undefined,
onAgent: (agent: ClaudeAcpAgent) => void,
protocols: AcpProtocols = "auto",
): v2.AgentProtocolRouter {
return v2
.agentProtocolRouter()
.withV1(v1AgentApp(logger, onAgent))
.withV2(v2AgentApp(logger, onAgent));
const router = v2.agentProtocolRouter();
if (protocols !== "v2") router.withV1(v1AgentApp(logger, onAgent));
if (protocols !== "v1") router.withV2(v2AgentApp(logger, onAgent));
return router;
}

View File

@@ -2,3 +2,4 @@
export const DEFAULT_MODEL_ID = "default";
export const DEFAULT_AGENT_ID = "default";
export const EFFORT_CONFIG_ID = "effort";
export const ULTRACODE_CONFIG_ID = "ultracode";

View File

@@ -1,4 +1,4 @@
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
import type { EffortLevel, ModelInfo, Settings } from "@anthropic-ai/claude-agent-sdk";
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
import { EFFORT_CONFIG_ID } from "./session-config-ids.js";
@@ -14,14 +14,15 @@ export function toSdkEffortLevel(value: string | undefined): EffortLevel | null
// Since CLI 2.1.284 an `effortLevel` that changes the level, sent without an
// `ultracode` key, turns ultracode off. The CLI's own effort control keeps
// ultracode on at any level, so carry a settings-requested ultracode along
// with every effort apply.
// ultracode on at any level, so send `ultracode: true` with every effort apply
// while the session has ultracode on. When it is off, the key is omitted: an
// effort change then leaves ultracode off, as it already was.
export function effortFlagSettings(
value: string | undefined,
settings: Settings,
ultracode: boolean,
): { effortLevel: EffortLevel | null; ultracode?: true } {
const effortLevel = toSdkEffortLevel(value);
return settings.ultracode === true ? { effortLevel, ultracode: true } : { effortLevel };
return ultracode ? { effortLevel, ultracode: true } : { effortLevel };
}
function canonicalizeModelSettingsKey(value: string): string {
@@ -79,6 +80,13 @@ export function mergeEffortSettings(base: Settings, override: Settings | undefin
};
}
export function effortLevelName(level: string): string {
return level
.split(/[_-]/)
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
.join(" ");
}
export function buildEffortConfigOption(
modelInfos: ModelInfo[],
currentModelId: string,
@@ -98,10 +106,7 @@ export function buildEffortConfigOption(
...(useRecommendedValue ? [] : [{ value: "default", name: "Default" }]),
...supportedLevels.map((level) => ({
value: level,
name: level
.split(/[_-]/)
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
.join(" "),
name: effortLevelName(level),
})),
];
const includes = (level: string) =>

View File

@@ -1,4 +1,4 @@
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
import {
getSessionMessages,
type SDKAssistantMessageError,

View File

@@ -11,6 +11,10 @@
* - an interactive CLI that is not driven by an SDK holds it, and its registry
* status is newer than the transcript: the registry status (an idle one is
* `error` after such a turn);
* - a turn that ended with an open background task is `running`, also after
* 10 minutes: Claude Code wakes the session again when the task ends. The
* wait ends when the registry status is `idle`, because a task can end
* without a transcript record (a stop in the task UI);
* - otherwise the transcript tail: a finished turn is `idle` (`error` after
* an API error), an unfinished turn written in the last 10 minutes is
* `running`, and anything else has no known state.
@@ -72,6 +76,14 @@ function usesRegistryStatus(record: LiveRecord, transcriptMtimeMs: number): bool
);
}
/** Whether the last turn ended while a background task of the session is
* open. */
function waitsForBackgroundTasks(facts: TranscriptFacts): boolean {
if (facts.turnState !== "finished") return false;
const openTasks = facts.backgroundTasks?.open ?? [];
return openTasks.length > 0;
}
function iso(ms: number | undefined): string | undefined {
return ms === undefined || !Number.isFinite(ms) ? undefined : new Date(ms).toISOString();
}
@@ -103,6 +115,9 @@ export function deriveActivity(input: {
// A busy or waiting CLI wins; an idle one shows how its turn ended.
state = registryState(live.status);
if (state === "idle" && failed) state = "error";
} else if (waitsForBackgroundTasks(facts) && live.status !== "idle") {
// Claude Code wakes the session again when an open task ends.
state = "running";
} else if (failed) {
state = "error";
} else if (facts.turnState === "finished") {

View File

@@ -0,0 +1,223 @@
/**
* The background tasks of a session that did not end yet, from its transcript.
*
* A Claude Code session can start a background shell command or a background
* sub-agent and end its turn at once. When the task ends, Claude Code wakes
* the session with a `<task-notification>` input for a short turn. So a turn
* end with an open task is no end of the work: the session list keeps such a
* session `running` (see `activity.ts`).
*
* The records, as Claude Code 2.1.299 writes them, in the rules of AIR's
* native Claude provider:
*
* - a tool result opens a task when its `toolUseResult` has a
* `backgroundTaskId` (a shell command), or `isAsync: true` with an
* `agentId` (a sub-agent);
* - a task closes when its notification reaches the session: as a user
* record, or as a `queue-operation` `remove` when a running turn takes it
* in. A notification that waits in the queue (`enqueue`) keeps the task
* open, because the session still runs a turn for it;
* - only a notification with a `<status>` closes a task. A notification
* without one comes while the task still runs: the check-in of a quiet
* shell ("is still running after …", since Claude Code 2.1.303) or a
* `Monitor` event;
* - a `TaskStop` result closes the task of its `toolUseResult.task_id`:
* Claude Code writes no notification for it. The result does not name its
* tool, so its `tool_use_id` must match a `TaskStop` call. A `task_id` in
* the result of another tool keeps the task open;
* - a resumed CLI writes a `stopped` notification for each task of the
* previous process;
* - a sidechain record belongs to a sub-agent and opens or closes no task of
* the session.
*
* A background shell that does not end, for example a dev server, keeps its
* task open, so its session stays `running` while the CLI holds it.
*
* A task that a sub-agent starts is not seen. Its start record is only in the
* transcript of the sub-agent, so the session can be `idle` while the
* sub-agent waits for it.
*/
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../task-notifications.js";
export type BackgroundTasks = {
/** The ids of the tasks that started and did not end, in start order. */
open: string[];
/** The `tool_use` ids of the `TaskStop` calls without a result. */
stopCalls: string[];
/** Whether the sets hold the records of the whole file. A set from the
* tail window only does not know a task that started before it. */
complete: boolean;
};
/** The fields of a transcript record that bear on a task. */
type TranscriptRecord = {
type?: string;
isSidechain?: boolean;
/** Of a `queue-operation` record: `enqueue`, `remove`, ... */
operation?: string;
/** Of a `queue-operation` record: the queued input. */
content?: string;
message?: { content?: string | ContentBlock[] };
toolUseResult?: ToolUseResult;
};
type ContentBlock = {
type?: string;
text?: string;
/** Of a `tool_use` block. */
id?: string;
name?: string;
/** Of a `tool_result` block. */
tool_use_id?: string;
};
type ToolUseResult = {
/** The task of a background shell command. */
backgroundTaskId?: string;
/** With `agentId`: an async sub-agent. */
isAsync?: boolean;
agentId?: string;
/** The task that a `TaskStop` call stopped. Another tool can give one. */
task_id?: string;
};
const NOTIFICATION_TAG = `<${TASK_NOTIFICATION_TAG}>`;
/** Text that a line must hold to bear on a task, so that the other lines
* are not parsed. */
const TASK_MARKERS = ["backgroundTaskId", '"isAsync"', NOTIFICATION_TAG, "TaskStop", "task_id"];
function parseRecord(line: string): TranscriptRecord | undefined {
try {
return JSON.parse(line) as TranscriptRecord;
} catch {
return undefined;
}
}
function blocksOf(record: TranscriptRecord): ContentBlock[] {
const content = record.message?.content;
return Array.isArray(content) ? content : [];
}
/** The input of a user record: its string content, else its first text
* block. */
function inputTextOf(record: TranscriptRecord): string | undefined {
const content = record.message?.content;
if (typeof content === "string") return content;
return blocksOf(record).find((block) => block.type === "text")?.text;
}
/** The task that a `<task-notification>` input ends: only an input that
* starts with the tag, after whitespace, as AIR's `TaskNotification` reads
* it. Only a notification with a `<status>` ends a task (see
* `taskNotificationsOf`). */
export function endedTaskId(input: string | undefined): string | undefined {
if (!input?.trimStart().startsWith(NOTIFICATION_TAG)) return undefined;
return taskNotificationsOf(input)[0]?.task_id;
}
/** The task that a tool result started: a background shell command, or an
* async sub-agent. */
function startedTaskId(record: TranscriptRecord): string | undefined {
if (record.type !== "user") return undefined;
const result = record.toolUseResult;
if (result?.backgroundTaskId) return result.backgroundTaskId;
if (result?.isAsync) return result.agentId;
return undefined;
}
/** The task whose notification reached the session: as a user record, or
* as a queue `remove` when a running turn takes it in. */
function deliveredTaskId(record: TranscriptRecord): string | undefined {
if (record.type === "user") return endedTaskId(inputTextOf(record));
if (record.type === "queue-operation" && record.operation === "remove") {
return endedTaskId(record.content);
}
return undefined;
}
/** Collects the tasks of the lines it is given, in file order. */
class TaskTracker {
private readonly open: Set<string>;
private readonly stopCalls: Set<string>;
constructor(after?: BackgroundTasks) {
this.open = new Set(after?.open);
this.stopCalls = new Set(after?.stopCalls);
}
add(line: string): void {
const mayBearOnTask = TASK_MARKERS.some((marker) => line.includes(marker));
if (!mayBearOnTask) return;
const record = parseRecord(line);
// A sidechain record belongs to a sub-agent, which receives the
// notifications of its own tasks.
if (!record || record.isSidechain) return;
this.apply(record);
}
result({ complete }: { complete: boolean }): BackgroundTasks {
return { open: [...this.open], stopCalls: [...this.stopCalls], complete };
}
private apply(record: TranscriptRecord): void {
const started = startedTaskId(record);
if (started) this.open.add(started);
const delivered = deliveredTaskId(record);
if (delivered) this.open.delete(delivered);
this.applyTaskStop(record);
}
/** A `task_id` stops its task only in the result of a `TaskStop` call:
* another tool can give a `task_id` too. */
private applyTaskStop(record: TranscriptRecord): void {
let isTaskStopResult = false;
for (const block of blocksOf(record)) {
if (block.type === "tool_use" && block.name === "TaskStop" && block.id) {
this.stopCalls.add(block.id);
}
const resultId = block.tool_use_id;
if (resultId && this.stopCalls.has(resultId)) {
this.stopCalls.delete(resultId);
isTaskStopResult = true;
}
}
const stopped = record.toolUseResult?.task_id;
if (isTaskStopResult && stopped) this.open.delete(stopped);
}
}
/**
* The tasks of the lines of `text`, applied on the tasks `after`.
*
* `after` can be what an earlier scan found for a shorter copy of the file,
* and `text` can repeat records of that copy. The result is still the same:
* the last record that opens or closes a task decides, and `text` holds the
* last records.
*/
export function backgroundTasksOf(
text: string,
options: { complete: boolean; after?: BackgroundTasks },
): BackgroundTasks {
const tracker = new TaskTracker(options.after);
for (const line of text.split("\n")) tracker.add(line);
return tracker.result({ complete: options.complete });
}
/** The tasks of the first `size` bytes of `filePath`, from all its records.
* Reads the whole file, so only for a session that a live process holds. */
export async function searchBackgroundTasks(
filePath: string,
size: number,
): Promise<BackgroundTasks> {
const tracker = new TaskTracker();
if (size === 0) return tracker.result({ complete: true });
const stream = createReadStream(filePath, { start: 0, end: size - 1, encoding: "utf8" });
const lines = createInterface({ input: stream, crlfDelay: Infinity });
for await (const line of lines) tracker.add(line);
return tracker.result({ complete: true });
}

View File

@@ -15,11 +15,12 @@ import {
type InitializeRequest,
type ListSessionsRequest,
type ListSessionsResponse,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
import type { Query } from "@anthropic-ai/claude-agent-sdk";
import type { AcpClient, Logger, Session } from "../acp-agent.js";
import {
AIR_SESSION_ARCHIVE_CAPABILITY,
AIR_SESSION_BATCH_GET_CAPABILITY,
AIR_SESSION_INDEX_CAPABILITY,
AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY,
AIR_SESSION_RENAME_CAPABILITY,
@@ -46,6 +47,8 @@ import {
renameTo,
parseRenameSessionRequest,
parseSessionIdRequest,
parseBatchGetSessionsRequest,
SESSION_BATCH_GET_METHOD,
SESSION_ARCHIVE_METHOD,
SESSION_RENAME_METHOD,
SESSION_UNARCHIVE_METHOD,
@@ -54,6 +57,8 @@ import {
type RetitleOptions,
type SessionIdRequest,
type TitleChange,
type BatchGetSessionsRequest,
type BatchGetSessionsResponse,
} from "./service.js";
/** What the session index keeps on each `Session`. */
@@ -144,12 +149,13 @@ export class SessionIndexConnection {
}
/** The AIR capabilities the agent advertises for the index: the index
* itself, archive, rename, the list subscription and the `agentWorktree`
* itself, batch get, archive, rename, the list subscription and the `agentWorktree`
* row field, all only to a client that declared `sessionIndex`. */
capabilities(): string[] {
return this.service
? [
AIR_SESSION_INDEX_CAPABILITY,
AIR_SESSION_BATCH_GET_CAPABILITY,
AIR_SESSION_ARCHIVE_CAPABILITY,
AIR_SESSION_RENAME_CAPABILITY,
AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY,
@@ -230,6 +236,12 @@ export class SessionIndexConnection {
return this.service?.list(params, (sessionId) => this.ownSessionState(sessionId));
}
batchGet(params: BatchGetSessionsRequest): Promise<BatchGetSessionsResponse> {
return this.requireService(SESSION_BATCH_GET_METHOD).batchGet(params, (sessionId) =>
this.ownSessionState(sessionId),
);
}
/** An AIR client archives with session/delete (see {@link deleteSession}):
* its archived sessions (an archived custom title) stay hidden from the
* old list, as when the delete removed them. */
@@ -448,6 +460,7 @@ export class SessionIndexConnection {
/** The agent methods behind the session index extension methods. */
type SessionIndexMethods = {
batchGetSessions(params: BatchGetSessionsRequest): Promise<BatchGetSessionsResponse>;
subscribeSessionList(params: ListSubscribeRequest): Promise<ListSubscribeResponse>;
unsubscribeSessionList(params: ListUnsubscribeRequest): Promise<EmptyResponse>;
renameSessionTitle(params: RenameSessionRequest): Promise<EmptyResponse>;
@@ -455,10 +468,15 @@ type SessionIndexMethods = {
unarchiveSession(params: SessionIdRequest): Promise<EmptyResponse>;
};
/** Routes `_session/list/subscribe`, `_session/list/unsubscribe`,
/** Routes `_session/batch_get`, `_session/list/subscribe`, `_session/list/unsubscribe`,
* `_session/rename`, `_session/archive` and `_session/unarchive`. */
export function onSessionIndexRequests(app: AgentApp, agent: () => SessionIndexMethods): AgentApp {
return app
.onRequest<BatchGetSessionsRequest, BatchGetSessionsResponse>(
SESSION_BATCH_GET_METHOD,
{ parse: parseBatchGetSessionsRequest },
(ctx) => agent().batchGetSessions(ctx.params),
)
.onRequest<ListSubscribeRequest, ListSubscribeResponse>(
LIST_SUBSCRIBE_METHOD,
{ parse: parseListSubscribeRequest },

View File

@@ -40,7 +40,7 @@ import { randomUUID } from "node:crypto";
import * as fs from "node:fs";
import * as fsp from "node:fs/promises";
import * as path from "node:path";
import { RequestError, type SessionInfo } from "@agentclientprotocol/sdk";
import { RequestError, type SessionInfo } from "@agentclientprotocol/sdk/unstable";
import type { OwnSessionState } from "./activity.js";
import {
bySession,

View File

@@ -104,6 +104,17 @@ export async function canonicalPath(cwd: string): Promise<string> {
}
}
/** Whether the path exists, including a symlink whose target is missing. */
export async function exists(target: string): Promise<boolean> {
try {
await fs.lstat(target);
return true;
} catch (error) {
if (errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR") return false;
throw error;
}
}
/** The names of the project directories of `cwd` among `rootEntries`. */
export function projectDirsOf(cwd: string, rootEntries: readonly string[]): string[] {
const exact = encodeProjectPath(cwd);

View File

@@ -15,7 +15,8 @@ import {
RequestError,
type ListSessionsRequest,
type ListSessionsResponse,
} from "@agentclientprotocol/sdk";
type SessionInfo,
} from "@agentclientprotocol/sdk/unstable";
import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
import { airExtensionMeta } from "../air-extension.js";
import { sanitizeTitle } from "../session-titles.js";
@@ -37,7 +38,7 @@ import {
import { LiveSessionRegistry } from "./live-registry.js";
import {
canonicalPath,
errorCode,
exists,
isExactProjectDir,
isSessionId,
projectDirMatches,
@@ -65,6 +66,7 @@ import { sessionInfoOf } from "./session-info.js";
export const SESSION_RENAME_METHOD = "_session/rename";
export const SESSION_ARCHIVE_METHOD = "_session/archive";
export const SESSION_UNARCHIVE_METHOD = "_session/unarchive";
export const SESSION_BATCH_GET_METHOD = "_session/batch_get";
/** The JSON-RPC code of an unknown session (ACP `ResourceNotFound`). */
const RESOURCE_NOT_FOUND = -32002;
@@ -73,6 +75,27 @@ const CURSOR_VERSION = 5;
export type SessionIdRequest = { sessionId: string };
export type RenameSessionRequest = { sessionId: string; title: string };
export type BatchGetSessionsRequest = { sessionIds: string[] };
export type BatchGetSessionsResponse = {
sessions: SessionInfo[];
};
export function parseBatchGetSessionsRequest(value: unknown): BatchGetSessionsRequest {
const record = asRecord(value);
const ids = record.sessionIds;
if (
Object.keys(record).length !== 1 ||
!Array.isArray(ids) ||
ids.some((id) => typeof id !== "string" || !id.trim()) ||
new Set(ids).size !== ids.length
) {
throw RequestError.invalidParams(
undefined,
"params require only sessionIds: distinct, non-blank strings",
);
}
return { sessionIds: ids };
}
export function sessionNotFound(sessionId: string): RequestError {
return new RequestError(RESOURCE_NOT_FOUND, `Session not found: ${sessionId}`, { sessionId });
@@ -364,26 +387,6 @@ function isSdkNotFound(error: unknown): boolean {
return error instanceof Error && /^Session \S+ not found in /.test(error.message);
}
async function exists(target: string): Promise<boolean> {
try {
await fs.lstat(target);
return true;
} catch (error) {
if (errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR") return false;
throw error;
}
}
/** The error the SDK `deleteSession` gives for a session id it rejects or
* does not find, which a client without `sessionIndex` got before. */
function sdkDeleteError(sessionId: string): Error {
return new Error(
isSessionId(sessionId)
? `Session ${sessionId} not found in any project directory`
: `Invalid sessionId: ${sessionId}`,
);
}
/**
* `session/delete` of a client that is neither AIR nor a `sessionIndex` client:
* the SDK delete of the session's transcript. A session without one succeeds,
@@ -409,16 +412,18 @@ export async function deleteSessionIfStored(
/**
* `session/delete` of an AIR client without `sessionIndex`, which uses delete
* to mark a session done: archives it instead, so the transcript survives. A
* session without a transcript fails as the SDK delete did.
* session without a transcript has nothing to archive and succeeds, as ACP
* asks of a delete of a session that never existed (see
* {@link deleteSessionIfStored}).
*/
export async function archiveInsteadOfDelete(
sessionId: string,
service: SessionIndexService,
): Promise<void> {
if (!isSessionId(sessionId)) throw sdkDeleteError(sessionId);
if (!isSessionId(sessionId)) return;
// The SDK delete skips empty transcripts, so they do not count.
const found = await service.index.findTranscripts(sessionId);
if (found.length === 0) throw sdkDeleteError(sessionId);
if (found.length === 0) return;
await service.retitle(sessionId, archiveTo(true), { sidecar: "existing" });
}
@@ -476,12 +481,18 @@ export class SessionIndexService {
private readonly deleteSession: (sessionId: string) => Promise<void>;
constructor(private readonly deps: SessionIndexDeps) {
this.index = new SessionIndex();
this.registry = deps.registry ?? new LiveSessionRegistry();
this.index = new SessionIndex({ isLive: (sessionId) => this.isLive(sessionId) });
this.now = deps.now ?? Date.now;
this.deleteSession = deps.deleteSession ?? ((id) => sdkDeleteSession(id));
}
/** Whether a live process holds the session, by the live registry. */
private async isLive(sessionId: string): Promise<boolean> {
const live = await this.registry.snapshot();
return live.has(sessionId);
}
/** `session/list` of a `sessionIndex` client. `own` reports the sessions
* that this connection runs. */
async list(
@@ -514,6 +525,22 @@ export class SessionIndexService {
return hasMore && last ? { sessions, nextCursor: encodeListCursor(last, scope) } : { sessions };
}
/** Metadata for known ids, independent of list pages and project filters. */
async batchGet(
{ sessionIds }: BatchGetSessionsRequest,
own: (sessionId: string) => OwnSessionState | undefined,
): Promise<BatchGetSessionsResponse> {
if (sessionIds.length === 0) return { sessions: [] };
const live = await this.registry.snapshot();
const now = this.now();
const rows = await this.index.getSessions(sessionIds);
return {
sessions: rows.map((row) =>
sessionInfoOf(row, own(row.sessionId), live.get(row.sessionId), now),
),
};
}
/** Runs `mutation` after the previous mutation of the session ended. */
private exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
const previous = this.mutations.get(sessionId) ?? Promise.resolve();

View File

@@ -26,6 +26,8 @@ import { effectiveTitle, visibleTitle } from "./archive-title.js";
import {
canonicalPath,
encodeProjectPath,
errorCode,
exists,
isExactProjectDir,
isSessionId,
normalizePath,
@@ -52,16 +54,21 @@ import {
type SdkTitles,
type TranscriptFacts,
} from "./transcript-scan.js";
import { searchBackgroundTasks } from "./background-tasks.js";
import { DirListings, statFiles } from "./dir-listing.js";
import { isInLinkedWorktree, worktreeCounterparts } from "./worktrees.js";
export const DEFAULT_LIST_LIMIT = 50;
export const MAX_LIST_LIMIT = 200;
/** Discovery could not establish which sessions the store contains. */
export class SessionStoreUnavailableError extends Error {}
/** Entries of the metadata cache: a few hundred bytes each, so the
* transcripts of a large history (tens of thousands) stay cached and deep
* pages do not read them again. */
const METADATA_CACHE_SIZE = 50_000;
const READ_BATCH_SIZE = 16;
/** Session ids looked up together by {@link SessionIndex.getSessions}. */
const LOOKUP_CHUNK_SIZE = 100;
/** Transcripts read at most per directory to recover a sibling's cwd. */
const MAX_CWD_PROBES_PER_DIR = 64;
/** How long a directory that gave no cwd is not probed again. */
@@ -96,6 +103,24 @@ export type TranscriptCandidate = {
ino: number;
};
type TranscriptFile = Pick<
TranscriptCandidate,
"sessionId" | "filePath" | "dirName" | "projectPath"
>;
/** Non-empty transcript files, with bounded stats shared by lists and id lookups. */
async function transcriptCandidates(
files: readonly TranscriptFile[],
): Promise<TranscriptCandidate[]> {
const stats = await statFiles(files.map(({ filePath }) => filePath));
return files.flatMap((file, i) => {
const current = stats[i];
return current?.isFile() && current.size > 0
? [{ ...file, mtimeMs: current.mtimeMs, size: current.size, ino: current.ino }]
: [];
});
}
/** The cached metadata of one transcript. */
type TranscriptMetadata = {
/** The visible title: without the archive prefix. */
@@ -400,7 +425,15 @@ function scopeOf(
};
}
export type SessionIndexOptions = {
/** Whether a live process holds the session. Only for such a session the
* whole transcript is searched for its background tasks. */
isLive?: (sessionId: string) => Promise<boolean>;
};
export class SessionIndex {
constructor(private readonly options: SessionIndexOptions = {}) {}
private readonly listings = new DirListings();
private readonly metadata = new Lru<string, CacheEntry>(METADATA_CACHE_SIZE);
/** The metadata reads in flight, by path: a list and a subscription that
@@ -517,25 +550,16 @@ export class SessionIndex {
const listed = await this.listings.namesOfAll(dirPaths);
const files = dirs.flatMap(({ dirName, projectPath }, i) =>
listed[i]!.filter((name) => name.endsWith(".jsonl") && isSessionId(name.slice(0, -6))).map(
(name) => ({ name, filePath: path.join(root, dirName, name), dirName, projectPath }),
(name) => ({
sessionId: name.slice(0, -6),
filePath: path.join(root, dirName, name),
dirName,
projectPath,
}),
),
);
const stats = await statFiles(files.map(({ filePath }) => filePath));
const all: TranscriptCandidate[] = [];
files.forEach(({ name, filePath, dirName, projectPath }, i) => {
const fileStats = stats[i];
if (!fileStats?.isFile() || fileStats.size === 0) return;
all.push({
sessionId: name.slice(0, -6),
filePath,
dirName,
projectPath,
mtimeMs: fileStats.mtimeMs,
size: fileStats.size,
ino: fileStats.ino,
});
});
return { dirs: [root, ...dirPaths], names: [rootNames, ...listed], candidates: all };
const candidates = await transcriptCandidates(files);
return { dirs: [root, ...dirPaths], names: [rootNames, ...listed], candidates };
}
/**
@@ -571,6 +595,90 @@ export class SessionIndex {
};
}
/** Known ids in request order, using the same rows as the list.
* Addresses exact filenames; only rows without a cwd need sibling discovery. */
async getSessions(sessionIds: readonly string[]): Promise<IndexRow[]> {
if (sessionIds.length === 0) return [];
const root = projectsRoot();
let projectEntries: string[];
try {
projectEntries = await fs.readdir(root);
} catch (cause) {
if (errorCode(cause) === "ENOENT" && !(await exists(root))) {
return [];
}
throw new SessionStoreUnavailableError("Session store could not be read", { cause });
}
const ids = sessionIds.filter(isSessionId);
const byId = new Map<string, IndexRow>();
// Paths are project directories × ids: bound them per chunk, not per request.
for (let start = 0; start < ids.length; start += LOOKUP_CHUNK_SIZE) {
const chunk = ids.slice(start, start + LOOKUP_CHUNK_SIZE);
for (const row of await this.lookupRows(root, projectEntries, chunk)) {
byId.set(row.sessionId, row);
}
}
return sessionIds.flatMap((id) => {
const row = byId.get(id);
return row ? [row] : [];
});
}
/** The rows of `ids` found under `projectEntries`. All copies of an id are
* in the same call, so copy selection is unchanged by chunking. */
private async lookupRows(
root: string,
projectEntries: readonly string[],
ids: readonly string[],
): Promise<IndexRow[]> {
let candidates = await transcriptCandidates(
projectEntries.flatMap((dirName) =>
ids.map((sessionId) => ({
sessionId,
filePath: path.join(root, dirName, `${sessionId}.jsonl`),
dirName,
})),
),
);
if (CASE_INSENSITIVE_PATHS) {
// On a case-insensitive file system, stat of `ABC.jsonl` also finds
// `abc.jsonl`. Keep a file only when its real name is the requested one,
// or differs by more than case (a symlink, as the list would read it).
// A failed realpath means the file is unreadable, so the id is omitted.
const exact = await Promise.all(
candidates.map(async ({ filePath }) => {
try {
const actual = path.basename(await fs.realpath(filePath));
const expected = path.basename(filePath);
return actual === expected || actual.toLowerCase() !== expected.toLowerCase();
} catch {
return false;
}
}),
);
candidates = candidates.filter((_, i) => exact[i]);
}
return this.collectRows(candidates.sort(byMtimeDescending), {
inScope: scopeOf(undefined),
archived: "all",
wanted: Infinity,
unread: async (read, dirNames) => {
const dirs = [...dirNames];
const listed = await this.listings.namesOfAll(dirs.map((dir) => path.join(root, dir)));
const siblings = dirs.flatMap((dirName, i) =>
listed[i]!.filter((name) => name.endsWith(".jsonl") && isSessionId(name.slice(0, -6)))
.map((name) => ({
sessionId: name.slice(0, -6),
filePath: path.join(root, dirName, name),
dirName,
}))
.filter(({ filePath }) => !read.has(filePath)),
);
return (await transcriptCandidates(siblings)).sort(byMtimeDescending);
},
});
}
/** Every transcript file of `sessionId` (see {@link scanSession}). Empty
* files only with `includeEmpty`. */
async findTranscripts(
@@ -769,9 +877,12 @@ export class SessionIndex {
archived: ArchivedFilter;
after?: ListCursor;
wanted: number;
/** The transcripts not read (by path), which may supply a sibling's
* cwd. */
unread: (read: ReadonlySet<string>) => readonly TranscriptCandidate[];
/** The unread transcripts of the directories still eligible for cwd
* recovery. Called only after checking the negative cache. */
unread?: (
read: ReadonlySet<string>,
dirNames: ReadonlySet<string>,
) => readonly TranscriptCandidate[] | Promise<readonly TranscriptCandidate[]>;
restat?: boolean;
},
): Promise<IndexRow[]> {
@@ -843,14 +954,15 @@ export class SessionIndex {
}
await settlePending();
}
if (pending.length > 0) {
const unread = options.unread;
if (pending.length > 0 && unread) {
// A row recovered after the scan stopped still sorts into the page: it
// was read before the stop, and more rows only raise the bound. A
// sibling that the archive filter or the cursor leaves out may still
// supply the cwd.
await this.learnDirCwds(
new Set(pending.map(({ candidate }) => candidate.dirName)),
options.unread(read),
(dirNames) => unread(read, dirNames),
options.restat ?? false,
);
await settlePending();
@@ -884,7 +996,9 @@ export class SessionIndex {
* changes. `restat` as in {@link collectRows}. */
private async learnDirCwds(
dirNames: ReadonlySet<string>,
unread: readonly TranscriptCandidate[],
unread: (
dirNames: ReadonlySet<string>,
) => readonly TranscriptCandidate[] | Promise<readonly TranscriptCandidate[]>,
restatFirst: boolean,
): Promise<void> {
const root = projectsRoot();
@@ -900,8 +1014,9 @@ export class SessionIndex {
if (known && known.mtimeMs === mtimeMs && now - known.at < NO_CWD_RETRY_MS) continue;
mtimes.set(dirName, mtimeMs);
}
if (mtimes.size === 0) return;
const probes = new Map<string, number>();
let remaining = unread.filter((candidate) => {
let remaining = (await unread(new Set(mtimes.keys()))).filter((candidate) => {
if (!mtimes.has(candidate.dirName)) return false;
const count = probes.get(candidate.dirName) ?? 0;
if (count >= MAX_CWD_PROBES_PER_DIR) return false;
@@ -992,14 +1107,15 @@ export class SessionIndex {
this.reads++;
const headTail = await readHeadTail(candidate.filePath, candidate.size);
if (isSidechainTranscript(headTail.head)) return null;
const facts = await scanTranscriptFile(
const scanned = await scanTranscriptFile(
candidate.filePath,
candidate.size,
headTail,
previous,
candidate.ino,
);
if (!facts.hasMessages) return null;
if (!scanned.hasMessages) return null;
const facts = await this.withAllBackgroundTasks(candidate, scanned);
// The last relocation names the session's cwd, as the SDK reads it; the
// messages before it keep the old one.
const fileCwd = await this.recoverCwd(candidate.dirName, [
@@ -1032,6 +1148,24 @@ export class SessionIndex {
};
}
/**
* `facts` with the background tasks of the whole transcript, when the
* tasks of its tail are not complete: a task can start before the tail.
* Only a finished turn of a live session waits for the tasks, so only
* then the whole transcript is read.
*/
private async withAllBackgroundTasks(
candidate: TranscriptCandidate,
facts: TranscriptFacts,
): Promise<TranscriptFacts> {
if (facts.turnState !== "finished") return facts;
if (facts.backgroundTasks?.complete) return facts;
const isLive = await this.options.isLive?.(candidate.sessionId);
if (!isLive) return facts;
const backgroundTasks = await searchBackgroundTasks(candidate.filePath, candidate.size);
return { ...facts, backgroundTasks };
}
/**
* The titles the SDK gives the listed transcript (`customTitle` and
* `summary` of its `getSessionInfo`), by the SDK's rule from the head and

View File

@@ -4,7 +4,7 @@
* the RFDs in `_meta.jetbrains.air`.
*/
import type { SessionInfo } from "@agentclientprotocol/sdk";
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
import { withAirMeta } from "../air-extension.js";
import { deriveActivity, selectCost, type OwnSessionState } from "./activity.js";
import type { LiveRecord } from "./live-registry.js";

View File

@@ -7,9 +7,13 @@
*
* A last message longer than the tail window leaves no message in it: then
* the tail window grows, up to {@link MAX_TAIL_SIZE}, until it holds one.
*
* The open background tasks come from the tail, on top of the tasks of an
* earlier scan (see `background-tasks.ts`).
*/
import * as fs from "node:fs/promises";
import { backgroundTasksOf, type BackgroundTasks } from "./background-tasks.js";
import { firstPrompt, isUserPrompt, mediaPrompt, promptOf } from "./first-prompt.js";
const CHUNK_SIZE = 64 * 1024;
@@ -56,6 +60,9 @@ export type TranscriptFacts = {
* worktree and the session is in it), false when it says otherwise.
* Undefined when no such record was read. See {@link agentWorktreeOf}. */
agentWorktree?: boolean;
/** The background tasks that did not end. From the tail, on top of what
* an earlier scan found when the file only grew within the tail. */
backgroundTasks?: BackgroundTasks;
/** Whether the head or the tail holds a user or an assistant message. A
* transcript without one is a metadata-only stub. */
hasMessages: boolean;
@@ -77,7 +84,7 @@ const INHERITED_FACTS = ["model", "tailCwd", "lastTurnEndedAt", "agentWorktree"]
/** What an earlier scan of the same file found, at `size`. */
export type PreviousScan = { size: number } & Pick<
TranscriptFacts,
"lastPromptAt" | "promptSearched" | (typeof INHERITED_FACTS)[number]
"lastPromptAt" | "promptSearched" | "backgroundTasks" | (typeof INHERITED_FACTS)[number]
>;
/** The last `window` bytes of a file of `size` bytes, without the first,
@@ -157,6 +164,8 @@ export async function scanTranscriptFile(
}
}
}
const previousTasks = grownWithinTail ? previous.backgroundTasks : undefined;
facts.backgroundTasks = tailBackgroundTasks(read.tail, size, previousTasks);
// No worktree record in the tail (or before it, by an earlier scan): the
// one `claude --worktree` writes at the start may still be in the head.
if (facts.agentWorktree === undefined) {
@@ -174,7 +183,8 @@ export async function scanTranscriptFile(
if (!facts.hasMessages || complete(facts) || size <= CHUNK_SIZE) return facts;
const key = `${filePath}\0${size}\0${identity ?? ""}`;
const searched = fullySearched.get(key);
if (searched) return searched;
// The tasks can differ by the earlier scan.
if (searched) return { ...searched, backgroundTasks: facts.backgroundTasks };
let result = facts;
const handle = await fs.open(filePath, "r");
try {
@@ -218,6 +228,7 @@ export async function scanTranscriptFile(
// A turn end the wider tail lacks may lie before it: the
// narrow scan inherited it from an earlier scan of this file.
lastTurnEndedAt: wider.lastTurnEndedAt ?? facts.lastTurnEndedAt,
backgroundTasks: facts.backgroundTasks,
hasMessages: facts.hasMessages,
lastPromptAt,
// Searched to the start of the file, or found.
@@ -237,6 +248,22 @@ export async function scanTranscriptFile(
return result;
}
/**
* The background tasks of the tail of a file of `size` bytes. `previous`:
* the tasks of an earlier scan, when the file only grew within the tail
* since. The set is complete when the tail is the whole file, or when it
* holds all records after a complete earlier set.
*/
function tailBackgroundTasks(
tail: string,
size: number,
previous: BackgroundTasks | undefined,
): BackgroundTasks {
if (size <= CHUNK_SIZE) return backgroundTasksOf(tail, { complete: true });
if (previous?.complete) return backgroundTasksOf(tail, { complete: true, after: previous });
return backgroundTasksOf(tail, { complete: false });
}
const RELOCATED_MARKER = '"relocated"';
/** The cwd of the last `relocated` record of the tail: where the session

View File

@@ -5,7 +5,7 @@ import {
type SessionNotification,
type SetSessionModeRequest,
type SetSessionModeResponse,
} from "@agentclientprotocol/sdk";
} from "@agentclientprotocol/sdk/unstable";
import type {
ModelInfo,
PermissionMode,
@@ -79,8 +79,9 @@ type InitializeSessionModeParams = {
* The permission mode a new Claude query starts in.
*
* An explicit mode wins. A resume or a load continues in the mode of the last
* user record of the transcript. If that mode is unknown or not available now,
* for example bypass, the session starts in `defaultMode` of the settings.
* user record of the transcript. If that mode is unknown or absent from the
* session's mode catalog, use an available `defaultMode` from settings, or
* `default` when that setting is unavailable too.
*/
export function resolveInitialPermissionMode(
sources: { explicit?: PermissionMode; resumed?: string; defaultMode?: unknown },
@@ -97,13 +98,23 @@ export function resolveInitialPermissionMode(
{ error: () => (rejected = true) },
allowBypass,
);
if (!rejected) return mode;
if (!rejected && buildAvailableModes(allowBypass).some((available) => available.id === mode)) {
return mode;
}
logger.error(
`Ignoring permission mode "${sources.resumed}" of the resumed transcript: ` +
`not available in this session.`,
);
}
return resolvePermissionMode(sources.defaultMode, logger, allowBypass);
const configuredMode = resolvePermissionMode(sources.defaultMode, logger, allowBypass);
// A rejected transcript mode falls back only to a mode this session can offer.
if (
sources.resumed !== undefined &&
!buildAvailableModes(allowBypass).some((available) => available.id === configuredMode)
) {
return "default";
}
return configuredMode;
}
/** Owns session-mode policy and the ACP/SDK synchronization it requires. */
@@ -120,7 +131,7 @@ export class SessionModeManager<S extends SessionMode> {
modes: SessionModeState;
autoModeFallbackWarningPending: boolean;
}> {
const availableModes = this.buildAvailableModes(allowBypass);
const availableModes = buildAvailableModes(allowBypass, this.options.airClient?.() ?? false);
let effectiveMode = requestedMode;
let autoModeFallbackWarningPending = false;
@@ -247,7 +258,10 @@ export class SessionModeManager<S extends SessionMode> {
const session = this.requireOpenSession(sessionId);
const requestedMode = this.parseMode(modeId);
if (!this.isAvailable(session.modes.availableModes, requestedMode)) {
throw new Error(`Mode ${modeId} is not available in this session`);
throw RequestError.invalidParams(
undefined,
`Mode ${modeId} is not available in this session`,
);
}
const effectiveMode = this.effectiveMode(session, requestedMode);
@@ -314,7 +328,7 @@ export class SessionModeManager<S extends SessionMode> {
private requireOpenSession(sessionId: string): S {
const session = this.options.getSession(sessionId);
if (!session) throw new Error("Session not found");
if (!session) throw RequestError.sessionNotFound(sessionId);
if (session.queryClosed) {
throw RequestError.internalError(undefined, this.options.sessionEndedMessage);
}
@@ -343,54 +357,10 @@ export class SessionModeManager<S extends SessionMode> {
case "plan":
return modeId;
default:
throw new Error("Invalid Mode");
throw RequestError.invalidParams(undefined, `Invalid Mode: ${modeId}`);
}
}
private buildAvailableModes(allowBypass: boolean): SessionModeState["availableModes"] {
// Only AIR gets the mode kind.
const air = this.options.airClient?.() ?? false;
const kind = (value: string) => {
const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
return meta ? { _meta: meta } : {};
};
const modes: SessionModeState["availableModes"] = [
{
id: "default",
name: "Manual",
description: "Always ask before making changes",
...kind("standard"),
},
{
id: "acceptEdits",
name: "Accept edits",
description: "Automatically accept all file edits",
...kind("standard"),
},
{
id: "plan",
name: "Plan",
description: "Create a plan before making changes",
...kind("plan"),
},
{
id: "auto",
name: "Auto",
description: "Claude handles permission decisions",
...kind("auto_review"),
},
];
if (allowBypass) {
modes.push({
id: "bypassPermissions",
name: "Bypass permissions",
description: "Accepts all permissions",
...kind("full_access"),
});
}
return modes;
}
private async trySyncMode(
query: Pick<Query, "setPermissionMode">,
mode: PermissionMode,
@@ -403,3 +373,49 @@ export class SessionModeManager<S extends SessionMode> {
}
}
}
function buildAvailableModes(
allowBypass: boolean,
air = false,
): SessionModeState["availableModes"] {
// Only AIR gets the mode kind.
const kind = (value: string) => {
const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
return meta ? { _meta: meta } : {};
};
const modes: SessionModeState["availableModes"] = [
{
id: "default",
name: "Manual",
description: "Always ask before making changes",
...kind("standard"),
},
{
id: "acceptEdits",
name: "Accept edits",
description: "Automatically accept all file edits",
...kind("standard"),
},
{
id: "plan",
name: "Plan",
description: "Create a plan before making changes",
...kind("plan"),
},
{
id: "auto",
name: "Auto",
description: "Claude handles permission decisions",
...kind("auto_review"),
},
];
if (allowBypass) {
modes.push({
id: "bypassPermissions",
name: "Bypass permissions",
description: "Accepts all permissions",
...kind("full_access"),
});
}
return modes;
}

View File

@@ -1,4 +1,4 @@
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo, Query, Settings } from "@anthropic-ai/claude-agent-sdk";
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
import { bedrockRegionPrefix } from "./gateway-models.js";
@@ -210,22 +210,12 @@ export function buildModelConfigOption(
? models.availableModels.filter((model) => model.modelId !== "default")
: models.availableModels;
return {
id: MODEL_CONFIG_ID,
name: "Model",
description: "AI model to use",
category: "model",
type: "select",
currentValue:
concreteRecommendation && models.currentModelId === "default"
? recommended.value
: models.currentModelId,
...(concreteRecommendation
? {
_meta: withAirMeta(undefined, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, recommended.value),
}
: {}),
options: available.map((model) => {
const currentValue =
concreteRecommendation && models.currentModelId === "default"
? recommended.value
: models.currentModelId;
const options: Array<{ value: string; name: string; description?: string }> = available.map(
(model) => {
if (model.modelId === "default" && defaultInfo?.resolvedModel) {
const named = modelInfos.find(
(info) => info.value !== "default" && info.resolvedModel === defaultInfo.resolvedModel,
@@ -241,7 +231,35 @@ export function buildModelConfigOption(
name: model.name,
description: model.description ?? undefined,
};
}),
},
);
// ACP requires `currentValue` to be one of `options`. A session can run a
// model the picker does not list: resumed onto a model excluded by the
// `availableModels` allowlist, or switched by a refusal fallback. List that
// model as its own entry. It is not added to `models.availableModels`, so the
// next rebuild after switching to a listed model drops it.
if (!options.some((option) => option.value === currentValue)) {
const info = modelInfos.find((model) => model.value === currentValue);
options.push({
value: currentValue,
name: info?.displayName || currentValue,
description: info?.description || undefined,
});
}
return {
id: MODEL_CONFIG_ID,
name: "Model",
description: "AI model to use",
category: "model",
type: "select",
currentValue,
...(concreteRecommendation
? {
_meta: withAirMeta(undefined, AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, recommended.value),
}
: {}),
options,
};
}
@@ -264,25 +282,40 @@ function resolveSettingsModel(
const MODEL_FAMILY_ALIASES = new Set(["opus", "sonnet", "haiku", "fable"]);
/** A model id without the spellings that name the same model: provider
* prefixes, dates, version suffixes, `-fast` and context hints. `deniedModels`
* and gateway listings both match on it. */
* prefixes, dates, version suffixes, `:variant` suffixes, dotted versions,
* `-fast` and context hints. `deniedModels` and gateway listings both match on
* it. OpenRouter lists `anthropic/claude-opus-5.5:batch[1m]`, which normalizes
* to `claude-opus-5-5`; `anthropic/openai/gpt-5` keeps its `openai/` prefix and
* matches no Claude id. */
function normalizeModelId(value: string): string {
let id = stripContextHints(value.trim().toLowerCase());
const providerPrefix = id.lastIndexOf("anthropic.");
if (providerPrefix !== -1) id = id.slice(providerPrefix + "anthropic.".length);
return id
.replace(/@.*$/, "")
.replace(/-v\d+(?::\d+)?$/, "")
.replace(/-fast$/, "")
.replace(/-\d{8}$/, "");
return (
id
// Only a leading `anthropic/`: OpenRouter's `anthropic/openai/gpt-5` must
// keep `openai/`, which `lastIndexOf` would strip along with it.
.replace(/^anthropic\//, "")
// `:batch`, `:free`. A letter must follow the colon, so Bedrock's `-v1:0`
// is left for the `-vN:M` step below.
.replace(/:[a-z][a-z0-9-]*$/, "")
// `claude-opus-5.5` -> `claude-opus-5-5`; only dots between two digits.
.replace(/(?<=\d)\.(?=\d)/g, "-")
.replace(/@.*$/, "")
.replace(/-v\d+(?::\d+)?$/, "")
.replace(/-fast$/, "")
.replace(/-\d{8}$/, "")
);
}
/**
* Whether managed `deniedModels` blocks a model id, following the CLI's rules
* closely enough for the picker: a family alias blocks the whole family, and a
* model id blocks that version in every spelling plus the later minor versions
* it prefixes (`claude-opus-5` also blocks `claude-opus-5-5`). The CLI stays
* the enforcer; this only keeps the picker from offering a refused row.
* it prefixes (`claude-opus-5` also blocks `claude-opus-5-5`). Entries share
* `normalizeModelId`, so `claude-opus-4.5` or `anthropic/claude-opus-4.5` also
* blocks `claude-opus-4-5`, which the CLI may still allow. The CLI stays the
* enforcer; this only keeps the picker from offering a refused row.
*/
export function isDeniedModel(modelId: string, deniedModels: readonly string[]): boolean {
const id = normalizeModelId(modelId);
@@ -580,14 +613,107 @@ export async function getAvailableModels(
for (const name of displayNames) {
displayNameCounts.set(name, (displayNameCounts.get(name) ?? 0) + 1);
}
const names = models.map((model, index) =>
displayNameCounts.get(displayNames[index]) === 1 ? displayNames[index] : model.displayName,
);
const nameCounts = new Map<string, number>();
for (const name of names) {
nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
}
return {
availableModels: models.map((model, index) => ({
modelId: model.value,
name:
displayNameCounts.get(displayNames[index]) === 1 ? displayNames[index] : model.displayName,
// Allowlisted aliases can inherit the same SDK label. Keep every ID
// selectable, but distinguish the rows for clients that show only names.
name: nameCounts.get(names[index]) === 1 ? names[index] : `${names[index]} (${model.value})`,
description: model.description,
})),
currentModelId: currentModel.value,
};
}
/** The display name of the model id `model` in `modelInfos`, if it is there. */
export function modelDisplayName(modelInfos: ModelInfo[], model: string): string | undefined {
let byModel = displayNameCache.get(modelInfos);
if (!byModel) displayNameCache.set(modelInfos, (byModel = new Map()));
if (!byModel.has(model)) {
const info = modelInfos.find((info) => info.value === model || info.resolvedModel === model);
byModel.set(model, info?.displayName);
}
return byModel.get(model);
}
/** {@link modelDisplayName} per model list, which a session replaces and never edits. */
const displayNameCache = new WeakMap<ModelInfo[], Map<string, string | undefined>>();
/** The flat entries of a session's `model` select option. */
function modelOptionEntries(option: SessionConfigOption): Array<{ value: string; name: string }> {
if (option.type !== "select") return [];
return option.options.flatMap((entry) =>
"value" in entry ? [{ value: entry.value, name: entry.name }] : entry.options,
);
}
/**
* The entry of the `model` option among `configOptions` for `model`, a model
* id that a response reported, so that a client can compare it with the
* session's own model. Best first:
*
* 1. the current entry, when it runs that model up to a context hint: a
* session on `opus[1m]` or `default` that answers as `claude-opus-4-6`;
* 2. the entry whose value or resolved model is that id;
* 3. an entry that runs that model up to a context hint.
*
* Among several entries for one model, a concrete entry comes before
* `default`, as in {@link resolveModelPreference}. Undefined when no entry
* matches. A model's resolved id comes from `modelInfos`
* (`ModelInfo.resolvedModel`), the wire id that a response reports, as for
* {@link matchResumedModel}. It is compared as it is: a dated id such as
* `claude-haiku-4-5-20251001` matches only the same dated id.
*/
export function modelEntryFor(
configOptions: readonly SessionConfigOption[],
modelInfos: ModelInfo[],
model: string,
): { value: string; name: string } | undefined {
// Each response of a subagent reports its model, so the result is kept per
// list. A session replaces its lists on a change and never edits them.
let byInfos = modelEntryCache.get(configOptions);
if (!byInfos) modelEntryCache.set(configOptions, (byInfos = new WeakMap()));
let byModel = byInfos.get(modelInfos);
if (!byModel) byInfos.set(modelInfos, (byModel = new Map()));
if (byModel.has(model)) return byModel.get(model);
const entry = findModelEntry(configOptions, modelInfos, model);
byModel.set(model, entry);
return entry;
}
const modelEntryCache = new WeakMap<
readonly SessionConfigOption[],
WeakMap<ModelInfo[], Map<string, { value: string; name: string } | undefined>>
>();
function findModelEntry(
configOptions: readonly SessionConfigOption[],
modelInfos: ModelInfo[],
model: string,
): { value: string; name: string } | undefined {
const option = configOptions.find((candidate) => candidate.id === MODEL_CONFIG_ID);
if (option?.type !== "select") return undefined;
// Concrete entries first; `sort` keeps the list order otherwise.
const entries = modelOptionEntries(option).sort(
(left, right) => Number(left.value === "default") - Number(right.value === "default"),
);
const observed = canonicalizeModelId(model);
const resolved = (value: string) =>
canonicalizeModelId(modelInfos.find((info) => info.value === value)?.resolvedModel ?? value);
const sameModel = (value: string) =>
stripContextHints(resolved(value)) === stripContextHints(observed);
const current = entries.find((entry) => entry.value === option.currentValue);
if (current && sameModel(current.value)) return current;
return (
entries.find((entry) => entry.value === model || resolved(entry.value) === observed) ??
entries.find((entry) => sameModel(entry.value))
);
}

View File

@@ -1,4 +1,4 @@
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
/** Fire-and-forget advisory text for the user: live events rather than
* conversation history. See the ACP Session Notices RFD

View File

@@ -1,20 +1,25 @@
/**
* AI-generated session titles.
*
* Claude Code's own auto-title generation never runs under the Agent SDK — the
* latch that arms it is pre-set in the headless path — so `SDKSessionInfo.summary`
* degrades to the raw first prompt. This module asks the CLI for a real title
* instead, via the `generate_session_title` control request, and publishes it
* over ACP as a `session_info_update`.
* Claude Code titles a new SDK session itself: when it reads the first prompt,
* it asks a small model for a title and stores it in the transcript as an
* `ai-title` entry, a few seconds in. It sends no message when it does. This
* module asks for the title with the `generate_session_title` control request,
* which returns the stored title or waits for the one being generated, and
* publishes it over ACP as a `session_info_update`. Where the CLI does not
* title the session itself (a resumed session, or one with
* `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` or
* `CLAUDE_CODE_DISABLE_TERMINAL_TITLE` set), the same request generates it.
*
* {@link SessionTitles} holds the per-session state and is owned by `Session`.
* `acp-agent.ts` drives it from four places: {@link SessionTitles.onPrompt} and
* `acp-agent.ts` drives it from five places: {@link SessionTitles.onPrompt} and
* {@link SessionTitles.onAssistantText} to collect text,
* {@link SessionTitles.onPromptQueued} once a prompt is sent to the CLI,
* {@link SessionTitles.onTurnEnd} at `session_state_changed: idle`, and
* {@link SessionTitles.reset} on `conversation_reset`.
*/
import type { ContentBlock, PromptRequest } from "@agentclientprotocol/sdk";
import type { ContentBlock, PromptRequest } from "@agentclientprotocol/sdk/unstable";
import { getSessionInfo, type Query, type SDKSessionInfo } from "@anthropic-ai/claude-agent-sdk";
import type { ClaudeAcpAgent, Session } from "./acp-agent.js";
import { ExplicitTitle } from "./session-index/explicit-title.js";
@@ -77,6 +82,11 @@ export function appendTitleContext(previous: string | undefined, text: string):
return next.length > MAX_TITLE_CONTEXT_LENGTH ? next.slice(-MAX_TITLE_CONTEXT_LENGTH) : next;
}
/** The text chunks of a prompt, one per line. */
function promptText(prompt: PromptRequest["prompt"]): string {
return prompt.flatMap((chunk) => (chunk.type === "text" ? [chunk.text] : [])).join("\n");
}
/** Whether this SDK still offers on-demand title generation. */
function supportsTitleGeneration(query: Query): boolean {
return typeof (query as TitleCapableQuery).generateSessionTitle === "function";
@@ -100,6 +110,11 @@ export class SessionTitles {
* one. Released by {@link reset}, and when generation yields nothing. */
private settled = false;
/** Whether {@link onPromptQueued} asked for a title from the latest prompt.
* A turn end then does not ask again: like the CLI, a prompt that yields
* no title gets one try, and the next prompt tries again. */
private askedAtPrompt = false;
/** The title a client set, which no generated title replaces. */
private readonly explicit = new ExplicitTitle();
@@ -132,11 +147,33 @@ export class SessionTitles {
if (this.settled) {
return;
}
const promptText = prompt
.flatMap((chunk) => (chunk.type === "text" ? [chunk.text] : []))
.join("\n");
this.context = appendTitleContext(this.context, `\n${promptText(prompt)}\n`);
}
this.context = appendTitleContext(this.context, `\n${promptText}\n`);
/** Ask for the title as soon as a prompt is sent to the CLI, instead of
* waiting for `idle`: `idle` comes only after the whole turn and its
* background tasks, often minutes after the CLI stored its title. The CLI
* answers `generate_session_title` while the turn runs, and with the title
* it stored or is generating, so this makes no second model call. The
* prompt alone is the description, because the CLI titles from the prompt
* alone. When this yields no title, the stored summary is published and
* the next prompt tries again. */
onPromptQueued(session: Session, prompt: PromptRequest["prompt"]): void {
this.askedAtPrompt = false;
// `session.cancelled` is not checked: after a cancelled turn it stays set
// until the next turn starts, which is after this prompt is queued.
if (this.settled || this.explicit.active || session.queryClosed) {
return;
}
if (!supportsTitleGeneration(session.query)) {
return;
}
const description = promptText(prompt).trim();
if (description.length < MIN_TITLE_CONTEXT_LENGTH) {
return;
}
this.askedAtPrompt = true;
this.startGeneration(() => this.titleFromPrompt(session, description));
}
/** Collect the assistant's own answer for the title. Chunks are appended
@@ -152,6 +189,7 @@ export class SessionTitles {
* `conversation_reset`, which mounts a fresh transcript. */
reset(): void {
this.settled = false;
this.askedAtPrompt = false;
this.explicit.reset();
this.context = undefined;
this.lastTitle = undefined;
@@ -165,10 +203,12 @@ export class SessionTitles {
* title — a user `/rename` or one generated earlier; the SDK folds both into
* that one field. Adopt it and latch, so neither is ever titled over.
*
* With no title yet, ask the SDK to generate one in the background: it is a
* ~2s small-model call and turn-end must not wait on it. Only when that is
* not possible do we fall back to `summary`, which for an SDK-driven session
* is just the raw first prompt. */
* With no title yet, and none asked for at the latest prompt (see
* {@link onPromptQueued}), ask the SDK to generate one in the background
* from the prompts and replies so far: it is a ~2s small-model call and
* turn-end must not wait on it. That covers a prompt too short to title
* from, such as "hi". Only when that is not possible do we fall back to
* `summary`, which for an SDK-driven session is just the raw first prompt. */
async onTurnEnd(session: Session): Promise<void> {
if (this.explicit.settledByClient) {
// No title is generated over the one the client set, but a custom title
@@ -200,17 +240,9 @@ export class SessionTitles {
? { title: info.summary, lastModified: info.lastModified }
: undefined;
if (this.canRequest(session)) {
this.settled = true;
this.explicit.track(
this.requestGenerateTitle(session, fallback).catch((error) => {
this.agent.logger.error(
`Session ${this.sessionId}: session title update failed: ${error}`,
);
}),
);
if (!this.askedAtPrompt && this.canRequest(session)) {
const description = this.context?.trim() ?? "";
this.startGeneration(() => this.requestGenerateTitle(session, description, fallback));
return;
}
// Only while this session has no title of its own: `summary` degrades to the
@@ -264,6 +296,32 @@ export class SessionTitles {
});
}
/** Sends the session's current title and `updatedAt`, even when the client
* was already sent that title: a client that resumes the session may not
* have it. Both come from the session file, as `session/list` reports
* them; a session with no file yet sends the last title published, if any,
* without `updatedAt`. */
async publishCurrent(session: Session): Promise<void> {
const info = await this.readSessionInfo(session);
// A title published but not yet stored (a generated title still being
// written) wins over `summary`, which is then only the raw first prompt.
const stored =
info?.customTitle || (this.settled && this.lastTitle ? undefined : info?.summary);
const title = stored
? sanitizeTitle(this.agent.clientTitle?.(stored) ?? stored)
: this.lastTitle;
if (!title) return;
this.lastTitle = title;
await this.agent.client.sessionUpdate({
sessionId: this.sessionId,
update: {
sessionUpdate: "session_info_update",
title,
...(info ? { updatedAt: new Date(info.lastModified).toISOString() } : {}),
},
});
}
/** Records a title the CLI took, as soon as it took it. */
rememberStoredTitle(title: string): void {
this.persistedTitle = title;
@@ -309,17 +367,48 @@ export class SessionTitles {
return (this.context?.trim().length ?? 0) >= MIN_TITLE_CONTEXT_LENGTH;
}
/** Generate a title from the session's own text and publish it. `persist`
* writes it to the session file, which is what carries it into
* `session/list` and `session/load`. A null or failed generation releases the
* latch so a later turn retries, and publishes `fallback` (the stored
* summary) so a backend that can't generate titles is no worse off than
* before. */
/** Latch the title and run `generation` in the background, so neither the
* prompt nor turn-end waits on a model call. A client title change waits
* for it to finish (see {@link ExplicitTitle.track}). */
private startGeneration(generation: () => Promise<void>): void {
this.settled = true;
this.explicit.track(
generation().catch((error) => {
this.agent.logger.error(`Session ${this.sessionId}: session title update failed: ${error}`);
}),
);
}
/** The prompt-time title: the one the session file already holds (a resumed
* session's title, or a `/rename`), else one generated from `description`.
* The file is read first because the CLI checks only the titles it holds in
* memory before generating one. */
private async titleFromPrompt(session: Session, description: string): Promise<void> {
const info = await this.readSessionInfo(session);
if (this.agent.sessions[this.sessionId] !== session || this.explicit.active) {
return;
}
if (info?.customTitle) {
this.context = undefined;
await this.publish(info.customTitle, info.lastModified);
return;
}
const fallback = info?.summary
? { title: info.summary, lastModified: info.lastModified }
: undefined;
await this.requestGenerateTitle(session, description, fallback);
}
/** Generate a title from `description` and publish it. `persist` writes it
* to the session file, which is what carries it into `session/list` and
* `session/load`. A null or failed generation releases the latch so a later
* prompt or turn end retries, and publishes `fallback` (the stored summary)
* so a backend that can't generate titles is no worse off than before. */
private async requestGenerateTitle(
session: Session,
description: string,
fallback: TitleFallback | undefined,
): Promise<void> {
const description = this.context?.trim() ?? "";
let title: string | null = null;
try {

140
src/session-ultracode.ts Normal file
View File

@@ -0,0 +1,140 @@
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type {
SessionConfigOption,
SetSessionConfigOptionRequest,
} from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo, Query } from "@anthropic-ai/claude-agent-sdk";
import { ULTRACODE_CONFIG_ID } from "./session-config-ids.js";
export { ULTRACODE_CONFIG_ID } from "./session-config-ids.js";
/** Select-fallback values for clients without boolean config options. */
export const ULTRACODE_ON = "on";
export const ULTRACODE_OFF = "off";
const ULTRACODE_DESCRIPTION = "Dynamic workflows on every task, at any effort level";
/** Ultracode state from the CLI's `get_settings` control response
* (`applied.ultracodeAvailable` / `applied.ultracodeRequested`). Each field is
* undefined when the CLI does not report it. */
export type UltracodeSettingsReport = {
/** Workflows are enabled and the current model supports ultracode. */
available?: boolean;
/** Ultracode is turned on for the session, whether or not it can take
* effect on the current model. */
requested?: boolean;
};
// `Query.getSettings()` exists at runtime (sdk.mjs) but is missing from the
// SDK's type declarations as of 0.3.316.
type GetSettingsQuery = Query & {
getSettings?: () => Promise<{
applied?: { ultracodeAvailable?: unknown; ultracodeRequested?: unknown };
}>;
};
/** Ask the CLI whether ultracode is available and requested for the session.
* Returns undefined when the SDK has no `getSettings` or the request fails;
* callers then fall back to {@link modelSupportsUltracode}. */
export async function readUltracodeSettings(
query: Query,
): Promise<UltracodeSettingsReport | undefined> {
const getSettings = (query as GetSettingsQuery).getSettings;
if (typeof getSettings !== "function") return undefined;
try {
const applied = (await getSettings.call(query))?.applied;
return {
available:
typeof applied?.ultracodeAvailable === "boolean" ? applied.ultracodeAvailable : undefined,
requested:
typeof applied?.ultracodeRequested === "boolean" ? applied.ultracodeRequested : undefined,
};
} catch {
return undefined;
}
}
/** The CLI supports ultracode on exactly the models that support `xhigh`
* effort (both use the same capability check), and `supportedEffortLevels`
* lists `xhigh` only for those models. E.g. Opus 4.7+, Sonnet 5+ and Haiku
* 5.5 qualify; Opus 4.6 and Haiku 4.5 do not. */
export function modelSupportsUltracode(modelInfo: ModelInfo | undefined): boolean {
return (
modelInfo?.supportsEffort === true &&
modelInfo.supportedEffortLevels?.includes("xhigh") === true
);
}
/** Whether dynamic workflows are enabled for the session, inferred from the
* CLI's `ultracodeAvailable` for the model the session started on. This is
* the part of availability that does not change with the model: the plan
* default (off on Pro), `enableWorkflows`/`disableWorkflows` and org policy.
* Undefined when the report can't tell, e.g. `ultracodeAvailable: false` on a
* model that lacks ultracode support anyway. */
export function ultracodeWorkflowsEnabled(
report: UltracodeSettingsReport | undefined,
modelInfo: ModelInfo | undefined,
): boolean | undefined {
if (report?.available === true) return true;
if (report?.available === false && modelSupportsUltracode(modelInfo)) return false;
return undefined;
}
/** Whether to offer the ultracode toggle for `modelInfo`. An unknown workflows
* state shows the toggle: the CLI keeps an unavailable request without
* error, so the toggle is merely inert there. */
export function ultracodeSupported(
workflowsEnabled: boolean | undefined,
modelInfo: ModelInfo | undefined,
): boolean {
return workflowsEnabled !== false && modelSupportsUltracode(modelInfo);
}
/** Ultracode state threaded into `buildConfigOptions`. The option is only
* surfaced while `supported`. */
export type UltracodeOptionState = {
supported: boolean;
enabled: boolean;
/** Whether the client opted into boolean config options. */
useBooleanOption: boolean;
};
/** Build the ultracode toggle: a native boolean for clients that opted into
* boolean config options, otherwise an "on"/"off" select. */
export function createUltracodeConfigOption(
enabled: boolean,
useBooleanOption: boolean,
): SessionConfigOption {
const base = {
id: ULTRACODE_CONFIG_ID,
name: "Ultracode",
description: ULTRACODE_DESCRIPTION,
category: "model_config",
} as const;
if (useBooleanOption) {
return { ...base, type: "boolean", currentValue: enabled };
}
return {
...base,
type: "select",
currentValue: enabled ? ULTRACODE_ON : ULTRACODE_OFF,
options: [
{ value: ULTRACODE_ON, name: "On" },
{ value: ULTRACODE_OFF, name: "Off" },
],
};
}
/** Read the requested ultracode value from a `session/set_config_option`
* request: a boolean, or the "on"/"off" select-fallback strings. */
export function resolveUltracodeEnabled(params: SetSessionConfigOptionRequest): boolean {
const value = params.value;
if (typeof value === "boolean") return value;
if (value === ULTRACODE_ON) return true;
if (value === ULTRACODE_OFF) return false;
throw RequestError.invalidParams(
undefined,
`Invalid value for config option ${ULTRACODE_CONFIG_ID}: ${value}`,
);
}

42
src/startup-failure.ts Normal file
View File

@@ -0,0 +1,42 @@
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { SDKStartupFailureReason } from "@anthropic-ai/claude-agent-sdk";
/** Makes the CLI write an `error_during_execution` result with a
* `startup_failure_reason` before it exits on a known startup failure. Without
* it, some failures (e.g. `proxy_invalid`) end with stderr and an exit code
* alone. The SDK then rejects `initializationResult()` with an error that
* carries the reason as `startupFailureReason`. */
export const STARTUP_FAILURE_RESULTS_ENV = { CLAUDE_CODE_STARTUP_FAILURE_RESULTS: "1" };
/** The SDK's prefix on the message of an error built from an error result. */
const ERROR_RESULT_PREFIX = "Claude Code returned an error result: ";
/** Reasons that signing in again, or with another account, can fix. */
const AUTH_REQUIRED_REASONS: ReadonlySet<SDKStartupFailureReason> = new Set([
"gateway_signin_required",
"org_pin_mismatch",
"org_config_refused",
]);
/** The ACP error for a CLI that refused to start, or `undefined` when `error`
* carries no `startupFailureReason` (older CLIs, failures without a known
* cause). The message is the CLI's own, which says what to fix, e.g.
* `Invalid proxy URL in HTTPS_PROXY: … Fix or unset HTTPS_PROXY and restart
* Claude Code.`; `data.startupFailureReason` carries the reason. */
export function startupFailureError(error: unknown): RequestError | undefined {
if (!(error instanceof Error)) return undefined;
const reason = (error as { startupFailureReason?: unknown }).startupFailureReason;
if (typeof reason !== "string" || reason === "") return undefined;
const message = error.message.startsWith(ERROR_RESULT_PREFIX)
? error.message.slice(ERROR_RESULT_PREFIX.length)
: error.message;
const data = { startupFailureReason: reason };
if (AUTH_REQUIRED_REASONS.has(reason as SDKStartupFailureReason)) {
return RequestError.authRequired(data, message);
}
if (reason === "cwd_unavailable") {
return RequestError.invalidParams(data, message);
}
return RequestError.internalError(data, message);
}

View File

@@ -24,6 +24,11 @@ export interface SubagentRecord {
background: boolean;
/** Whether the user stopped it. */
stoppedByUser: boolean;
/**
* Its working directory, when Claude Code placed it outside its parent's:
* in a worktree of its own or of an ancestor, or in a `cwd` of its own.
*/
cwd?: string;
}
/** Where the subagent transcripts of a session are. */
@@ -43,7 +48,9 @@ export interface SubagentHistory {
* Claude keeps the history of a subagent in its own transcript,
* `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
* The `agent-<id>.meta.json` file beside it names the launching tool use.
* The ids are empty for a session without a local transcript.
* For a child that Claude Code placed elsewhere, it has `worktreePath`,
* `inheritedWorktreePath` or `cwd`; then the child's working directory is the
* `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
*/
export async function subagentHistory(sessionId: string): Promise<SubagentHistory> {
const agents = new Map<string, SubagentRecord>();
@@ -67,6 +74,12 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
> | null;
if (typeof meta?.toolUseId !== "string") return;
const text = (value: unknown) => (typeof value === "string" ? value : undefined);
const placement = [meta.cwd, meta.worktreePath, meta.inheritedWorktreePath]
.map(text)
.find((value) => value && path.isAbsolute(value));
const cwd = placement
? ((await recordedCwd(path.join(directory, `agent-${match[1]!}.jsonl`))) ?? placement)
: undefined;
agents.set(meta.toolUseId, {
agentId: match[1]!,
name: text(meta.name),
@@ -74,6 +87,7 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
description: text(meta.description),
background: meta.requestShape === "background",
stoppedByUser: meta.stoppedByUser === true,
...(cwd ? { cwd } : {}),
});
} catch {
// A damaged meta file leaves its subagent without history.
@@ -87,6 +101,31 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
return dir ? { agents, dir } : { agents };
}
/**
* The `cwd` of the first record of the transcript `file` that has one, or
* undefined when the file cannot be read. Claude Code writes the working
* directory of the session or subagent on each record.
*/
async function recordedCwd(file: string): Promise<string | undefined> {
const stream = createReadStream(file, { encoding: "utf8" });
try {
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
if (!line.includes('"cwd"')) continue;
try {
const cwd = (JSON.parse(line) as { cwd?: unknown } | null)?.cwd;
if (typeof cwd === "string" && path.isAbsolute(cwd)) return cwd;
} catch {
// A damaged record; a later one can have the `cwd`.
}
}
} catch {
// An unreadable transcript; the caller falls back to the metadata's path.
} finally {
stream.destroy();
}
return undefined;
}
/** The folder of the subagent transcripts, next to the session transcript `transcript`. */
function subagentsDirectory(transcript: string, sessionId: string): string {
return path.join(path.dirname(transcript), sessionId, "subagents");
@@ -162,6 +201,7 @@ export async function subagentLineage(
agentId: current,
launchId: launch.launchId,
...(launcher !== null ? { parentAgentId: launcher } : {}),
...(launch.record.cwd ? { cwd: launch.record.cwd } : {}),
...(launch.record.name ? { name: launch.record.name } : {}),
...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
...(launch.record.description ? { description: launch.record.description } : {}),
@@ -202,7 +242,8 @@ async function readLaunchIdsFromTranscript(
const stream = createReadStream(file, { encoding: "utf8" });
try {
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
if (!line.includes('"tool_use"')) continue;
const forkDirective = owner !== null && line.includes(FORK_BOILERPLATE_OPEN);
if (!line.includes('"tool_use"') && !forkDirective) continue;
let record: {
type?: unknown;
isSidechain?: unknown;
@@ -214,6 +255,13 @@ async function readLaunchIdsFromTranscript(
} catch {
continue;
}
// Every record of a fork child's transcript before its directive message
// is inherited context: a copy of the launching message, whose launch
// calls are the parent's, not the fork's.
if (forkDirective && record !== null && isForkDirectiveMessage(record)) {
launches.clear();
continue;
}
if (record?.type !== "assistant") continue;
if (
owner === null
@@ -248,11 +296,72 @@ export async function readSubagentMessages(
try {
if (history.dir) {
const messages = await getSubagentMessages(sessionId, agentId, { dir: history.dir });
if (messages.length > 0) return messages;
if (messages.length > 0) return stripForkPrefix(messages);
}
return await getSubagentMessages(sessionId, agentId);
return stripForkPrefix(await getSubagentMessages(sessionId, agentId));
} catch (error) {
logger.error(`Failed to read the history of subagent ${agentId}:`, error);
return [];
}
}
/** How Claude Code opens the directive message of a `subagent_type: "fork"` child. */
const FORK_BOILERPLATE_OPEN = "<fork-boilerplate>";
/** What follows the boilerplate, right before the prompt of the Agent call. */
const FORK_DIRECTIVE_MARKER = "</fork-boilerplate>\nYour directive: ";
/** Whether `block` is the text block of a fork child's directive message. */
function isForkDirectiveText(block: unknown): block is { type: "text"; text: string } {
return (
typeof block === "object" &&
block !== null &&
(block as { type?: unknown }).type === "text" &&
typeof (block as { text?: unknown }).text === "string" &&
(block as { text: string }).text.startsWith(FORK_BOILERPLATE_OPEN)
);
}
/** Whether the transcript record `message` is the directive message of a fork child. */
function isForkDirectiveMessage(message: { type?: unknown; message?: unknown }): boolean {
if (message.type !== "user") return false;
const content = (message.message as { content?: unknown } | null | undefined)?.content;
return Array.isArray(content) && content.some(isForkDirectiveText);
}
/**
* The transcript of a `subagent_type: "fork"` child without the context it
* inherited. A fork transcript starts with a copy of the parent's launching
* assistant message (its text, thinking and every tool_use, including the
* Agent call itself), then a user message with a placeholder tool_result for
* each of those tool_uses and the text
* `<fork-boilerplate>…</fork-boilerplate>\nYour directive: <prompt>`. Since
* Claude Code 2.1.307 the fork's own Agent call gets "Fork started — that fork
* is you." and the other tool_uses get "This is the parent's call, not
* yours. …"; before that, every one got "Fork started — processing in
* background". Only the directive text is matched here. A fork
* launched from inside a subagent also gets a copy of that subagent's history
* before them. None of it is the child's own work: replayed as is, the
* parent's text and tool calls would show again in the child.
*
* This drops every record before the directive message, and reduces that
* message to the prompt. Other transcripts are returned unchanged.
*/
export function stripForkPrefix(messages: SessionMessage[]): SessionMessage[] {
const index = messages.findIndex(isForkDirectiveMessage);
if (index === -1) return messages;
const directive = messages[index];
const content = (directive.message as { content: unknown[] }).content;
const prompt = content.flatMap((block) => {
if (!isForkDirectiveText(block)) return [];
const start = block.text.indexOf(FORK_DIRECTIVE_MARKER);
return start === -1
? []
: [{ type: "text", text: block.text.slice(start + FORK_DIRECTIVE_MARKER.length) }];
});
const rest = messages.slice(index + 1);
if (prompt.length === 0) return rest;
return [
{ ...directive, message: { ...(directive.message as object), content: prompt } },
...rest,
];
}

View File

@@ -6,19 +6,50 @@
* `subagentHistory`). The replay announces each child right after that call,
* under the session id it had live, the SDK agent id, and sends the child's
* history to the child session. Every child's end goes out before the replay
* ends, since no subagent from before the reload still runs.
* ends. These states are history to the client, so each child's current
* state goes out again after the load response (`currentStates`).
*
* As live, the parent is not shown the Agent or Task call of a child that the
* replay announces: the launch's prompt is the parent's outgoing message, and
* a foreground child's answer is its reply to the parent. A child's
* successful SubagentHandback call is its message to the parent instead.
* Unlike live, the replay does not tell runs apart, so a child that handed
* back any report gets no reply from its launch's result.
*
* A SendMessage call whose message the replay delivered to a child before it
* reached the call is that message too: the sender's outgoing view, at the
* call, and the child's incoming one, in the child's history. A child's
* history follows its launch, so a call to a child from its parent is always
* delivered first. Any other SendMessage call stays a tool call.
*
* Right after its announcement, each child gets its `model` option: the model
* of its first response, in its transcript. A later response with another
* model sends it again. A child without responses gets none. After the load
* response, a model goes again only when the session's model list, which the
* replay can lack, maps it to another entry.
*
* A launch without metadata has no known id, so it is not announced: the RFD
* forbids inventing one. Its Agent call stays an ordinary tool call of the
* parent, and a notice says that subagent history is missing.
*/
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
import { statSync } from "node:fs";
import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
import {
childModelOption,
HANDBACK_TOOL,
handbackMessage,
isNativeSubagentControlTool,
launchReply,
linkedSessionMessages,
type ModelEntry,
nativeSubagentState,
responseModelEntry,
rfdEndState,
rfdLabels,
sameDirectory,
type SubagentModels,
type WorkState,
} from "./native-subagents.js";
import { noticeUpdate } from "./session-notices.js";
@@ -42,7 +73,15 @@ export type ReplayChild = {
/** The state of the child's last persisted task notification. */
notified?: SubagentState;
announced: boolean;
/** The `cwd` that the replay reported for it; undefined while it inherits its parent's. */
cwd?: string;
promptSent: boolean;
/** Whether it handed a report to its parent with a SubagentHandback call. */
handedBack?: boolean;
/** The `model` option entry that the replay sent. */
model?: ModelEntry;
/** The model id of the latest response in its transcript. */
responseModel?: string;
};
type SendMessageCall = {
@@ -50,7 +89,12 @@ type SendMessageCall = {
to: string;
message: string;
senderSessionId: string;
delivered: boolean;
/** The child the replay delivered the message to. */
recipientSessionId?: string;
/** The replay reached the call before the delivery, so it stays a tool call. */
shownAsToolCall: boolean;
/** The sender's view of the message went out, at the call. */
outgoingSent: boolean;
};
export class RfdSubagentReplay {
@@ -62,10 +106,24 @@ export class RfdSubagentReplay {
private readonly omitted = new Set<string>();
/** Children whose transcript could not be read: only their association is replayed. */
private withoutHistory = 0;
/** The SubagentHandback calls, with their report and whether their result failed. */
private readonly handbacks = new Map<
string,
{ message: string; failed: boolean; sent: boolean }
>();
constructor(
private readonly history: SubagentHistory,
private readonly publish: Publish,
private readonly models?: SubagentModels,
/**
* Whether the replay maps a model to the session's model list as it goes.
* A load lacks the list until the CLI started, so it maps only in
* {@link currentStates}, after the load response.
*/
private readonly mapsDuringReplay = true,
/** The working directory of the root session, which a child inherits unless it reports its own. */
private readonly rootCwd?: string,
) {}
/**
@@ -78,6 +136,9 @@ export class RfdSubagentReplay {
if (block.type === "tool_use" && typeof block.id === "string") {
if (isNativeSubagentControlTool(block.name)) {
this.registerLaunch(block.id, inputOf(block), ownerSessionId);
} else if (block.name === HANDBACK_TOOL) {
const message = handbackMessage(block.input);
if (message) this.handbacks.set(block.id, { message, failed: false, sent: false });
} else if (block.name === "SendMessage") {
const input = inputOf(block);
if (typeof input.to === "string" && typeof input.message === "string") {
@@ -86,11 +147,14 @@ export class RfdSubagentReplay {
to: input.to,
message: input.message,
senderSessionId: ownerSessionId,
delivered: false,
shownAsToolCall: false,
outgoingSent: false,
});
}
}
} else if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
const handback = this.handbacks.get(block.tool_use_id);
if (handback && block.is_error === true) handback.failed = true;
const child = this.children.get(block.tool_use_id);
// A background launch answers at once; its result is not the end.
if (child && !child.record.background) {
@@ -106,14 +170,25 @@ export class RfdSubagentReplay {
return this.children.get(toolUseId);
}
/** Announces `child` on its parent, as live: running, with the parent's labels. */
async announce(child: ReplayChild): Promise<void> {
/**
* Announces `child` on its parent, as live: running, with the parent's
* labels, and with its recorded `cwd` when that differs from its parent's.
* Then it sends `firstModel`, the model of the child's first response, when
* the transcript has one.
*/
async announce(child: ReplayChild, firstModel?: string): Promise<void> {
if (child.announced) return;
const labels = rfdLabels(
child.record.name ?? child.input.name,
child.record.agentType ?? child.input.subagent_type,
child.record.description ?? child.input.description,
);
const inherited = this.effectiveCwd(child.parentSessionId);
const cwd =
child.record.cwd &&
(inherited === undefined || !(await sameDirectory(child.record.cwd, inherited)))
? child.record.cwd
: undefined;
await this.publish({
sessionId: child.parentSessionId,
update: {
@@ -121,10 +196,51 @@ export class RfdSubagentReplay {
sessionId: child.sessionId,
...(labels.title ? { title: labels.title } : {}),
...(labels.description ? { description: labels.description } : {}),
...(cwd ? { cwd } : {}),
state: { state: "running" },
},
});
child.cwd = cwd;
child.announced = true;
await this.responseModel(child, firstModel);
}
/**
* The working directory of the session `sessionId` as the client holds it:
* a child's reported `cwd`, else its parent's, up to the root's.
*/
private effectiveCwd(sessionId: string): string | undefined {
const seen = new Set<string>();
let child = this.byAgentId.get(sessionId);
while (child && !seen.has(child.sessionId)) {
if (child.cwd) return child.cwd;
seen.add(child.sessionId);
child = this.byAgentId.get(child.parentSessionId);
}
return this.rootCwd;
}
/**
* Takes the model of a replayed response of the child `owner`. A child
* whose model changes gets its `model` option again.
*/
async responseModel(owner: ReplayChild, model: string | undefined): Promise<void> {
const entry = responseModelEntry(model, this.mapsDuringReplay ? this.models : undefined);
if (!entry || !owner.announced) return;
owner.responseModel = model;
await this.sendModel(owner, entry);
}
private async sendModel(child: ReplayChild, model: ModelEntry): Promise<void> {
if (child.model?.value === model.value && child.model.name === model.name) return;
child.model = model;
await this.publish({
sessionId: child.sessionId,
update: {
sessionUpdate: "config_option_update",
configOptions: [childModelOption(model)],
},
});
}
/**
@@ -167,6 +283,70 @@ export class RfdSubagentReplay {
}
}
/**
* Takes a replayed update of a tool call, from the transcript of `owner`
* when it is a child's. Returns whether the client is not shown it: an
* update of the launch of a child that the replay shows, of a SendMessage
* call whose message the replay already delivered, or of a successful
* SubagentHandback call of `owner`. A foreground launch's completed result
* goes out as the child's reply, a delivered SendMessage as the sender's
* outgoing message, and a hand-back as the child's message to its parent.
*/
async toolCallUpdate(
update: AcpSessionNotification["update"],
owner: ReplayChild | undefined,
): Promise<boolean> {
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
return false;
}
const sendMessage = this.sendMessages.find((call) => call.toolUseId === update.toolCallId);
if (sendMessage) {
if (sendMessage.recipientSessionId === undefined) {
sendMessage.shownAsToolCall = true;
return false;
}
if (!sendMessage.outgoingSent) {
sendMessage.outgoingSent = true;
const [outgoing] = linkedSessionMessages(
sendMessage.toolUseId,
sendMessage.senderSessionId,
sendMessage.recipientSessionId,
sendMessage.message,
);
if (outgoing) await this.publish(outgoing);
}
return true;
}
const handback = owner ? this.handbacks.get(update.toolCallId) : undefined;
if (owner && handback && !handback.failed) {
if (!handback.sent) {
handback.sent = true;
owner.handedBack = true;
for (const view of linkedSessionMessages(
update.toolCallId,
owner.sessionId,
owner.parentSessionId,
handback.message,
)) {
await this.publish(view);
}
}
return true;
}
const child = this.children.get(update.toolCallId);
if (!child) return false;
if (
update.status === "completed" &&
child.announced &&
!child.record.background &&
!child.handedBack
) {
const reply = launchReply(child.launchId, child.sessionId, child.parentSessionId, update);
if (reply) await this.publish(reply);
}
return true;
}
/**
* Takes a persisted task notification. Returns whether it was a child's:
* its task id is the agent id.
@@ -179,19 +359,19 @@ export class RfdSubagentReplay {
}
/**
* Sends the end of every announced child, and a notice when subagent history
* is missing. The end is the state of the child's last task notification,
* else `cancelled` for a child the user stopped, else the result of its
* foreground launch, else `idle` with no stop reason.
* Sends the end of every announced child (see `endState`), and a notice when
* subagent history is missing.
*/
async finish(supportsNotices: boolean, rootSessionId: string): Promise<void> {
for (const child of [...this.children.values()].reverse()) {
if (!child.announced) continue;
const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
const state: WorkState = end ? rfdEndState(end, undefined) : { state: "idle" };
await this.publish({
sessionId: child.parentSessionId,
update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, state },
update: {
sessionUpdate: "subagent_update",
sessionId: child.sessionId,
state: endState(child),
},
});
}
const missing = this.omitted.size + this.withoutHistory;
@@ -210,6 +390,61 @@ export class RfdSubagentReplay {
}
}
/**
* The current state of each announced child, to send after the load
* response. The RFD makes that response the freshness boundary: a client
* treats the replayed states before it as history, so they show no current
* activity and allow no cancel.
*
* `live` gives the work state of a child that still runs in this process, as
* when a live session is loaded again. Such a child reports that state and
* can be cancelled, as live. Any other child cannot be running, because the
* process that ran it is gone, so its replayed end is its current state.
*
* A replayed `cwd` whose directory is gone, such as a worktree that Claude
* Code removed when the child finished, is cleared with `cwd: null`, as the
* RFD requires after a reconnection.
*/
currentStates(
live: (sessionId: string) => WorkState["state"] | undefined,
liveConfiguration?: (sessionId: string) => SessionConfigOption[] | undefined,
): AcpSessionNotification[] {
return [...this.children.values()]
.filter((child) => child.announced)
.flatMap((child): AcpSessionNotification[] => {
const liveState = live(child.sessionId);
const state: WorkState =
liveState === undefined || liveState === "idle" ? endState(child) : { state: liveState };
// The session's model list may be known only now, after the load response.
const entry = responseModelEntry(child.responseModel, this.models);
const replayed = child.model ? JSON.stringify([childModelOption(child.model)]) : undefined;
const current =
liveConfiguration?.(child.sessionId) ?? (entry ? [childModelOption(entry)] : undefined);
// A configuration that the replay already sent does not go again.
const configOptions = current && JSON.stringify(current) !== replayed ? current : undefined;
return [
{
sessionId: child.parentSessionId,
update: {
sessionUpdate: "subagent_update",
sessionId: child.sessionId,
...(liveState !== undefined ? { capabilities: { cancel: {} } } : {}),
...(child.cwd && !isDirectory(child.cwd) ? { cwd: null } : {}),
state,
},
},
...(configOptions
? [
{
sessionId: child.sessionId,
update: { sessionUpdate: "config_option_update" as const, configOptions },
},
]
: []),
];
});
}
private registerLaunch(
toolUseId: string,
input: Record<string, unknown>,
@@ -234,10 +469,17 @@ export class RfdSubagentReplay {
this.byAgentId.set(record.agentId, child);
}
/** The prompt of the launch, with the launch's id, as live. */
/** The prompt of the launch, with the launch's id, to the parent and the child, as live. */
private async sendPrompt(child: ReplayChild, text: string): Promise<void> {
child.promptSent = true;
await this.sendMessage(child, child.launchId, text, child.parentSessionId);
for (const view of linkedSessionMessages(
child.launchId,
child.parentSessionId,
child.sessionId,
text,
)) {
await this.publish(view);
}
}
/**
@@ -247,6 +489,9 @@ export class RfdSubagentReplay {
* first undelivered SendMessage to the child whose message the delivered
* text contains. With that call, the message has the call's id and text and
* its sender, as live; without it, its transcript id and the delivered text.
* Unless the replay already showed the call as a tool call, the message
* replaces it, so this view names the sender's view, which goes out at the
* call (see {@link toolCallUpdate}).
*/
private async sendDelivery(
child: ReplayChild,
@@ -256,13 +501,15 @@ export class RfdSubagentReplay {
const names = [child.sessionId, child.record.name, child.input.name];
const call = this.sendMessages.find(
(candidate) =>
!candidate.delivered &&
candidate.recipientSessionId === undefined &&
names.includes(candidate.to) &&
text.includes(candidate.message.trim()),
);
if (call) {
call.delivered = true;
await this.sendMessage(child, call.toolUseId, call.message, call.senderSessionId);
call.recipientSessionId = child.sessionId;
await this.sendMessage(child, call.toolUseId, call.message, call.senderSessionId, {
linked: !call.shownAsToolCall,
});
} else {
await this.sendMessage(child, message.uuid, text, undefined);
}
@@ -273,6 +520,7 @@ export class RfdSubagentReplay {
messageId: string,
text: string,
sender: string | undefined,
{ linked = false }: { linked?: boolean } = {},
): Promise<void> {
await this.publish({
sessionId: child.sessionId,
@@ -281,12 +529,23 @@ export class RfdSubagentReplay {
messageId,
...(sender ? { senderSessionId: sender } : {}),
recipientSessionId: child.sessionId,
...(linked ? { senderMessageId: messageId } : {}),
content: [{ type: "text", text }],
},
});
}
}
/** The model of the first response in `messages`, a subagent's transcript. */
export function firstResponseModel(messages: readonly SessionMessage[]): string | undefined {
for (const message of messages) {
if (message.type !== "assistant") continue;
const model = (message.message as { model?: unknown } | undefined)?.model;
if (typeof model === "string" && model && model !== "<synthetic>") return model;
}
return undefined;
}
/**
* The end of a foreground launch from its tool result: an error result that
* names a stop is a cancellation, any other error a failure.
@@ -301,6 +560,16 @@ export function replaySubagentTerminalState(
: "failed";
}
/**
* How `child`'s work ended: the state of its last task notification, else
* `cancelled` for a child the user stopped, else the result of its foreground
* launch, else `idle` with no stop reason.
*/
function endState(child: ReplayChild): WorkState {
const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
return end ? rfdEndState(end, undefined) : { state: "idle" };
}
function replayContentText(value: unknown, seen = new Set<unknown>()): string {
if (typeof value === "string") return value;
if (typeof value !== "object" || value === null || seen.has(value)) return "";
@@ -340,6 +609,15 @@ function textOf(content: unknown): string {
.join("\n");
}
/** Whether `dir` is an existing directory. */
function isDirectory(dir: string): boolean {
try {
return statSync(dir).isDirectory();
} catch {
return false;
}
}
/** A message that a SendMessage call delivered to the child. */
function isDelivery(message: SessionMessage): boolean {
return (message as { origin?: { kind?: unknown } | null }).origin?.kind === "coordinator";

75
src/task-notifications.ts Normal file
View File

@@ -0,0 +1,75 @@
/**
* The `<task-notification>` inputs that Claude Code writes to a transcript
* when a background task stops. The replay of a session (`acp-agent.ts`) and
* the session index (`session-index/background-tasks.ts`) read them here, so
* both read the same fields.
*/
export const TASK_NOTIFICATION_TAG = "task-notification";
/** The text between the first `<tag>` and the next `</tag>` after it. */
export function markerTagText(text: string, tag: string): string | undefined {
const open = `<${tag}>`;
const close = `</${tag}>`;
const start = text.indexOf(open);
if (start === -1) return undefined;
const end = text.indexOf(close, start + open.length);
if (end === -1) return undefined;
return text.slice(start + open.length, end);
}
/** The fields of a persisted `<task-notification>`, in the shape of the SDK
* `task_notification` frame that reported the same stop live. */
export type PersistedTaskNotification = {
task_id: string;
status: string;
tool_use_id?: string;
summary?: string;
output_file?: string;
};
/**
* Returns each `<task-notification>` in user-message content (a string or its
* text blocks). The `<result>` body is model context that the live frame does
* not carry, so the scan stops at it and a tag quoted in it cannot match.
*
* Only a notification with a `<task-id>` and a `<status>` is returned. A
* notification without a status comes while its task still runs: the
* check-in of a quiet shell ("is still running after …") or a `Monitor`
* event.
*/
export function taskNotificationsOf(content: unknown): PersistedTaskNotification[] {
const texts =
typeof content === "string"
? [content]
: Array.isArray(content)
? content.flatMap((block) =>
block?.type === "text" && typeof block.text === "string" ? [block.text as string] : [],
)
: [];
const open = `<${TASK_NOTIFICATION_TAG}>`;
const close = `</${TASK_NOTIFICATION_TAG}>`;
const notifications: PersistedTaskNotification[] = [];
for (const text of texts) {
let start = text.indexOf(open);
while (start !== -1) {
const end = text.indexOf(close, start + open.length);
if (end === -1) break;
const body = text.slice(start + open.length, end).split("<result>", 1)[0];
const field = (tag: string) => markerTagText(body, tag)?.trim() || undefined;
const taskId = field("task-id");
const status = field("status");
if (taskId && status) {
notifications.push({
task_id: taskId,
status,
tool_use_id: field("tool-use-id"),
summary: field("summary"),
output_file: field("output-file"),
});
}
start = text.indexOf(open, end + close.length);
}
}
return notifications;
}

View File

@@ -464,6 +464,16 @@ describe("ClaudeAcpAgent settings", () => {
expect(response.modes.currentModeId).toBe("plan");
});
it("uses the available configured mode when the restored mode is not in the catalog", async () => {
const { permissionMode, response } = await resume([user("dontAsk")]);
expect(permissionMode).toBe("plan");
expect(response.modes.currentModeId).toBe("plan");
expect(response.configOptions.find((option: any) => option.id === "mode").currentValue).toBe(
"plan",
);
});
it("uses permissions.defaultMode when bypass is not available now", async () => {
const { permissionMode, response } = await resume([user("bypassPermissions")], {
claudeCode: { options: { allowDangerouslySkipPermissions: false } },
@@ -515,21 +525,22 @@ describe("ClaudeAcpAgent settings", () => {
_meta: { disableBuiltInTools: true },
});
await agent.setSessionMode({ sessionId, modeId: "default" });
const { RequestError } = await import("@agentclientprotocol/sdk");
const { RequestError } = await import("@agentclientprotocol/sdk/unstable");
const createSession = (agent as any).createSession.bind(agent);
const creations: object[] = [];
(agent as any).createSession = async (params: object, options: { resume?: string }) => {
creations.push(options);
// The CLI never wrote the conversation, so the resume fails.
if (options.resume) throw RequestError.resourceNotFound(sessionId);
if (options.resume) throw RequestError.sessionNotFound(sessionId);
return createSession(params, options);
};
await (agent as any).recreateSignedOutQuery(sessionId, (agent as any).sessions[sessionId]);
const husk = (agent as any).sessions[sessionId];
await (agent as any).recreateSignedOutQuery(sessionId, husk);
expect(creations).toEqual([
{ resume: sessionId, permissionMode: "default" },
{ reuseSessionId: sessionId, permissionMode: "default" },
{ resume: sessionId, permissionMode: "default", replacingSession: husk },
{ reuseSessionId: sessionId, permissionMode: "default", replacingSession: husk },
]);
expect(getCapturedOptions().permissionMode).toBe("default");
});

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
...actual,
query: harness.mockedQuery,
getSessionMessages: harness.mockedSessionMessages,
getSessionInfo: harness.mockedSessionInfo,
};
});

View File

@@ -1,4 +1,5 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolName":"SandboxNetworkAccess"}},"content":[],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"sessionUpdate":"tool_call","status":"pending","title":"SandboxNetworkAccess","toolCallId":"toolu_net"}}}
{"kind":"requestPermission","payload":{"_meta":{"jetbrains":{"air":{"permission":{"description":"Reason: Sandbox blocks network","title":"example.com","version":1},"version":1}}},"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","toolCall":{"rawInput":{"host":"example.com"},"title":"example.com","toolCallId":"toolu_net"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_net"}}}
{"kind":"promptResponse","payload":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}

View File

@@ -1,4 +1,4 @@
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Check the file.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_thought_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}}
@@ -8,8 +8,10 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"terminal_output_delta":{"data":"app.ts","terminal_id":"toolu_r_bash"}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_r_bash"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"terminal_exit":{"exit_code":0,"signal":null,"terminal_id":"toolu_r_bash"}},"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_r_bash"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"rawOutput":"The file <cwd>/src/app.ts has been updated successfully.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_r_edit"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>:replay-subagent:toolu_r_task","update":{"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"claude-sonnet-4-6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>:replay-subagent:toolu_r_task","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_r_task"}},"content":{"text":"Subagent text.","type":"text"},"messageId":"msg_r_sub","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Fixed.","type":"text"},"messageId":"msg_r2","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"sessionUpdate":"subagent_state_update","state":"completed","subagentSessionId":"<session>:replay-subagent:toolu_r_task"}}}
{"kind":"loadSession","payload":{"configOptions":[{"category":"mode","currentValue":"default","description":"Session permission mode","id":"mode","name":"Mode","options":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","name":"Manual","value":"default"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","id":"model","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"modes":{"availableModes":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","id":"default","name":"Manual"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","id":"acceptEdits","name":"Accept edits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","id":"plan","name":"Plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","id":"auto","name":"Auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","id":"bypassPermissions","name":"Bypass permissions"}],"currentModeId":"default"},"sessionId":"<session>"}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>:replay-subagent:toolu_r_task","update":{"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}

View File

@@ -1,3 +1,3 @@
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"newSession","payload":{"configOptions":[{"category":"mode","currentValue":"default","description":"Session permission mode","id":"mode","name":"Mode","options":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","name":"Manual","value":"default"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","id":"model","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"modes":{"availableModes":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","id":"default","name":"Manual"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","id":"acceptEdits","name":"Accept edits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","id":"plan","name":"Plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","id":"auto","name":"Auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","id":"bypassPermissions","name":"Bypass permissions"}],"currentModeId":"default"},"sessionId":"<session>"}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}

View File

@@ -2,6 +2,7 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Background review","prompt":"Review the diff","sessionUpdate":"subagent_spawned","subagentSessionId":"a2","task":"Review the diff"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"sessionUpdate":"subagent_state_update","state":"completed","subagentSessionId":"a2"}}}

View File

@@ -2,6 +2,7 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Check the logs","prompt":"Check logs","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_late","task":"Check logs"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","sessionUpdate":"tool_call","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_late","update":{"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"sessionUpdate":"subagent_state_update","state":"completed","subagentSessionId":"agent_late"}}}

View File

@@ -2,6 +2,7 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Fix the build","prompt":"Make the build pass","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_n","task":"Make the build pass"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"Running t","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"he build.","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native","toolName":"Grep"}},"content":[],"kind":"search","name":"Grep","sessionUpdate":"tool_call","status":"pending","title":"grep","toolCallId":"toolu_native_grep"}}}

View File

@@ -2,7 +2,9 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Plan the work","prompt":"Plan it","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_outer","task":"Plan it"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_outer","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_outer","update":{"capabilities":{},"name":"Read the spec","prompt":"Read spec.md","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_inner","task":"Read spec.md"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_inner","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_inner","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner","toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","sessionUpdate":"tool_call","status":"pending","title":"Read File","toolCallId":"toolu_grandchild_read"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_inner","update":{"locations":[{"line":1,"path":"/spec.md"}],"rawInput":{"file_path":"/spec.md"},"sessionUpdate":"tool_call_update","title":"Read /spec.md","toolCallId":"toolu_grandchild_read"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_inner","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_grandchild_read"}}}

View File

@@ -2,6 +2,7 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Explore code","prompt":"Find the parser","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_1","task":"Find the parser"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Looking for","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":" the parser.","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task","toolName":"Read"}},"content":[],"kind":"read","locations":[],"name":"Read","sessionUpdate":"tool_call","status":"pending","title":"Read File","toolCallId":"toolu_sub_read"}}}

View File

@@ -2,6 +2,7 @@
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Summarize","prompt":"Summarize the repo","sessionUpdate":"subagent_spawned","subagentSessionId":"agent_t","task":"Summarize the repo"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"configOptions":[{"category":"model","currentValue":"claude-sonnet-4-6","id":"model","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"Look at t","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"he layout.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
{"kind":"sessionUpdate","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"The repo has ","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}}

View File

@@ -1,15 +1,15 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"AskUserQuestion","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Asking for your input","toolCallId":"toolu_ask"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Which database?","type":"text"},"type":"content"}],"rawInput":{"questions":[{"header":"Database","multiSelect":false,"options":[{"description":"Relational","label":"Postgres","preview":"CREATE TABLE t ();"},{"description":"Key-value","label":"Redis"}],"question":"Which database?"}]},"sessionUpdate":"tool_call_update","title":"Which database?","toolCallId":"toolu_ask"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"elicitation/create","payload":{"message":"Which database?","mode":"form","requestedSchema":{"properties":{"question_0":{"oneOf":[{"_meta":{"_claude/askUserQuestionOption":{"preview":"CREATE TABLE t ();"}},"const":"Postgres","description":"Relational","title":"Postgres"},{"const":"Redis","description":"Key-value","title":"Redis"}],"title":"Database","type":"string"},"question_0_custom":{"description":"Type your own answer, or add a note to the option you chose above (optional).","title":"Other","type":"string"}},"type":"object"},"sessionId":"<session>","toolCallId":"toolu_ask"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"User has answered your questions: \"Which database?\"=\"Postgres\".","type":"text"},"type":"content"}],"rawOutput":"User has answered your questions: \"Which database?\"=\"Postgres\".","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_ask"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,6 +1,6 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,6 +1,6 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,6 +1,6 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bg"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bg","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bg"}}}
@@ -15,4 +15,4 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_bg"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_bg"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bash"}}}
@@ -14,4 +14,4 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":1},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bash"}}}
@@ -10,9 +10,9 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"ls -la"},"sessionUpdate":"tool_call_update","title":"ls -la","toolCallId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"ls -la","description":"List files"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"ls -la","description":"List files"},"status":"pending","title":"ls -la","toolCallId":"toolu_bash"},"type":"tool_call"},"title":"ls -la"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"ls -la","description":"List files"},"title":"ls -la","toolCallId":"toolu_bash"},"type":"tool_call"},"title":"ls -la"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"output":{"data":"YS50cwpiLnRz"},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_bash"}}}
@@ -20,4 +20,4 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"ne.","type":"text"},"messageId":"msg_done","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","error":"Context too small","sessionUpdate":"compaction_update","status":"failed"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,11 +1,11 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","content":{"text":"The summary.","type":"text"},"sessionUpdate":"compaction_summary_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"completed"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,11 +1,11 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","content":{"text":"The summary.","type":"text"},"sessionUpdate":"compaction_summary_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"completed"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/src/app.ts"}],"rawInput":{"file_path":"<cwd>/src/app.ts"},"sessionUpdate":"tool_call_update","title":"Edit src/app.ts","toolCallId":"toolu_edit"}}}
@@ -10,9 +10,9 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"value = 2","old_string":"value = 1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1 +1 @@\n-const value = 1;\n+const value = 2;\n"},"type":"diff"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1 +1 @@\n-const value = 1;\n+const value = 2;\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"value = 2","old_string":"value = 1"},"status":"pending","title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1 +1 @@\n-const value = 1;\n+const value = 2;\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"value = 2","old_string":"value = 1"},"title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\nThe user doesn't want to proceed with this tool use. The tool use was rejected.\n```","type":"text"},"type":"content"}],"rawOutput":"The user doesn't want to proceed with this tool use. The tool use was rejected.","sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/src/app.ts"}],"rawInput":{"file_path":"<cwd>/src/app.ts"},"sessionUpdate":"tool_call_update","title":"Edit src/app.ts","toolCallId":"toolu_edit"}}}
@@ -10,10 +10,10 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"const value = 2;","old_string":"const value = 1;"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1,3 +1,3 @@\n line 1\n-const value = 1;\n+const value = 2;\n line 3\n"},"type":"diff"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1,3 +1,3 @@\n line 1\n-const value = 1;\n+const value = 2;\n line 3\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"const value = 2;","old_string":"const value = 1;"},"status":"pending","title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1,3 +1,3 @@\n line 1\n-const value = 1;\n+const value = 2;\n line 3\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"const value = 2;","old_string":"const value = 1;"},"title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"The file <cwd>/src/app.ts has been updated successfully.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/src/app.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,17 +1,17 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"rawInput":{"plan":"# Plan\n1. Do it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"User has approved your plan. You can now start coding.\n\n## Approved Plan:\n# Plan\n1. Do it","sessionUpdate":"tool_call_update","status":"completed","title":"Exited Plan Mode","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"content":"# Plan\n1. Do it","planId":"plan-mode","type":"markdown"},"sessionUpdate":"plan_update"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,15 +1,15 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"rawInput":{"plan":"# Plan\n1. Do it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n```\nThe user doesn't want to proceed with this tool use.\n```\n```","type":"text"},"type":"content"}],"rawOutput":"The user doesn't want to proceed with this tool use.","sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_plan"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,6 +1,6 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"/goal Ship the feature","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"search","name":"Grep","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"grep","toolCallId":"toolu_grep"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"value"},"sessionUpdate":"tool_call_update","title":"grep \"value\"","toolCallId":"toolu_grep"}}}
@@ -16,4 +16,4 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"**/*.ts"},"sessionUpdate":"tool_call_update","title":"Find `**/*.ts`","toolCallId":"toolu_glob"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"src/app.ts\nsrc/b.ts","type":"text"},"type":"content"}],"rawOutput":"src/app.ts\nsrc/b.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_glob"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,15 +1,15 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"mcp__docs__search","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"mcp__docs__search","toolCallId":"toolu_mcp"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"q":"acp"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_mcp"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"mcpServer":{"name":"docs","source":"project"}}},"content":[],"kind":"other","name":"mcp__docs__search","rawInput":{"q":"acp"},"status":"pending","title":"mcp__docs__search","toolCallId":"toolu_mcp"},"type":"tool_call"},"title":"mcp__docs__search"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"mcpServer":{"name":"docs","source":"project"}}},"content":[],"kind":"other","name":"mcp__docs__search","rawInput":{"q":"acp"},"title":"mcp__docs__search","toolCallId":"toolu_mcp"},"type":"tool_call"},"title":"mcp__docs__search"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Found 2 documents.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Found 2 documents.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_mcp"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"mode":"select"}}},"kind":"read","locations":[{"path":"/memory/a.md"},{"path":"/memory/b.md"}],"name":"memory_recall","sessionUpdate":"tool_call_update","status":"completed","title":"Recalled 2 memories","toolCallId":"00000000-0000-4000-8000-000000000001"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"mode":"synthesize"}}},"content":[{"content":{"text":"The user prefers tabs.","type":"text"},"type":"content"}],"kind":"read","name":"memory_recall","sessionUpdate":"tool_call_update","status":"completed","title":"Recalled synthesized memory","toolCallId":"00000000-0000-4000-8000-000000000002"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,10 +1,11 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"sessionUpdate":"tool_call_update","status":"pending","title":"SandboxNetworkAccess","toolCallId":"toolu_net"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"description":"Reason: Sandbox blocks network","options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"```json\n{\n \"host\": \"example.com\"\n}\n```","type":"text"},"type":"content"}],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"status":"pending","title":"example.com","toolCallId":"toolu_net"},"type":"tool_call"},"title":"example.com"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"description":"Reason: Sandbox blocks network","options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"```json\n{\n \"host\": \"example.com\"\n}\n```","type":"text"},"type":"content"}],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"title":"example.com","toolCallId":"toolu_net"},"type":"tool_call"},"title":"example.com"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_net"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"NotebookEdit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"NotebookEdit","toolCallId":"toolu_nb"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
@@ -10,9 +10,9 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"cell_id":"cell-1","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"cell_id":"cell-1","edit_mode":"replace","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[],"kind":"other","name":"NotebookEdit","rawInput":{"cell_id":"cell-1","edit_mode":"replace","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"status":"pending","title":"NotebookEdit","toolCallId":"toolu_nb"},"type":"tool_call"},"title":"NotebookEdit"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[],"kind":"other","name":"NotebookEdit","rawInput":{"cell_id":"cell-1","edit_mode":"replace","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"title":"NotebookEdit","toolCallId":"toolu_nb"},"type":"tool_call"},"title":"NotebookEdit"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Updated cell cell-1 with print('hi')","type":"text"},"type":"content"}],"rawOutput":"Updated cell cell-1 with print('hi')","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_nb"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,8 +1,8 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_denied"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_denied","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_denied"}}}
@@ -14,4 +14,4 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_denied"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_denied","type":"terminal"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_denied"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,12 +1,12 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Work","type":"text"},"messageId":"msg_rate","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"ing.","type":"text"},"messageId":"msg_rate","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6","_claude/rateLimit":{"resetsAt":1700000000,"status":"allowed_warning","utilization":0.9}},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6","_claude/origin":{"kind":"human"}},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,12 +1,12 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/a.ts"}],"rawInput":{"file_path":"<cwd>/a.ts"},"sessionUpdate":"tool_call_update","title":"Read a.ts","toolCallId":"toolu_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n1\tconst a = 1;\n```","type":"text"},"type":"content"}],"rawOutput":"1\tconst a = 1;\n<system-reminder>x</system-reminder>","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -6,9 +6,9 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r1","sessionUpdate":"agent_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"command":"ls","sessionUpdate":"terminal_update","terminalId":"toolu_r_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_r_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"ls","description":"List"},"sessionUpdate":"tool_call_update","status":"pending","title":"ls","toolCallId":"toolu_r_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"b","old_string":"a"},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit src/app.ts","toolCallId":"toolu_r_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Look around","type":"text"},"type":"content"}],"kind":"think","name":"Task","rawInput":{"description":"Explore","prompt":"Look around"},"sessionUpdate":"tool_call_update","status":"pending","title":"Explore","toolCallId":"toolu_r_task"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":null,"content":[{"terminalId":"toolu_r_bash","type":"terminal"}],"kind":"execute","locations":null,"name":"Bash","rawInput":{"command":"ls","description":"List"},"rawOutput":null,"sessionUpdate":"tool_call_update","status":"pending","title":"ls","toolCallId":"toolu_r_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":null,"content":[],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"b","old_string":"a"},"rawOutput":null,"sessionUpdate":"tool_call_update","status":"pending","title":"Edit src/app.ts","toolCallId":"toolu_r_edit"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":null,"content":[{"content":{"text":"Look around","type":"text"},"type":"content"}],"kind":"think","locations":null,"name":"Task","rawInput":{"description":"Explore","prompt":"Look around"},"rawOutput":null,"sessionUpdate":"tool_call_update","status":"pending","title":"Explore","toolCallId":"toolu_r_task"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"output":{"data":"YXBwLnRz"},"sessionUpdate":"terminal_update","terminalId":"toolu_r_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_r_bash"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_r_bash","type":"terminal"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_r_bash"}}}
@@ -17,5 +17,6 @@
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r2","sessionUpdate":"agent_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Fixed.","type":"text"},"messageId":"msg_r2","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"description":"The history of 1 subagent of this session could not be restored.","sessionUpdate":"notice","severity":"info","title":"Some subagent history is not available"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"idle"}}}
{"kind":"response","method":"session/resume","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}]}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"session_info_update","title":"Fix the app","updatedAt":"2026-01-01T00:00:00.000Z"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"idle"}}}
{"kind":"response","method":"session/resume","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}]}}

View File

@@ -1,2 +1,2 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}

View File

@@ -1,12 +1,12 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"Skill","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Load skill","toolCallId":"toolu_skill"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"skill":"commits"},"sessionUpdate":"tool_call_update","title":"Load skill: commits","toolCallId":"toolu_skill"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"Launching skill: commits","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_skill"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,23 +1,18 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review"},"sessionUpdate":"tool_call_update","title":"Background review","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review","prompt":"Review the diff"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review","prompt":"Review the diff","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Review the diff","type":"text"},"type":"content"}],"rawInput":{"description":"Background review","prompt":"Review the diff","run_in_background":true,"subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Async agent launched successfully.","type":"text"},"type":"content"}],"rawOutput":"Async agent launched successfully.\nagentId: a2 (use SendMessage to continue)","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"isAsync":true,"status":"async_launched"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Background review","sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"general-purpose"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientSessionId":"a2","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"a2","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Background review"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientMessageId":"toolu_agent","recipientSessionId":"a2","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientSessionId":"a2","senderMessageId":"toolu_agent","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"a2","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

View File

@@ -1,23 +1,21 @@
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_late"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Check the logs"},"sessionUpdate":"tool_call_update","title":"Check the logs","toolCallId":"toolu_late"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Check the logs","prompt":"Check logs"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Check logs","type":"text"},"type":"content"}],"rawInput":{"description":"Check the logs","prompt":"Check logs","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Check the logs","sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"general-purpose"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientSessionId":"agent_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Check the logs"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientMessageId":"toolu_late","recipientSessionId":"agent_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientSessionId":"agent_late","senderMessageId":"toolu_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"The logs are clean.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The logs are clean.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"The logs are clean.","type":"text"}],"messageId":"toolu_late:reply","recipientSessionId":"<session>","senderSessionId":"agent_late","sessionUpdate":"session_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolResponse":{"elapsedTimeSeconds":4}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_late_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"content":{"text":"```\n1\tok\n```","type":"text"},"type":"content"}],"rawOutput":"1\tok","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late_read"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}

Some files were not shown because too many files have changed in this diff Show More