Compare commits

..

29 Commits

Author SHA1 Message Date
nikita-ashihmin
4f690e129c feat: say the cause of an api retry in the AIR warning details (#1594)
## Summary

The AIR warning for an SDK `api_retry` message had only a title, such as
`Retrying Claude, attempt 2 of 10.`. The user did not see why Claude
retries or how long the next attempt waits. The warning now carries
`details` with the cause and the wait.

## Changes

- `apiRetryWarning(message)` in `src/session-failure-extension.ts`
builds the title and the details. The title text does not change. The
`api_retry` case of `src/v1/acp-agent.ts` only calls it.
- The cause:
  - `HTTP 529 (overloaded).` when the CLI sends a status;
- `Connection error: no HTTP response.` for a null status with the
`unknown` kind;
- `No HTTP status (overloaded).` for a null status with a kind. The CLI
sends `overloaded` with no status for an SSE `overloaded_error` event
after HTTP 200, so "no HTTP response" would be wrong there.
- The wait: `Next attempt now.` for 0 ms (a `no_response` retry), `Next
attempt in 8s.`, and whole minutes from 120 s (a background rate-limit
wait), e.g. `Next attempt in 30m.`.
- The `no_response` wait stays in the title only.
- The kind label map has the type
`Record<Exclude<SDKAssistantMessageError, "unknown">, string>`, so a new
SDK kind fails typecheck. A missing `error` reads as `unknown`.
- The 401 skip, the actions and the terminal failures do not change. The
subagent retry path sends a `tool_progress` update and no session
failure, so it does not change. v2 has no `api_retry` session failure.

## Tests

- `src/tests/session-failure-extension.test.ts` (new): 0 ms, minutes,
the 119 s / 2 m boundary, a null status with `cloud_credential_error`,
and a missing `error`.
- `src/tests/v1/acp-agent.test.ts`: the warning details on the wire,
revision 2 replaces the details, and the terminal revision has none. The
test doubles use status and kind pairs and delays that the bundled CLI
really sends.
- `npm run build`, `npm run check`: clean.
- `npx vitest run src/tests/session-failure-extension.test.ts
src/tests/v1/acp-agent.test.ts`: 766 passed, 9 skipped.
- `npm run test:run`: 17 tests in `create-session-options.test.ts`,
`providers.test.ts` and `acp-v2.test.ts` fail on `main` without this
change too. They read the model list of the local environment.
2026-11-11 14:19:41 +04:00
acp-release-bot[bot]
51f514d73c chore(main): release 0.106.0 (#1588)
🤖 I have created a release *beep* *boop*
---


##
[0.106.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.105.0...v0.106.0)
(2026-11-10)


### Features

* report the auth detail as the account's description
([#1591](https://github.com/agentclientprotocol/claude-agent-acp/issues/1591))
([f6d6429](f6d6429187))
* serve session/options and initial config options
([#1592](https://github.com/agentclientprotocol/claude-agent-acp/issues/1592))
([b1763c7](b1763c750a))


### Bug Fixes

* honour the client's settings option in idle compaction
([#1555](https://github.com/agentclientprotocol/claude-agent-acp/issues/1555))
([b73ee49](b73ee491c8))
* replay the messages before each compaction on ACP v1 and v2
([#1430](https://github.com/agentclientprotocol/claude-agent-acp/issues/1430))
([626015b](626015bf81))
* stop the release executable from loading the project's .env and
bunfig.toml
([#1589](https://github.com/agentclientprotocol/claude-agent-acp/issues/1589))
([4a3ff6a](4a3ff6a1eb))
* update to @agentclientprotocol/sdk 2.6.0
([#1590](https://github.com/agentclientprotocol/claude-agent-acp/issues/1590))
([9fca22a](9fca22a03b))

---
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-10 22:45:08 +00:00
Ben Brandt
b1763c750a feat: serve session/options and initial config options (#1592)
Implements the unstable [Session options
RFD](https://agentclientprotocol.com/rfds/session-options) on ACP v1 and
v2. Both cores advertise the `options` session capability.

## `session/options`

Answers with what a session would start with: modes and config options
on v1, config options and commands on v2.

- A session that the connection holds answers with what it has now.
- Otherwise a query starts as the session's would, without MCP servers,
and closes once its options are read. With `sessionId` it resumes that
session, and an unknown one fails with `-32002`.
- It sends no update and registers no session.

Claude Code only reports its models, modes and agents from a running
process, so there is no way to answer without starting one. The query
runs with `persistSession: false`, so it stores no session and does not
write to a resumed transcript. Two consequences: each call costs a CLI
start, and the user's `SessionStart` hooks run in it. The v2 commands
lack MCP prompts, which the RFD allows.

On v1 this is an `optionsOnly` exit in `createSession`, after the config
options are built and before the session is registered. On v2,
`launchSession` is split into `openQuery` (start the query, build the
config) and the part that makes the session active, and
`session/options` uses only the first.

## Initial `configOptions`

`session/new`, `session/load` (v1) and `session/resume` take
`configOptions`. They are set in list order through the same code as
`session/set_config_option`, so a model entry decides which effort
levels a later entry can pick.

- Two entries with the same `configId` fail with `-32602` before a query
starts. So does a value type that v2 does not take.
- An entry with an unknown `configId`, a value of another type, or a
value the option does not offer is skipped. An entry that Claude Code
refuses is skipped and logged.
- No update goes out for the entries. The response carries the modes and
options, and the warning of an Auto mode fallback waits for the next
prompt, as it does at a session's start.

`session/fork` does not take them yet: the schema has no field for it.
agentclientprotocol/agent-client-protocol#2794 adds one.

## Testing

New tests in `src/tests/v1/create-session-options.test.ts` and
`src/tests/v2/v2.test.ts` cover both versions. The scenario golden files
change only by the new capability in `initialize`.

Not verified against a real Claude Code yet: `persistSession: false`
together with `resume`. The tests run against the mocked SDK.
2026-11-10 22:42:28 +00:00
Ben Brandt
f6d6429187 feat: report the auth detail as the account's description (#1591)
Stacked on the SDK 2.6.0 update (`acp-sdk-2.6.0`). Merge that first.

`auth/status` and `auth/status_update` carried the detail of an account,
such as the key source or the gateway host, in parentheses in its label:
`Anthropic API key (ANTHROPIC_API_KEY)`. SDK 2.6.0 adds
`AuthAccount.description` for it, so the account is now `label:
"Anthropic API key"` with `description: "ANTHROPIC_API_KEY"`.

`_meta.claudeCode.detail` still carries the detail for clients that read
it there. A client that shows only `label` no longer shows the detail.

The mapping is shared, so v1 and v2 both change.
`docs/air-extensions.md` is updated.
2026-11-10 22:34:49 +00:00
Ben Brandt
9fca22a03b fix: update to @agentclientprotocol/sdk 2.6.0 (#1590)
Updates `@agentclientprotocol/sdk` from 2.5.0 to 2.6.0 (schema v1.31.0
and v2.0.0-alpha.15).

The new schema adds `configOptions` to session setup requests. The v2
core converts a setup request to its v1 form, and the v2 entries are not
assignable to the v1 type, so `v1NewSessionRequest` leaves them out.
Nothing reads them yet: a Client only sends them to an Agent that
advertises `session.options`. #1592 serves them.

No behavior change. `npm run check`, `npm run build` and `npm run
test:run` pass.
2026-11-10 22:34:49 +00:00
Ben Brandt
4a3ff6a1eb fix: stop the release executable from loading the project's .env and bunfig.toml (#1589)
Fixes #1579.

## Problem

The release executable is built with `bun build --compile`. By default a
compiled Bun executable loads `.env`, `.env.local` and `bunfig.toml`
from its working directory at startup. The editor starts the agent in
the user's project, so:

- The project's dotenv values land in the agent's `process.env` and are
passed on to Claude and every Bash command as real environment
variables. Most frameworks never override a variable that is already
set, so the project's own `.env.test` is silently ignored. In the
reported case this made a test suite drop the dev database.
- A `bunfig.toml` in the project that Bun cannot parse stops the agent
from starting (`SyntaxError: An error occurred while parsing code`, exit
1).

Only the executable is affected; running the npm package under Node does
not load these files.

## Fix

`scripts/build-binary.mjs` passes `--no-compile-autoload-dotenv` and
`--no-compile-autoload-bunfig`. Bun's other two autoloads
(`tsconfig.json`, `package.json`) are already off by default.

## Testing

`scripts/smoke-binary.mjs` gains a check that runs the executable in a
directory containing a `.env`, a `.env.local` and an unparseable
`bunfig.toml`, with Node standing in for `claude` to print the
environment the CLI is spawned with. It fails on an executable built
without the flags and passes with them.

Run locally on macOS arm64 only; the Linux, Alpine and Windows targets
are covered by the `binaries.yml` matrix.
2026-11-10 21:27:42 +00:00
Erdős Péter
b73ee491c8 fix: honour the client's settings option in idle compaction (#1555)
## Problem

A client can pass Claude Code settings in
`_meta.claudeCode.options.settings`, and the CLI gets them as
`--settings`. Idle compaction ignored them: it read only
`settingsManager.getSettings()`, the settings files. A client that sent
`{ idleCompaction: false }` still got `/compact` after about 54 idle
minutes, though the CLI had the setting off. The same went for
`autoCompactEnabled` and the `env` block (`DISABLE_COMPACT`,
`DISABLE_AUTO_COMPACT`, `CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS`). This
affected both ACP v1 and v2.

## Change

- `SettingsManager.getCompactionSettings(override)` resolves
`idleCompaction`, `autoCompactEnabled` and `env` in the CLI's order:
managed settings, then `--settings`, then the user/project/local files.
`env` merges entry by entry. An `undefined` value in the override does
not hide the files' value. This sits next to `getApiKeyHelper` and
`getModelSelectionSettings`, which already take the override the same
way.
- v1 (`src/v1/acp-agent.ts`) and v2 (`src/v2/reports.ts`, via
`ReportsSetup.configuredSettings`) pass the client's settings to it.
`IdleCompaction` itself is unchanged.

## Tests

- `settings-api-key-helper.test.ts`: managed > override > files for each
key and each `env` entry; an `undefined` override key keeps the files'
value.
- `v1/idle-compaction-agent.test.ts` and `v2/acp-v2.test.ts`: a session
started with `settings: { idleCompaction: false }` gets no `/compact`.
Both fail without the wiring change.

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Erdős Péter <6430206+sodre90@users.noreply.github.com>
2026-11-10 21:24:24 +00:00
Hussain Hammad
626015bf81 fix: replay the messages before each compaction on ACP v1 and v2 (#1430)
Fixes #1297.

After `/compact`, `session/load` replays from the summary of the last
compaction only. `getSessionMessages` follows `parentUuid` links, and
Claude Code writes each `compact_boundary` with `parentUuid: null`, so
the chain ends there. The messages before it are still in the `.jsonl`,
and the boundary still links the last one of them in
`logicalParentUuid`; nothing follows that link. The spec requires the
agent to "replay the entire conversation", and the replay test `replays
a compaction as one completed compaction_update at its position`
(`v2/acp-v2.test.ts`) already mocks a prompt before the summary, which
the real SDK never returns.

This now happens without `/compact` too: since #1522, idle sessions
compact on their own. It reproduces in Zed as well
([zed-industries/zed#66523](https://github.com/zed-industries/zed/issues/66523#issuecomment-6644375981)).

- When the chain starts at a compact summary, replay reads the
transcript again through `readHistoryAcrossCompactions`
(`resumed-session.ts`). It loads every record with
`loadFullSessionHistory`, which moves from `fork-session.ts` (where it
reads the inactive branches for the same reason) to sit beside
`findTranscript`. It links each boundary to its `logicalParentUuid` and
passes the records back to `getSessionMessages` as a `sessionStore`. The
SDK's own parsing (queued commands, meta messages, deliveries) is
unchanged.
- The boundary's `preservedSegment` and `preservedMessages` are dropped.
With them, `getSessionMessages` moves the messages a compaction kept to
after the summary, and the relinked boundary then leads from them back
to the summary, so the chain loops and ends there. Without them, the
kept messages replay once, where they were said.
- The v1 agent's `session/load` and the v2 agent's replay both read the
history this way.
- A session without a compaction reads its transcript once, as before.
Resume, fork and rewind still use the active chain. If the second read
fails, replay logs it and sends the active chain, as it does today.

## Testing

- `npm run check` and `npm run build` pass. `npm run test:run` passes
except for 2 tests that fail the same way on `main` on this machine:
`v1/acp-scenarios.test.ts > normalize > replaces only the version of
this adapter and the path of this Node binary` (Node at
`/usr/bin/node`), and `v2/v2.test.ts > lists bypass before auto in
ExitPlanMode's options after plan mode left bypass` (this machine runs
as root, where bypass is not offered).
- New: `compacted session replay` (`v1/acp-agent.test.ts`) replays a
real transcript file with one `/compact` between two prompts to a client
that declares `session.compaction`. It fails on `main` without this
change: the replay has no "hi" and no "Hello".
- New: `replays the messages before a compaction from the transcript`
(`v2/v2.test.ts`) resumes the same transcript on v2 with `replayFrom:
start`. Without the change, the replay has no user message before the
compaction.
- New: `readHistoryAcrossCompactions` (`resumed-session.test.ts`) covers
two compactions, a compaction with both forms of preserved metadata as
Claude Code 2.1.293 writes them, and a session without a compaction.
- On 10 local transcripts with 1 to 10 compactions, no message replays
twice. 9 of them now replay from the first prompt with one summary per
compaction, for example 414 → 1925 messages and 909 → 3693. The tenth
has many records written a second time, and replays 5 of its 10
compactions (1428 → 7526 messages). The second read took 4–220 ms, and
745 ms for that 33,000-line transcript.
- Rechecked with 4 transcripts written by Claude Code 2.1.280 to
2.1.318, with 1, 2, 3 and 6 compactions. `session/load` sends one
`compaction_update` per compaction, where `main` sends only the last
one; for the 6-compaction transcript `main` replays nothing before the
last summary. No summary replays as a user message and no tool call
replays twice.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-11-10 22:10:24 +01:00
acp-release-bot[bot]
1ffbb6b577 chore(main): release 0.105.0 (#1583)
🤖 I have created a release *beep* *boop*
---


##
[0.105.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.1...v0.105.0)
(2026-11-10)


### Features

* serve session/set_title on ACP v1 and v2
([#1585](https://github.com/agentclientprotocol/claude-agent-acp/issues/1585))
([a4a8534](a4a8534c23))
* serve session/status on v1 and v2
([#1584](https://github.com/agentclientprotocol/claude-agent-acp/issues/1584))
([6bc2e05](6bc2e058cb))


### Bug Fixes

* include inline model settings in the model picker
([#1488](https://github.com/agentclientprotocol/claude-agent-acp/issues/1488))
([36cb674](36cb674589))
* keep a v2 session idle when the model changes between turns
([#1586](https://github.com/agentclientprotocol/claude-agent-acp/issues/1586))
([f5d7916](f5d79167a6))
* report Claude Code's API error message as a notice in v2
([#1582](https://github.com/agentclientprotocol/claude-agent-acp/issues/1582))
([99cc1f3](99cc1f3245))

---
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-10 21:51:06 +01:00
Slapoguzov Alexander
36cb674589 fix: include inline model settings in the model picker (#1488)
When `CLAUDE_MODEL_CONFIG` or caller-provided SDK settings list a model
that the SDK omits, include those settings when rebuilding the picker.
Configured models such as Bedrock Haiku versions appear with their
provider override IDs and can be selected in both ACP v1 and v2.

Merge file and inline model lists with the SDK's array semantics,
preserve managed allowlists and override precedence, and keep the
existing shallow caller-over-environment merge. The refreshed change
preserves upstream's resumed-model alias handling and uses the v2 setup
path introduced on current main.

This repairs explicitly configured models; it does not discover an older
model from a gateway listing or change the SDK's family defaults.

Validation on upstream main `a1d83c60310f33cbc3cd78f6b6133278e754efd5`
with SDK 0.3.321:
- TypeScript compilation, ESLint and Prettier checks passed using their
installed executables (the host npm script launcher exits 194).
- 181 focused tests passed, including managed policy and environment,
caller-object and caller-file settings; both v1 and v2 select the
configured provider ID.
- The three new v2 regression cases fail on unchanged main because the
configured models are missing.
- Full suite passed: 3,952 tests, 33 skipped. The first sandboxed run
could not open a local socket or receive filesystem watcher events; the
unchanged candidate passed with host test execution.
- No real model inference or live provider calls were used.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Aleksandr Slapoguzov <aleksandr.slapoguzov@jetbrains.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 21:11:22 +01:00
Ben Brandt
f5d79167a6 fix: keep a v2 session idle when the model changes between turns (#1586)
On ACP v2, changing the model of an idle session left the session
reporting `running` with no work in progress, and it never reported
`idle` again until some later turn ended. A client that refuses prompts
while the session is `running` could then never send the prompt that
would end it.

## Cause

After `setModel`, Claude Code echoes a user message that carries only
the local command's output:

```json
{
  "type": "user",
  "message": {
    "role": "user",
    "content": "<local-command-stdout>Set model to `haiku (claude-haiku-5-5)`</local-command-stdout>"
  },
  "parent_tool_use_id": null,
  "uuid": "…",
  "isReplay": true
}
```

The v2 session read every top-level echo as a prompt that Claude Code
took in, and opened a turn for it. A model change runs no turn: no
`command_lifecycle` frame, `result` or `session_state_changed` follows
the echo, so nothing closed that turn.

Captured against the bundled CLI (adapter 0.104.1): `state_update
running` went out 18 ms after `session/set_config_option`, and no `idle`
followed in the next 75 seconds.

## Fix

An echo whose content is only local command output, and whose uuid is
not one of the session's pending prompts, no longer opens a turn. The
check is `isLocalCommandOutput` in `message-filters.ts`, next to the
helpers that already recognize these markers.

The pending-prompt exception keeps a client's own prompt working with a
CLI that sends no `command_lifecycle` frames, where the echo is the only
sign that the prompt was inserted.

## Scope

- v2 only. The v1 core takes its state from `session_state_changed` and
does not open turns at echoes.
- I captured a model change. Other local commands that Claude Code runs
outside a turn and echoes the same way are covered by the same check,
but I did not capture them.

## Testing

- New case in `src/tests/v2/v2.test.ts` sends the echo as the CLI sent
it. Without the fix it fails with `expected [ 'running' ] to deeply
equal []`.
- Reran the capture against the bundled CLI with the fix: no `running`
after either model change, and the next prompt ran `running` then
`idle`.
- `npm run check` and `npm run test:run` pass.
2026-11-10 18:49:50 +00:00
Ben Brandt
a4a8534c23 feat: serve session/set_title on ACP v1 and v2 (#1585)
Implements the [`session/set_title`
RFD](https://agentclientprotocol.com/rfds/session-set-title) on ACP v1
and v2.

## What clients get

- v1 advertises `agentCapabilities.sessionCapabilities.title: {}` and v2
advertises `capabilities.session.title: {}`. Both serve
`session/set_title` for every client.
- The session does not need to be loaded. A session that runs on this
connection is renamed through its CLI (`rename_session`). Any other
session gets the `custom-title` and `agent-name` records in every copy
of its transcript, plus the `custom-title.json` sidecar, as the CLI's
`/rename` writes them.
- The title is normalized the way `_session/rename` normalizes it:
whitespace collapsed, cut to the CLI's 200 characters. The response
carries the stored title, as that client's `session/list` shows it. On
v1 an AIR client gets it without the `[archived] ` prefix. An archived
session stays archived.
- A loaded session publishes `session_info_update { title }`. A title
generation in flight finishes first, and no generated title replaces the
client's.
- Errors: a title that is empty after normalization returns `-32602`. A
session that is not loaded and has no transcript returns `-32002`. On
v2, a subagent session is refused, as close and delete refuse it.

## Structure

- The transcript writer behind `_session/rename` lived in
`src/v1/session-index/`, which v2 cannot import. Everything there except
`connection.ts` moves to the shared `src/session-index/` with no change
beyond import paths. `connection.ts` becomes `src/v1/session-index.ts`.
- `src/session-index/rename.ts` holds the rename logic that used to be
in `SessionIndexConnection.rename`/`retitle`. `_session/rename` and
`session/set_title` on both versions call it.
- Clients without `sessionIndex` (and every v2 client) use a
`SessionIndexService` with no list and no watcher (`transcriptWriter`).
Their `session/delete` now runs inside that service's per-session
`exclusive()`, so a delete cannot race a rename that is still appending
records.
- If a restart or resume starts a new query of a v2 session while a
rename runs, v2 renames through the new query too. Its CLI could
otherwise hold the old title and write it back.

This doesn't use the SDK's top-level `renameSession()`. It writes only a
`custom-title` record to the first copy of the transcript it finds, with
no `agent-name` and no sidecar, so a session renamed earlier through
`/rename` would keep its old name in the CLI and in AIR.

## Tests

- v1 (`session-index.test.ts`): a stored session for a client without
the session index; an archived session for three clients (AIR with
`sessionIndex`, AIR without it, a client that is not AIR); unknown
session and empty title.
- v2 (`acp-v2.test.ts`): an active session through its CLI with the
published update; a stored session's records; the rename that a resume
overtakes; unknown session and empty title.
- Golden files: only the `initialize` capability line changes.
`compare.ts` now allows the `title` capability against the origin/main
baseline.
- Not run: `npm run test:integration` against the real CLI. The
delete/rename ordering has no test, since it is a timing race.
2026-11-10 18:28:46 +00:00
Ben Brandt
6bc2e058cb feat: serve session/status on v1 and v2 (#1584)
Implements the unstable [Session status
RFD](https://agentclientprotocol.com/rfds/session-status) on both
agents, and advertises `status: {}` in the v1 `sessionCapabilities` and
the v2 `capabilities.session`.

## What it answers

| Session | v1 | v2 |
| --- | --- | --- |
| Held by this connection | `open` | `open` with `state` |
| Not held, non-empty transcript stored | `saved` | `saved` |
| Not held, no transcript | `-32002` with `data.sessionId` | same |
| Subagent session | `-32002` | `-32602`, as resume, close, fork and
delete of one |

- The request starts no query, sends no update, and waits for no resume,
restart or close.
- On v2, `state` is the foreground state as a `state_update` snapshot
holds it: with the session's `background` list and without a stop
reason. `V2Background.snapshot` builds it without marking the list as
reported, so a status request does not change which later
`state_update`s go out.
- `saved` or unknown is decided in the new shared
`src/session-status.ts`: a non-empty `<sessionId>.jsonl` in any project
directory. The request names no `cwd`, so a resume with another `cwd`
can still fail with `-32002`. A directory that cannot be read for
another reason than being absent fails the request and does not answer
`-32002`.

## Behaviour to review

- **A v1 session whose query ended reads `open`.** The connection still
holds it, and a `session/load` with the same params returns it as it is,
so `saved` would point the client at a resume that revives nothing. Its
prompts fail with the "session ended" error. v2 drops such a session and
answers from its transcript.
- **A session that was never prompted is unknown once it is closed.**
Claude Code writes the transcript at the first prompt, and a resume of
it fails with `-32002` too.
- **A subagent session does not advertise `status`,** so v2 refuses the
request like its other requests, and v1 does not know the id.
- **`open` is per connection.** Each connection has its own agent and
its own Claude Code processes.

## Not done

- The lookup does one `readdir` and one `stat` per project directory for
each probe of a session that is not held. Sharing the v1 session index's
cached scan is a follow-up.
- Not run against the real CLI (`npm run test:integration`).

## Other changes

- `isSessionId` and `projectsRoot` moved from
`src/v1/session-index/project-dirs.ts` to `src/paths.ts`, so the shared
module can use them.
- The 40 v2 and 2 AIR scenario golden files gain the capability in
`initialize`, and the origin/main comparison allows it, next to
`systemPrompt` and `auth.status`.
- The README lists `session/status` in the v2 surface.
2026-11-10 18:11:39 +00:00
Ben Brandt
99cc1f3245 fix: report Claude Code's API error message as a notice in v2 (#1582)
## Problem

When an API request fails, Claude Code writes its own assistant message
into the conversation, e.g. "API Error: Connection lost mid-response.
The response above may be incomplete." The SDK marks it with `error` on
a `<synthetic>` message.

The v2 agent forwarded that message as an ordinary
`agent_message_chunk`, so clients showed it as Claude's own prose. Right
after a thinking block it reads as if Claude wrote it, and a client
cannot tell it apart without matching the string.

## Change

The v2 agent no longer sends that message as Claude's text:

- When the turn fails, the idle's `error` already carries the failure,
so nothing else is sent.
- Otherwise the text goes out as a `notice` with severity `error`, once,
before the turn's idle. A result that repeats the same text is not shown
again.

Claude Code's sign-in message is handled as before.

## Not changed

- v1 keeps its current behaviour: clients with the session-failure
extension already drop this text, and others keep the transcript line.
- `session/load` replay still shows these messages as text.
`getSessionMessages` strips the `error` marker, so replay cannot
identify them without matching prose.

## Testing

- New v2 cases: a notice when the turn ends normally, one notice when
the result repeats the text, and no notice when the idle fails. They
fail without the change.
- `npm run check` and `npm run test:run` pass.
2026-11-10 18:51:11 +01:00
Mark Tkachenko
bce309f482 refactor: move the v1 client connection out of acp-agent.ts (#1576)
Moves `ClientConnection` and `ChangedMetaClient` verbatim from
`src/v1/acp-agent.ts` into `src/v1/client-connection.ts`. No behaviour
change.

- Both classes gain `export`; nothing else in them changes.
- `client-connection.ts` imports nothing from `acp-agent.ts`.
- The `acp-client.ts` comment that names where `ClientConnection` lives
now points to the new file.
- Emitted JS differs from `main` only in `dist/v1/acp-agent.js` (the
import and the two classes) plus the new `dist/v1/client-connection.js`.

Review with `git diff --color-moved` to see the move.
2026-11-10 17:54:17 +01:00
acp-release-bot[bot]
6b86f7b46a chore(main): release 0.104.1 (#1575)
🤖 I have created a release *beep* *boop*
---


##
[0.104.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.0...v0.104.1)
(2026-11-10)


### Bug Fixes

* carry the auth detail in the ACP account label
([#1573](https://github.com/agentclientprotocol/claude-agent-acp/issues/1573))
([3d6eb7b](3d6eb7b5b6))
* distinguish Ultra from Max in auth status
([#1574](https://github.com/agentclientprotocol/claude-agent-acp/issues/1574))
([7d95d30](7d95d30ec3))
* update to @anthropic-ai/claude-agent-sdk 0.3.322
([#1577](https://github.com/agentclientprotocol/claude-agent-acp/issues/1577))
([b89e449](b89e4497da))

---
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-10 14:48:59 +01:00
Ben Brandt
b89e4497da fix: update to @anthropic-ai/claude-agent-sdk 0.3.322 (#1577)
- Bumps `@anthropic-ai/claude-agent-sdk` 0.3.321 → 0.3.322.
- Runs `npm update` for in-range lockfile bumps: `rolldown` 1.2.16 →
1.2.18, `vite` 6.2.16 → 6.2.17, `express-rate-limit` 8.3.6 → 8.3.7,
`jose` 8.7.3 → 8.7.4.

TypeScript stays at 6.0.3: 7.0.2 is out, but the latest
`@typescript-eslint/parser` (8.72.2) requires `typescript <6.1.0`.

`npm run build`, `npm run check` and `npm run test:run` pass locally.
One test failed once after `npm update` (not captured); four later full
runs all passed, so it looks flaky.
2026-11-10 14:41:44 +01:00
Ben Brandt
7d95d30ec3 fix: distinguish Ultra from Max in auth status (#1574)
## Summary

- Read the optional `planTier` field from the existing `claude auth
status --json` probe. Display Max subscriptions with `max_20x` as
**Claude Ultra**, while `max_5x` remains **Claude Max**.
- Fall back to the existing subscription label when the tier is missing,
unknown, or malformed. Preserve auth-source precedence and raw plan
metadata.
- Keep the CLI-derived label when a session SDK account read reports the
same identity and plan without a tier. A fresh CLI probe can still
replace the label, including when its tier is absent.
- Cover ACP v1 and v2 notifications and responses, and document the
display behavior.

The SDK AccountInfo omits the plan tier, but the bundled CLI already
emits it in auth-status JSON. This uses that CLI-only field as
best-effort display metadata, without additional probes, direct
credential-file reads, or entitlement checks.

## Validation

- `npm run build`
- `npm run check`
- `npm run test:run` — 4,002 passed, 33 skipped
- Targeted v1 auth-status and v2 protocol tests — 341 passed
- Local bundled CLI smoke check: `subscriptionType: "max"`, `planTier:
"max_20x"` maps to `Claude Ultra`
- Live model integration tests were not run.
2026-11-10 13:25:20 +00:00
Ben Brandt
3d6eb7b5b6 fix: carry the auth detail in the ACP account label (#1573)
## Summary

The ACP `auth/status` account (`label`, `active`, `name`, `email`,
`organization`) has no field for the identity's `detail`: an API key's
source, a gateway's host, or "OAuth token". It was only in
`_meta.claudeCode.detail`, so a client that shows the standard fields
could not tell which API key or gateway is in use.

`toAcpAuthStatus` now appends the detail to the account's `label` in
parentheses:

- `Anthropic API key (ANTHROPIC_API_KEY)`
- `Custom model gateway (gw.example.com)`
- `Claude subscription (OAuth token)`

An identity without a detail keeps its label as it was (`Claude Max`).

## Unchanged

- `_meta.claudeCode` still carries `kind`, `detail` and `plan` on their
own.
- The `_auth/status_update` extension payload (`label` and `detail` as
separate fields).
- The mapping is shared, so v1 and v2 report the same label in
`auth/status` and `auth/status_update`.

## Testing

- `src/tests/v1/auth-status.test.ts` expectations updated for the API
key and gateway labels.
- `npm run test:run`, `npm run check` and `npm run build` pass.
2026-11-10 14:21:33 +01:00
acp-release-bot[bot]
de521df295 chore(main): release 0.104.0 (#1571)
🤖 I have created a release *beep* *boop*
---


##
[0.104.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.103.0...v0.104.0)
(2026-11-10)


### Features

* implement the ACP auth/status RFD next to _auth/status_update
([#1570](https://github.com/agentclientprotocol/claude-agent-acp/issues/1570))
([d202748](d202748976))


### Bug Fixes

* offer Anthropic Console login in remote environments
([#1572](https://github.com/agentclientprotocol/claude-agent-acp/issues/1572))
([c09543d](c09543d85a))

---
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-10 13:44:19 +01:00
Mark Tkachenko
c09543d85a fix: offer Anthropic Console login in remote environments (#1572)
## Problem

In a remote environment (`SSH_CONNECTION`, `SSH_CLIENT`, `SSH_TTY`,
`NO_BROWSER` or `CLAUDE_CODE_REMOTE` set), `initialize` advertised only
`claude-login`, the TUI `claude /login`. A user with API billing had no
Anthropic Console option. With `--hide-claude-auth`, the list was empty
and there was no way to log in at all.

There is a second bug. A `_meta["terminal-auth"]` command reruns the
agent with its own flags, for example `--hide-claude-auth --cli auth
login --console`. The `--cli` passthrough removed only `--cli`, so the
Claude CLI failed with `unknown option '--hide-claude-auth'`.

## Fix

- `terminalAuthMethods` (`src/auth-methods.ts`) now offers
`console-login` in remote environments too. Since Claude Code 2.1.126,
`auth login` accepts the OAuth code pasted into the terminal when the
localhost callback can't be reached. The subscription method stays as
`claude-login` on a remote host and `claude-ai-login` locally.
`--hide-claude-auth` hides it in both cases. The change is in the shared
module, so it applies to v1 and v2.
- New `cliArgs()` (`src/agent-command.ts`) removes `--cli` and
`--hide-claude-auth` before the arguments go to the Claude CLI.

## Tests

- `v1/authorization.test.ts`: an `it.each` over the five remote env vars
× typed/`_meta` terminal auth × with/without `--hide-claude-auth`.
- `v2/acp-v2.test.ts`: a remote host with `--hide-claude-auth` gets
exactly one `console-login` terminal method.
- `agent-command.test.ts`: `cliArgs` under Node, a compiled binary, and
`--cli --version`.

Manually checked in a JetBrains IDE through a local launch overlay with
`SSH_CONNECTION` set and `--hide-claude-auth`. Only Anthropic Console
was offered, the terminal login completed, and the chat worked
afterwards.
2026-11-10 13:40:44 +01:00
Ben Brandt
d202748976 feat: implement the ACP auth/status RFD next to _auth/status_update (#1570)
Implements the [auth status
RFD](https://agentclientprotocol.com/rfds/get-auth-state) on ACP v1 and
v2. The `_auth/status_update` extension is unchanged and still sent, so
clients that read it keep working.

## What changes for a client

- `initialize` advertises `auth.status: {}` (v1 under
`agentCapabilities`, v2 under `capabilities`), next to the existing
`_meta.authStatus` marker.
- `auth/status_update` is sent whenever the identity changes, right
after `_auth/status_update`. Each notification is deduplicated on its
own payload.
- `auth/status` answers with the identity the connection holds.
- If an identity is known, it answers at once and starts a `claude auth
status` read in the background. A change that read finds arrives as
`auth/status_update`.
- If none is known yet (a call right after `initialize`), it waits for
the probe `initialize` started, bounded by the existing 5 s timeout, and
answers `unknown` if that publishes nothing.

## Mapping

`toAcpAuthStatus` in `src/auth-status.ts` maps the existing identity:

| Identity | `state` | `accounts` |
| --- | --- | --- |
| not read yet | `unknown` | none |
| `kind: "none"` | `unauthenticated` | none |
| any other kind | `authenticated` | one entry, `active: true`, with
`label`, `email`, `organization` |

The RFD account has no field for the credential type, so `kind`,
`detail` and `plan` go under the account's `_meta.claudeCode`.

## Notes for review

- `state` describes the agent's own login. It does not count a
`providers/set` route, and `--hide-claude-auth` does not change it: a
subscription login is reported as `authenticated` even though the
subscription guard refuses `session/new` for it. The legacy notification
behaves the same way.
- `auth/status_update` goes to every client. The RFD has no client
capability to gate on.
- The background read from `auth/status` is an ordinary probe, so under
`--hide-claude-auth` it can mark a session for recreation when the
account kind changed, as the probe at the next prompt would.
- Probes are now numbered in start order. One that lands after a
later-started probe has published is discarded, so an older read cannot
overwrite a newer one.
- `AcpClient` gained `authStatusUpdate`, so two test doubles needed a
stub.
- 42 scenario golden files change by one line each (the new capability
in `initialize`), and the origin/main baseline comparison allows
`auth.status`.

## Testing

`npm run check`, `npm run build` and `npm run test:run` pass. New tests
cover the mapping, the capability, `auth/status` and
`auth/status_update` on v1 and v2, a gateway login, a first-probe
timeout, out-of-order probes and the dedupe.
2026-11-10 13:02:26 +01:00
acp-release-bot[bot]
a1d83c6031 chore(main): release 0.103.0 (#1559)
🤖 I have created a release *beep* *boop*
---


##
[0.103.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.102.0...v0.103.0)
(2026-11-10)


### ⚠ BREAKING CHANGES

* deep imports of `dist/` files fail with
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and `lib.ts` exports only `serveAcp`,
`AUTH_STATUS_UPDATE_METHOD` and their types.

### Features

* serve ACP v2 with a direct v2 agent and remove the v1 bridge
([#1560](https://github.com/agentclientprotocol/claude-agent-acp/issues/1560))
([cb285cd](cb285cd285))


### Bug Fixes

* give subagent checklists their own plan ID
([#1558](https://github.com/agentclientprotocol/claude-agent-acp/issues/1558))
([2f7e386](2f7e386f36))


### Code Refactoring

* export only lib.ts, with serveAcp and the authStatus wire names
([#1566](https://github.com/agentclientprotocol/claude-agent-acp/issues/1566))
([566e265](566e2652a9))

---
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-10 09:35:53 +01:00
Ben Brandt
966aee0f04 refactor: move the test-only tool render and metadata wrappers into src/tests/ (#1569)
`toolInfoFromToolUse` and `toolUpdateFromToolResult` in `src/tools.ts`
and `isLocalCommandMetadata` in `src/message-filters.ts` were public
`lib.ts` exports. #1566 removed them from `lib.ts`, so now only tests
call them. They're thin wrappers: the first two build an
`AcpToolCallRenderer` for one tool use or result, and the third checks
that `stripLocalCommandMetadata` drops everything.

They move unchanged to `src/tests/tool-render.ts`. `tools.test.ts`,
`diff-patch.test.ts` and `v1/acp-agent.test.ts` import them from there.
Production code renders through `AcpToolCallRenderer` directly, as
before. Code changes only: no test changes and no behavior change.

A scan of `src/` for exports finds no export that nothing uses. The only
remaining test-only exports are the deliberate test hooks
`clearDiffMemo` (`diff.ts`) and `sleep` (`utils.ts`).

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped
2026-11-10 08:31:08 +00:00
Ben Brandt
66adc1a4c9 refactor: share the subagent replay's transcript scan between v1 and v2 (#1568)
`src/subagent-replay.ts` (v1, `RfdSubagentReplay`) and
`src/v2/subagent-replay.ts` (`SubagentReplay`) each kept a copy of the
transcript scan. The copies differed only in formatting and variable
names. The scan does four things:
- It registers each Agent or Task launch that has metadata as a child,
and records a launch without metadata as omitted.
- It records each SendMessage and SubagentHandback call.
- It marks a failed hand-back.
- It sets a foreground launch's end from its tool result.

That scan, the `ReplayChild` and `SendMessageCall` types, the child's
end state, and the `contentBlocks`, `inputOf`, `textOf` and
`isDirectory` helpers move to `ReplayTranscript` in
`subagent-replay.ts`. Each replay holds one and reads `children`,
`sendMessages`, `handbacks` and `omitted` from it.

What each replay sends stays where it was. v1 publishes asynchronously
through `Publish`, and v2 sends synchronously through `SessionOutput`,
with the v2-only rules: each child's end also goes on the child's own
stream, and live children get their current state after the response.
Sharing those parts would tie the two protocols together, so I left them
separate. The scan only parses transcripts, so sharing it adds no
dependency between v1 and v2.

v2's `ReplayChild` is the shared type plus `output?: SessionOutput`. Its
`launchEnd` was a narrower `"completed" | "failed" | "cancelled"`, which
`endState` passed through `nativeSubagentState`. That call returns those
three values unchanged, so `replayEndState` gives the same result in
both versions.

No behavior change: 128 lines added and 242 removed, with no test
changes.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped. The v1 and v2 replay
scenario goldens are unchanged.
- `RUN_INTEGRATION_TESTS=1 npx vitest run`: passes. One run hit the
known load flake "absorbs the interrupt's lagged trailer after
cancelling a held turn", which passes on its own.
2026-11-10 08:19:11 +00:00
Ben Brandt
bc031f362c test: split src/tests/ into v1/, v2/ and shared tests (#1567)
`src/tests/` was flat, so it didn't follow the `src/v1/` / `src/v2/`
layout.

- **`src/tests/v1/`** holds the 37 tests that import `src/v1/`, plus
`session-doubles.ts`, which builds v1 sessions.
- **`src/tests/v2/`** holds the tests of `src/v2/` and `serve.ts`:
`v2.test.ts`, `v2-integration.test.ts`, `acp-v2.test.ts` and
`acp-scenarios-v2.test.ts`.
- **The top level** keeps the tests of shared modules, plus
`helpers.ts`, `setup-no-network.ts`, the scenario harness
(`acp-scenarios/`) and `fixtures/`.

A test goes to `v1/` when it imports the v1 agent, even if it is mostly
about a shared module. For example, `providers.test.ts` and
`fast-mode-config.test.ts` drive `ClaudeAcpAgent` for some of their
cases.

Only paths change. Imports and `vi.mock`/`vi.importActual` specifiers
gain one `../`. The `here`-relative paths to the goldens, the
origin/main recordings, `package.json` and `fixtures/` were updated too.
`AGENTS.md`, `docs/air-extensions.md` and the scenario files' doc
comments now give the new paths. No test, golden file or recording
changed. A script checked that every relative module specifier under
`src/tests/` resolves, because a `vi.mock` path that doesn't resolve
mocks nothing and raises no error.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped, the same as before the
move.
- `RUN_INTEGRATION_TESTS=1 npx vitest run`: 3964 passed / 8 skipped.
2026-11-10 09:07:09 +01:00
Ben Brandt
566e2652a9 refactor!: export only lib.ts, with serveAcp and the authStatus wire names (#1566)
Stacked on #1564.

The package's `exports` map listed `"./*": "./*"`, so every file under
`dist/` was public, and any file move could break a deep import. It now
lists only `.` (`dist/lib.js`) and `./package.json`.

**`lib.ts` exports:**
- `serveAcp`, which serves ACP v1 and v2 on stdio through the SDK's
protocol router. This is what `index.ts` runs.
- The `AcpProtocols` and `Logger` types.
- `AUTH_STATUS_UPDATE_METHOD` and the `authStatus` payload types, which
a client needs to read the extension's traffic.

**No longer exported:**
- `ClaudeAcpAgent`
- `runAcp`: v1 only and unused, replaced by `serveAcp`, and now deleted.
- `toAcpNotifications`, `streamEventToAcpNotifications`,
`isLocalCommandMetadata`, `stripLocalCommandMetadata`
- `toolInfoFromToolUse`, `toolUpdateFromToolResult`, `toDisplayPath`,
`planEntries`
- `SettingsManager`
- `nodeToWebReadable`, `nodeToWebWritable`, `Pushable`, `unreachable`
- The types `NewSessionMeta`, `SDKMessageFilter`, `ToolUpdateMeta` and
`ClaudePlanEntry`.

`src/acp-agent.ts`, which re-exported `src/v1/acp-agent.ts` for the
`dist/acp-agent.js` path, is gone. So are the re-exports in
`src/v1/acp-agent.ts` that kept moved names on that path. Tests now
import those names from their own modules. The ESLint boundary rules and
`AGENTS.md` drop their `acp-agent.ts` exemptions.

`release-please-config.json` sets `bump-minor-pre-major`, so the `!`
bumps the minor version, the same as a `feat:`.

BREAKING CHANGE: deep imports of `dist/` files fail with
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and `lib.ts` exports only `serveAcp`,
`AUTH_STATUS_UPDATE_METHOD` and their types.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped. `module-exports.test.ts`
pins the `lib.ts` names and the `exports` map.
- `node scripts/smoke-binary.mjs -- node dist/index.js`: pass
- Resolving the package from its root:
`import("@agentclientprotocol/claude-agent-acp")` gives `serveAcp` and
`AUTH_STATUS_UPDATE_METHOD`, `…/dist/acp-agent.js` throws
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and `…/package.json` resolves.
2026-11-10 07:56:24 +00:00
Ben Brandt
0e7e74d424 chore: lint the import boundaries between v1, v2 and shared modules (#1564)
Stacked on #1562.

Adds ESLint rules for the layout from #1562:

- `src/v2/**` must not import `src/v1/` or `src/acp-agent.ts` (the v1
re-export).
- `src/v1/**` must not import `src/v2/`.
- Shared modules (everything in `src/` except `v1/`, `v2/`, `tests/` and
the entry points `index.ts`, `lib.ts`, `serve.ts`, `binary-entry.ts`,
`acp-agent.ts`) must not import `src/v1/`, `src/v2/` or
`src/acp-agent.ts`.

This uses core `no-restricted-imports` with a `regex` pattern, which
covers static imports and re-exports, type-only ones too. Core
`no-restricted-syntax` covers `import()` calls and `import("...")`
types, which that rule does not check. No new dependencies. AGENTS.md
describes the rule in one sentence.

The tree already passes, so nothing is allow-listed. I checked the rules
with throwaway files covering each import form, at `src/`,
`src/tool-calls/` and `src/v2/` depth. They flagged every violation and
passed the shared import next to them.

No behavior change. Export pins unchanged.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped, goldens unchanged. The
first run hit the known session-index load flake "reads only the changed
transcript for a subscription". That file passed on its own, and so did
a full rerun.
- `node scripts/build-binary.mjs && node scripts/smoke-binary.mjs`: pass

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 07:49:27 +00:00
Ben Brandt
f115b90161 refactor: move the v1 agent into src/v1 (#1562)
Stacked on #1561.

Moves the modules that only the v1 agent uses into `src/v1/` with `git
mv`, so the tree reads as `src/v1/` (v1 agent), `src/v2/` (v2 agent) and
the rest of `src/` (shared). No behavior change.

The v1-only set comes from the import graph: modules reachable from
`acp-agent.ts` whose every non-test importer is itself v1-only (entry
points `lib.ts`/`serve.ts` aside).

Moved:
- `acp-agent.ts`, `background-work.ts`, `goal-extension.ts`,
`turn-events.ts`
- `tool-calls/changed-meta-filter.ts` ->
`v1/tool-calls/changed-meta-filter.ts`
- `session-index/*` -> `v1/session-index/*`, except
`session-index/explicit-title.ts`, which stays because
`session-titles.ts` (shared with v2) imports it

Stay shared: `clear-context-coordinator.ts`, `native-subagents.ts` and
`acp-subagents.ts`. v2 and shared modules import all three.

- `src/acp-agent.ts` is now `export * from "./v1/acp-agent.js"`.
`dist/acp-agent.js` exports the same names, and `module-exports.test.ts`
passes unchanged. `lib.ts` and `serve.ts` import `v1/acp-agent.js`
directly.
- Imports in `src/` and tests, path mentions in comments, and AGENTS.md
are updated. Scripts, vitest config and docs referenced no moved path.
- History: `git log --follow src/v1/acp-agent.ts` follows the move.
Because `src/acp-agent.ts` still exists as the re-export, blame needs
`git blame -C`.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped, goldens unchanged
- `node scripts/build-binary.mjs && node scripts/smoke-binary.mjs`: pass

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 08:44:57 +01:00
153 changed files with 4910 additions and 1463 deletions

View File

@@ -1,3 +1,3 @@
{
".": "0.102.0"
".": "0.106.0"
}

View File

@@ -1,7 +1,11 @@
# Agent guide
An ACP-compatible coding agent powered by the Claude Agent SDK. Source in `src/`,
TypeScript, ESM, Node >= 22.
TypeScript, ESM, Node >= 22. `src/v1/` holds the v1 agent (`ClaudeAcpAgent` in
`src/v1/acp-agent.ts`), `src/v2/` the v2 agent, and the rest of `src/` the modules
both share. The package exports only `src/lib.ts`. ESLint rejects imports between `src/v1/` and
`src/v2/`, and from shared modules into either agent. The entry points (`index.ts`, `lib.ts`,
`serve.ts`, `binary-entry.ts`) are exempt.
## Commands
@@ -40,9 +44,9 @@ opening a PR.
- 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.
- Don't add new features or components to `src/v1/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 `src/v1/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
@@ -81,9 +85,13 @@ opening a PR.
## 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.
- Tests live in `src/tests/`: `v1/` holds the tests that drive the v1 agent,
`v2/` the tests of the v2 agent, and the top level the tests of shared
modules, with `helpers.ts`, the scenario harness (`acp-scenarios/`) and
`fixtures/`. 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 `v1/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.

View File

@@ -1,5 +1,78 @@
# Changelog
## [0.106.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.105.0...v0.106.0) (2026-11-10)
### Features
* report the auth detail as the account's description ([#1591](https://github.com/agentclientprotocol/claude-agent-acp/issues/1591)) ([f6d6429](https://github.com/agentclientprotocol/claude-agent-acp/commit/f6d64291876177f9f593415abfc7e50eb7e7ff56))
* serve session/options and initial config options ([#1592](https://github.com/agentclientprotocol/claude-agent-acp/issues/1592)) ([b1763c7](https://github.com/agentclientprotocol/claude-agent-acp/commit/b1763c750aef83fd05ce16901ff1a0d9b4b90234))
### Bug Fixes
* honour the client's settings option in idle compaction ([#1555](https://github.com/agentclientprotocol/claude-agent-acp/issues/1555)) ([b73ee49](https://github.com/agentclientprotocol/claude-agent-acp/commit/b73ee491c8f60a1584a95a7f03557f356e1a991c))
* replay the messages before each compaction on ACP v1 and v2 ([#1430](https://github.com/agentclientprotocol/claude-agent-acp/issues/1430)) ([626015b](https://github.com/agentclientprotocol/claude-agent-acp/commit/626015bf81fd34f952406ae77bf60feb9ca39a16))
* stop the release executable from loading the project's .env and bunfig.toml ([#1589](https://github.com/agentclientprotocol/claude-agent-acp/issues/1589)) ([4a3ff6a](https://github.com/agentclientprotocol/claude-agent-acp/commit/4a3ff6a1eb16075b96676616ab2bbb98694a4d24))
* update to @agentclientprotocol/sdk 2.6.0 ([#1590](https://github.com/agentclientprotocol/claude-agent-acp/issues/1590)) ([9fca22a](https://github.com/agentclientprotocol/claude-agent-acp/commit/9fca22a03bdec5dc265bfdc42df316fb286a8142))
## [0.105.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.1...v0.105.0) (2026-11-10)
### Features
* serve session/set_title on ACP v1 and v2 ([#1585](https://github.com/agentclientprotocol/claude-agent-acp/issues/1585)) ([a4a8534](https://github.com/agentclientprotocol/claude-agent-acp/commit/a4a8534c23b08cdfd7f656d25f9ab16068fd8ffd))
* serve session/status on v1 and v2 ([#1584](https://github.com/agentclientprotocol/claude-agent-acp/issues/1584)) ([6bc2e05](https://github.com/agentclientprotocol/claude-agent-acp/commit/6bc2e058cb27624b4307462ac46167535af400d1))
### Bug Fixes
* include inline model settings in the model picker ([#1488](https://github.com/agentclientprotocol/claude-agent-acp/issues/1488)) ([36cb674](https://github.com/agentclientprotocol/claude-agent-acp/commit/36cb6745897bf9dabcb967b54233c42b860436b3))
* keep a v2 session idle when the model changes between turns ([#1586](https://github.com/agentclientprotocol/claude-agent-acp/issues/1586)) ([f5d7916](https://github.com/agentclientprotocol/claude-agent-acp/commit/f5d79167a649737bdeb1ce3b55ad449a34bb9d47))
* report Claude Code's API error message as a notice in v2 ([#1582](https://github.com/agentclientprotocol/claude-agent-acp/issues/1582)) ([99cc1f3](https://github.com/agentclientprotocol/claude-agent-acp/commit/99cc1f324586fad07f1da14bb9764cd16c64e920))
## [0.104.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.0...v0.104.1) (2026-11-10)
### Bug Fixes
* carry the auth detail in the ACP account label ([#1573](https://github.com/agentclientprotocol/claude-agent-acp/issues/1573)) ([3d6eb7b](https://github.com/agentclientprotocol/claude-agent-acp/commit/3d6eb7b5b650a1f9e5d2e869e92e313c32dbdc86))
* distinguish Ultra from Max in auth status ([#1574](https://github.com/agentclientprotocol/claude-agent-acp/issues/1574)) ([7d95d30](https://github.com/agentclientprotocol/claude-agent-acp/commit/7d95d30ec3a4813d580b3bfdedb08f03d93a3571))
* update to @anthropic-ai/claude-agent-sdk 0.3.322 ([#1577](https://github.com/agentclientprotocol/claude-agent-acp/issues/1577)) ([b89e449](https://github.com/agentclientprotocol/claude-agent-acp/commit/b89e4497da9184072e7f3c4bb28de79801659222))
## [0.104.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.103.0...v0.104.0) (2026-11-10)
### Features
* implement the ACP auth/status RFD next to _auth/status_update ([#1570](https://github.com/agentclientprotocol/claude-agent-acp/issues/1570)) ([d202748](https://github.com/agentclientprotocol/claude-agent-acp/commit/d20274897684e8813477389b83a268f2b4f4ad11))
### Bug Fixes
* offer Anthropic Console login in remote environments ([#1572](https://github.com/agentclientprotocol/claude-agent-acp/issues/1572)) ([c09543d](https://github.com/agentclientprotocol/claude-agent-acp/commit/c09543d85ae1067e8df315f1cd3179527701ea16))
## [0.103.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.102.0...v0.103.0) (2026-11-10)
### ⚠ BREAKING CHANGES
* deep imports of `dist/` files fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`, and `lib.ts` exports only `serveAcp`, `AUTH_STATUS_UPDATE_METHOD` and their types.
### Features
* serve ACP v2 with a direct v2 agent and remove the v1 bridge ([#1560](https://github.com/agentclientprotocol/claude-agent-acp/issues/1560)) ([cb285cd](https://github.com/agentclientprotocol/claude-agent-acp/commit/cb285cd2857924c07038c129cc836da007e2737f))
### Bug Fixes
* give subagent checklists their own plan ID ([#1558](https://github.com/agentclientprotocol/claude-agent-acp/issues/1558)) ([2f7e386](https://github.com/agentclientprotocol/claude-agent-acp/commit/2f7e386f3600658d905906f3b37301120b07605f))
### Code Refactoring
* export only lib.ts, with serveAcp and the authStatus wire names ([#1566](https://github.com/agentclientprotocol/claude-agent-acp/issues/1566)) ([566e265](https://github.com/agentclientprotocol/claude-agent-acp/commit/566e2652a9da5a31dab57a23ea1b06e9fff2ead8))
## [0.102.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.101.1...v0.102.0) (2026-11-09)

View File

@@ -140,7 +140,8 @@ for v2 is negotiated down to v1. With `v2`, the adapter rejects the `initialize`
On v2 the adapter serves the session baseline (`session/new`, `session/list`, `session/resume`
with replay, `session/close`, `session/prompt`, `session/cancel`), `session/delete`,
`session/fork`, additional directories, config options (the mode is one), plans, notices,
`session/fork`, `session/status`, `session/options` (with initial `configOptions` on `session/new` and
`session/resume`), `session/set_title`, additional directories, config options (the mode is one), plans, notices,
compaction, display terminals for commands, permission requests, elicitation, providers, auth, and
[subagents](#subagent-sessions). Steering is v1 only for now.

View File

@@ -78,7 +78,7 @@ A page with more sessions after it has a `nextCursor`, as ACP defines.
A client that does not follow `nextCursor` sees only the newest 1000 sessions of the directory.
An unknown cursor gives an `invalidParams` error.
`src/tests/acp-scenarios.test.ts` compares the traffic of a plain client and of Zed with recordings of origin/main.
`src/tests/v1/acp-scenarios.test.ts` compares the traffic of a plain client and of Zed with recordings of origin/main.
## Negotiation
@@ -228,7 +228,7 @@ The adapter keeps them where they are.
## Auth source display
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. The adapter also reports the same identity through the ACP `auth/status` method and `auth/status_update` notification (advertised as `auth.status`), with `kind`, `detail` and `plan` under the account's `_meta.claudeCode`. The account's `description` carries the `detail` too, e.g. `label: "Anthropic API key"` with `description: "ANTHROPIC_API_KEY"`. Each notification is sent when its own payload changed. `auth/status` answers with the identity last reported and starts a new read, whose change arrives as a notification. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
A command containing the whole word `central` and the word sequence `proxy start` is displayed as:
@@ -240,6 +240,8 @@ Matching is case-insensitive and accepts multiple whitespace characters between
Other active helpers use `label: "Anthropic API key"` and `detail: "API key helper"`; unknown key-source identifiers are not exposed as display text. CLI and SDK reads share this presentation, retain stale-probe protection, and suppress unchanged notifications.
For Max subscriptions, the existing `claude auth status --json` probe's optional `planTier` distinguishes `max_20x` (displayed as `Claude Ultra`) from `max_5x` (`Claude Max`). Missing, unrecognized, or malformed tiers fall back to the subscription's usual label. Other subscriptions and auth sources keep their existing classification. The raw `account.plan` and ACP `_meta.claudeCode.plan` remain unchanged. SDK account reads lack the tier, so they preserve the known label for the same account and plan; a fresh CLI read replaces it, including when the tier is absent. This is display metadata, not an entitlement check, and requires no additional probe or direct credential-file reads.
## Zed conventions
The adapter keeps these Zed and upstream conventions unchanged for every client.
@@ -557,7 +559,7 @@ AIR puts the hooks into `_meta.claudeCode.options.settings.hooks` of `session/ne
```
- The settings go to the flag settings tier of that session only. Other sessions and the settings files do not change.
- Claude Code runs the hooks of every settings tier, so the user, project and IDE hooks of one event all run. `src/tests/session-hooks.test.ts` checks this with the Claude Code binary.
- Claude Code runs the hooks of every settings tier, so the user, project and IDE hooks of one event all run. `src/tests/v1/session-hooks.test.ts` checks this with the Claude Code binary.
- `CLAUDE_MODEL_CONFIG` stays in effect (see [Model configuration](model-configuration.md#precedence)).
- A `session/load` with different settings recreates the query, so it applies the new hooks.
- Claude asks for no hook trust step.
@@ -1406,6 +1408,8 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
`_session/rename { "sessionId": "…", "title": "…" }` returns `{}`.
ACP's `session/set_title` renames the same way for every client, on v1 and v2, without `sessionIndex`, and returns `{ "title": "…" }`: the title as `session/list` shows it to the client. On v1 an AIR client gets it without the archive prefix, and every other client, and every v2 client, with it.
- A blank title is `-32602`. The adapter collapses whitespace and keeps the first 200 characters, trimmed, as the CLI does. An `[archived] ` prefix of the requested title is dropped: a rename never changes the archive state.
- The title is stored as the title records of [Archive](#archive): a `custom-title` and an `agent-name` record. An archived session keeps its `[archived] ` prefix (and the title is cut to 200 characters with it), so a rename does not unarchive it.
- A session that runs here on a CLI without the `rename_session` control request is refused with `-32600`: its CLI holds the title and would write it back.

View File

@@ -4,6 +4,23 @@ import tsparser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier";
import globals from "globals";
/**
* Rules that reject a module specifier matching `regex` in static imports and
* re-exports (type-only ones too), `import()` calls and `import("...")` types.
*/
function protocolBoundary(regex, message) {
// esquery regex literals end at the first `/`, so spell it `\x2F`.
const literal = `/${regex.replaceAll("/", "\\x2F")}/`;
return {
"no-restricted-imports": ["error", { patterns: [{ regex, message }] }],
"no-restricted-syntax": [
"error",
{ selector: `ImportExpression[source.value=${literal}]`, message },
{ selector: `TSImportType[argument.literal.value=${literal}]`, message },
],
};
}
export default [
{
ignores: ["node_modules/", "dist/", "coverage/", "*.min.js", "*.config.js", ".github/"],
@@ -44,5 +61,38 @@ export default [
curly: ["error", "all"],
},
},
// `src/v2/` must not import the v1 agent.
{
files: ["src/v2/**/*.ts"],
rules: protocolBoundary(
"^(\\.\\./)+v1/",
"src/v2/ must not import the v1 agent. Move what both versions need into a shared module in src/.",
),
},
// `src/v1/` must not import the v2 agent.
{
files: ["src/v1/**/*.ts"],
rules: protocolBoundary(
"^(\\.\\./)+v2/",
"src/v1/ must not import the v2 agent. Move what both versions need into a shared module in src/.",
),
},
// Shared modules must not import either agent. The entry points wire the agents up.
{
files: ["src/**/*.ts"],
ignores: [
"src/v1/**",
"src/v2/**",
"src/tests/**",
"src/index.ts",
"src/lib.ts",
"src/serve.ts",
"src/binary-entry.ts",
],
rules: protocolBoundary(
"^(\\./|(\\.\\./)+)(v1|v2)/",
"Shared modules must not import src/v1/ or src/v2/. Move the code into the shared module, or pass it in from the agent.",
),
},
prettierConfig,
];

230
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.102.0",
"version": "0.106.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.102.0",
"version": "0.106.0",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "2.5.0",
"@anthropic-ai/claude-agent-sdk": "0.3.321",
"@agentclientprotocol/sdk": "2.6.0",
"@anthropic-ai/claude-agent-sdk": "0.3.322",
"diff": "9.0.0",
"zod": "4.6.7"
},
@@ -38,9 +38,9 @@
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.5.0.tgz",
"integrity": "sha512-MFlXeIuwN6nZSK45SAsSnVjbLgQ2r4fnthoF1lQoGJP3PdaPRpOZeCF2Dds8S4cN8yZP5QbxB1bkJccGskmcYw==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.6.0.tgz",
"integrity": "sha512-3FtL6MkAV5F9qxg0FQV8wmBszIm2xuVPoDWOILIFSDQ+Vq3Twdh7Cw6Hk2GOPUbevgrTUpUAqZXQy7Geaj79mw==",
"license": "Apache-2.0",
"peerDependencies": {
"@modelcontextprotocol/server": "^2.4.0",
@@ -53,22 +53,22 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.321.tgz",
"integrity": "sha512-Smer/h/jQLTtT+l4sxaAUwZq7wif/9RzzuqltS5Gy+kAFo/iP89H7JHa8avhVT0xGkPBvBD/1Bbdg3pcArhQcw==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.322.tgz",
"integrity": "sha512-r88ij7R3SPV2j3BtFuavgrbWZNE8SVVS42AOQ1KxlS+QnSwNNnUhmVXV3Es2dniZySBYX8ENebL43ffhiB0azw==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.14.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.321",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.321",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.321",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.321",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.321",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.321",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.321",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.321"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.322",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.322"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
@@ -77,9 +77,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.321.tgz",
"integrity": "sha512-r718mW98hvRbmCa0TtSgImHkgyPUjPXSHOsORrehn2a8Z0FU1A2RkX9gCjY7EzZgL0RNbZXsBbkuab//kUjzJw==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.322.tgz",
"integrity": "sha512-LoKkldW2zkLfaOLQD44mL1Ot4Aup8dBIo356LEeO3v2vF7JVNhMllQIXhhGpbYRz57QaYsGvOe0Kb4SbssHwcg==",
"cpu": [
"arm64"
],
@@ -90,9 +90,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.321.tgz",
"integrity": "sha512-C08vEnnDylWDaFprx/B1B13RZK3la1HOqR986igkZSwhY//Yxsk5eRI+gz7sxMfhcLZX2NS8PftCOiXr3S74/Q==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.322.tgz",
"integrity": "sha512-rTvLS9G1OzjMIaSXrJglQtmRtVcaRmMbJQC+ND02+atCA463ac7Ty/Wc6tyzBGk8zFFGKWnfAhKtLZTNg91eMg==",
"cpu": [
"x64"
],
@@ -103,9 +103,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.321.tgz",
"integrity": "sha512-vdk9tON09SnsSh/6ULzYTBgsKRat6FyIaqnh4snYl3uy3qX0jpv5UuFUWgS5tvQ1ewI0qYF2k+D78El4gi7/iQ==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.322.tgz",
"integrity": "sha512-H1v33+MxB5JMPlnJLZffiOQ+6muxyA7tgHPyYQ+So4TH7uRhaPcjv2X03CUo5U9gXleO6u+X8ngNNDRFtfUaSg==",
"cpu": [
"arm64"
],
@@ -119,9 +119,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.321.tgz",
"integrity": "sha512-+8zFXMpJT2r+0j4E1v9BgRf+Qjtmmp4IG/3mDLT6jB6iA+9UvG9RLex+cqct09d+7xP2mDP8kL4cD1WtKUFX1w==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.322.tgz",
"integrity": "sha512-zvgvVA/Mxpu1QttICZ1DGDlmgGo62HdVgKg6WbDGDQ3dgxtRC/kpCIepjtGEL8O1iO5NRxQ4mv8okLwCFcyrLQ==",
"cpu": [
"arm64"
],
@@ -135,9 +135,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.321.tgz",
"integrity": "sha512-i/Y8Ps4q68SRwWB54yFXyItBwn5YHIOYY4R1gla6p4UwF/pM+xxrHycv+yecbC7oU30ShuBYKWN9mRIZ/BCMqw==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.322.tgz",
"integrity": "sha512-M/i7uV5wQf1kvjiPJr2KF5PeA9BpftUpMTtwE+DpQ0Tj+scthQXSMsEhDvau/Rh/MfFOc+UHED4tltignmA5+Q==",
"cpu": [
"x64"
],
@@ -151,9 +151,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.321.tgz",
"integrity": "sha512-ils3IGsNKIDiURuOgSWorLsjMOlKcMdXQCQyaygEycdtPFItPoIBIYDVjoC1I0XV4tlfXXCHfUvNrs23g4HKNw==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.322.tgz",
"integrity": "sha512-jprUZMcQ3XcpoxwKRQ0fi3qAM1zYcytmNGhlD0gWDknbbSu8di5jLU7231ihJrCr4gbbtDu3FiFUd6w9EBEjfw==",
"cpu": [
"x64"
],
@@ -167,9 +167,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.321.tgz",
"integrity": "sha512-v6/8ikUcYRSYhH5PSGcHq8L6+DKRq/M4I3BzFckis6kMh3degvSuUD1u3+M/oSJ/bcSOA8tXbUgTTwKrlOgvwQ==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.322.tgz",
"integrity": "sha512-o5sPmBKT4NOglODC45wSTFmjD1AQIboC9NdGFQFOTa8kMF3bok+YqpB0DF81RmMDf3iR5tieonZvOXdYm1TDjQ==",
"cpu": [
"arm64"
],
@@ -180,9 +180,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.3.321",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.321.tgz",
"integrity": "sha512-9i7ziCR+Wg47cRhYONHnUpTGxUgxfy/+J3N3/4F8YGqz7WLSNH3hpGRj6uJzNK4EMyEhFsca532uD2we0ZMckQ==",
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.322.tgz",
"integrity": "sha512-gONOkYx76of3p73opgWAT9gqpV7PRL8+az3vKoWXLc4xaDW6vhZF7fixpCnLDeUmPWxjXhnQxf30WGpKneTzNw==",
"cpu": [
"x64"
],
@@ -555,9 +555,9 @@
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
"integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz",
"integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==",
"cpu": [
"arm"
],
@@ -573,9 +573,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
"integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz",
"integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==",
"cpu": [
"arm64"
],
@@ -591,9 +591,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
"integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz",
"integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==",
"cpu": [
"arm64"
],
@@ -609,9 +609,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
"integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz",
"integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==",
"cpu": [
"x64"
],
@@ -627,9 +627,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
"integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz",
"integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==",
"cpu": [
"x64"
],
@@ -645,9 +645,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
"integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz",
"integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==",
"cpu": [
"arm"
],
@@ -663,9 +663,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
"integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz",
"integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==",
"cpu": [
"arm64"
],
@@ -684,9 +684,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
"integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz",
"integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==",
"cpu": [
"arm64"
],
@@ -705,9 +705,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
"integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz",
"integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==",
"cpu": [
"ppc64"
],
@@ -726,9 +726,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
"integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz",
"integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==",
"cpu": [
"s390x"
],
@@ -747,9 +747,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
"integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz",
"integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==",
"cpu": [
"x64"
],
@@ -768,9 +768,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
"integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz",
"integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==",
"cpu": [
"x64"
],
@@ -789,9 +789,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
"integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz",
"integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==",
"cpu": [
"arm64"
],
@@ -807,9 +807,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
"integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz",
"integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==",
"cpu": [
"arm64"
],
@@ -825,9 +825,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
"integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz",
"integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==",
"cpu": [
"x64"
],
@@ -1945,9 +1945,9 @@
}
},
"node_modules/express-rate-limit": {
"version": "8.7.3",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.3.tgz",
"integrity": "sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==",
"version": "8.7.4",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.4.tgz",
"integrity": "sha512-LKBXzX700wz3zAGNNmVnjRI5mEbYkLv7UbMI4+9Xa+JPcg2UZh6AgY/2CZh9RrJjFM5ttC/N1lD+KzyFoZITCw==",
"license": "MIT",
"peer": true,
"dependencies": {
@@ -2408,9 +2408,9 @@
"license": "ISC"
},
"node_modules/jose": {
"version": "6.2.16",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.16.tgz",
"integrity": "sha512-4LSJpbuBuPdGrONCdZUy0xnxtLf8eEz+EY0SMXdHQEBU7bgUHDVblU/JAjryv3wID9/LK7/YuwFwq+AQNfMscQ==",
"version": "6.2.17",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.17.tgz",
"integrity": "sha512-wOuK9V1yujQFH5S0ZWmLcDZN7+IKh9bUCC2ZPCf2HDlol3WWHy+PPyNjEI75FXRLMp3vDKQcupP59xhVU95hcg==",
"license": "MIT",
"peer": true,
"funding": {
@@ -3275,9 +3275,9 @@
}
},
"node_modules/rolldown": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
"integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz",
"integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -3292,21 +3292,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.16",
"@rolldown/binding-android-arm64": "1.2.16",
"@rolldown/binding-darwin-arm64": "1.2.16",
"@rolldown/binding-darwin-x64": "1.2.16",
"@rolldown/binding-freebsd-x64": "1.2.16",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
"@rolldown/binding-linux-arm64-gnu": "1.2.16",
"@rolldown/binding-linux-arm64-musl": "1.2.16",
"@rolldown/binding-linux-ppc64-gnu": "1.2.16",
"@rolldown/binding-linux-s390x-gnu": "1.2.16",
"@rolldown/binding-linux-x64-gnu": "1.2.16",
"@rolldown/binding-linux-x64-musl": "1.2.16",
"@rolldown/binding-openharmony-arm64": "1.2.16",
"@rolldown/binding-win32-arm64-msvc": "1.2.16",
"@rolldown/binding-win32-x64-msvc": "1.2.16"
"@rolldown/binding-android-arm-eabi": "1.2.18",
"@rolldown/binding-android-arm64": "1.2.18",
"@rolldown/binding-darwin-arm64": "1.2.18",
"@rolldown/binding-darwin-x64": "1.2.18",
"@rolldown/binding-freebsd-x64": "1.2.18",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.18",
"@rolldown/binding-linux-arm64-gnu": "1.2.18",
"@rolldown/binding-linux-arm64-musl": "1.2.18",
"@rolldown/binding-linux-ppc64-gnu": "1.2.18",
"@rolldown/binding-linux-s390x-gnu": "1.2.18",
"@rolldown/binding-linux-x64-gnu": "1.2.18",
"@rolldown/binding-linux-x64-musl": "1.2.18",
"@rolldown/binding-openharmony-arm64": "1.2.18",
"@rolldown/binding-win32-arm64-msvc": "1.2.18",
"@rolldown/binding-win32-x64-msvc": "1.2.18"
}
},
"node_modules/router": {
@@ -3699,9 +3699,9 @@
}
},
"node_modules/vite": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
"integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
"version": "8.3.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz",
"integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -3709,7 +3709,7 @@
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.32",
"rolldown": "~1.2.16",
"rolldown": "~1.2.18",
"tinyglobby": "^0.2.17"
},
"bin": {

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.102.0",
"version": "0.106.0",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -16,7 +16,7 @@
"types": "./dist/lib.d.ts",
"import": "./dist/lib.js"
},
"./*": "./*"
"./package.json": "./package.json"
},
"files": [
"dist/",
@@ -68,8 +68,8 @@
"node": ">=22"
},
"dependencies": {
"@agentclientprotocol/sdk": "2.5.0",
"@anthropic-ai/claude-agent-sdk": "0.3.321",
"@agentclientprotocol/sdk": "2.6.0",
"@anthropic-ai/claude-agent-sdk": "0.3.322",
"diff": "9.0.0",
"zod": "4.6.7"
},

View File

@@ -74,9 +74,24 @@ try {
process.exit(1);
}
// By default a compiled executable loads `.env`, `.env.local` and `bunfig.toml`
// from its working directory at startup. That directory is the user's project:
// its dotenv values would reach Claude and every Bash command as real
// environment variables (overriding the project's own `.env.test`), and a
// `bunfig.toml` that fails to parse would stop the agent from starting.
const bun = spawnSync(
"bun",
["build", "src/binary-entry.ts", "--compile", "--target", target, "--outfile", outfile],
[
"build",
"src/binary-entry.ts",
"--compile",
"--no-compile-autoload-dotenv",
"--no-compile-autoload-bunfig",
"--target",
target,
"--outfile",
outfile,
],
{ cwd: repoRoot, stdio: "inherit", shell: process.platform === "win32" },
);
if (bun.error) {

View File

@@ -58,8 +58,9 @@ function check(ok, label, detail) {
// ---------------------------------------------------------------- basic
function runSync(args, [cmd, ...cmdArgs] = [command, ...commandArgs]) {
function runSync(args, [cmd, ...cmdArgs] = [command, ...commandArgs], cwd) {
const result = spawnSync(cmd, [...cmdArgs, ...args], {
cwd,
encoding: "utf8",
timeout: 60_000,
});
@@ -101,6 +102,26 @@ if (positionals.length === 0) {
"without claude next to it, says the CLI is missing",
missing.stdout || missing.error,
);
// Started in a project directory, the executable must not read the
// project's dotenv files or bunfig.toml. Node stands in for `claude`, to
// print the environment the CLI is spawned with.
copyFileSync(process.execPath, join(scratch, `claude${exe}`));
const project = join(scratch, "project");
mkdirSync(project);
writeFileSync(join(project, ".env"), "ACP_SMOKE_DOTENV=leaked\n");
writeFileSync(join(project, ".env.local"), "ACP_SMOKE_DOTENV_LOCAL=leaked\n");
writeFileSync(join(project, "bunfig.toml"), "= not toml\n");
const probe = runSync(
["--cli", "-p", "[process.env.ACP_SMOKE_DOTENV, process.env.ACP_SMOKE_DOTENV_LOCAL].join()"],
[alone],
project,
);
check(
probe.stdout === ",",
"ignores .env, .env.local and bunfig.toml in the working directory",
probe.stdout || probe.error,
);
} finally {
rmSync(scratch, { recursive: true, force: true });
}

View File

@@ -1,4 +1,5 @@
import type {
AuthStatusUpdateNotification,
CompleteElicitationNotification,
CreateElicitationRequest,
CreateElicitationResponse,
@@ -15,7 +16,7 @@ import type { AcpPermissionRequest } from "./permissions/presentation.js";
* Client-facing surface the agent calls back into. This is the subset of ACP
* client methods the agent actually uses, expressed as a narrow interface so
* tests can supply lightweight mocks. In the v1 agent it is backed by
* `ClientConnection` (`acp-agent.ts`) over the SDK's typed `AgentContext`.
* `ClientConnection` (`v1/client-connection.ts`) over the SDK's typed `AgentContext`.
* The v2 session output passes one that only takes `sessionUpdate`
* (`v2/stream.ts`).
*/
@@ -38,6 +39,8 @@ export interface AcpClient {
signal?: AbortSignal,
): Promise<CreateElicitationResponse>;
completeElicitation(params: CompleteElicitationNotification): Promise<void>;
/** Sends `auth/status_update`: the connection's auth state changed. */
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void>;
/** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
}

View File

@@ -1,3 +1,5 @@
import { HIDE_CLAUDE_AUTH_FLAG } from "./hide-claude-auth.js";
/**
* The command that starts this agent again with the flags it was started with,
* for a client to run in a terminal (e.g. `terminal-auth` login).
@@ -17,3 +19,15 @@ export function agentCommand(argv: readonly string[] = process.argv): {
const compiled = script.includes("$bunfs") || script.includes("~BUN");
return { command: process.execPath, args: compiled ? flags : [script, ...flags] };
}
/**
* The arguments for the Claude CLI under `--cli`: the flags this process was
* started with, minus the ones the adapter owns. The terminal-auth command
* reruns the agent with its own flags (`--hide-claude-auth --cli auth login`),
* and the CLI rejects the unknown option `--hide-claude-auth`. `argv.slice(2)`
* is the flags under both Node and a compiled executable (see
* {@link agentCommand}).
*/
export function cliArgs(argv: readonly string[] = process.argv): string[] {
return argv.slice(2).filter((arg) => arg !== "--cli" && arg !== HIDE_CLAUDE_AUTH_FLAG);
}

View File

@@ -12,10 +12,12 @@ import { shouldHideClaudeAuth } from "./hide-claude-auth.js";
*
* Without either, the client cannot run a login, and the list is empty. On a
* remote machine (SSH, `NO_BROWSER`, `CLAUDE_CODE_REMOTE`, as the SDK's own
* isRemote check) the OAuth redirect to localhost cannot reach a browser, and
* `claude auth login` falls back to a manual flow that does not work over ACP,
* so the one method runs the TUI's `claude /login` instead. The Claude
* subscription method is left out when `shouldHideClaudeAuth()` says so.
* isRemote check) the subscription login runs the TUI's `claude /login`:
* clients such as Tidewave set `NO_BROWSER` to get it. The Anthropic Console
* login is offered there too, because since Claude Code 2.1.126 `auth login`
* accepts the OAuth code pasted into the terminal when the localhost callback
* can't be reached. The Claude subscription method is left out when
* `shouldHideClaudeAuth()` says so.
*/
export function terminalAuthMethods({
terminal,
@@ -42,10 +44,8 @@ export function terminalAuthMethods({
process.env.SSH_TTY ||
process.env.CLAUDE_CODE_REMOTE
);
if (isRemote) {
if (shouldHideClaudeAuth()) return [];
return [
withMeta(
const subscriptionLogin = isRemote
? withMeta(
{
description: "Run `claude /login` in the terminal",
name: "Log in with Claude",
@@ -54,13 +54,8 @@ export function terminalAuthMethods({
args: ["--cli"],
},
"Claude Login",
),
];
}
const methods: AuthMethod[] = [];
if (!shouldHideClaudeAuth()) {
methods.push(
withMeta(
)
: withMeta(
{
description: "Use Claude subscription ",
name: "Claude Subscription",
@@ -69,9 +64,9 @@ export function terminalAuthMethods({
args: ["--cli", "auth", "login", "--claudeai"],
},
"Claude Login",
),
);
}
);
const methods: AuthMethod[] = [];
if (!shouldHideClaudeAuth()) methods.push(subscriptionLogin);
methods.push(
withMeta(
{

View File

@@ -1,8 +1,9 @@
/**
* The connection's `authStatus` state, shared by the v1 core and the v2
* core: the last identity it reported, the `claude auth status --json` probe,
* and the `_auth/status_update` pushes (see `auth-status.ts` for when each
* core reads the identity).
* the `_auth/status_update` and `auth/status_update` pushes, and the
* `auth/status` answer (see `auth-status.ts` for when each core reads the
* identity).
*/
import { execFile } from "node:child_process";
import { promisify } from "node:util";
@@ -15,6 +16,7 @@ import {
type Settings,
} from "@anthropic-ai/claude-agent-sdk";
import {
type AcpAuthStatus,
AUTH_STATUS_PROBE_TIMEOUT_MS,
type AuthStatus,
type AuthStatusKind,
@@ -22,6 +24,7 @@ import {
fromCliStatus,
mergeAuthStatus,
sameAuthStatus,
toAcpAuthStatus,
} from "./auth-status.js";
import { claudeCliPath } from "./claude-cli.js";
import type { SettingsManager } from "./settings.js";
@@ -36,6 +39,8 @@ export type AuthStatusSettings = Pick<SettingsManager, "getCwd" | "getApiKeyHelp
export interface AuthStatusReporterOptions {
/** Sends `_auth/status_update`. */
push: (status: AuthStatus) => Promise<void>;
/** Sends the ACP `auth/status_update`. */
pushAcp: (status: AcpAuthStatus) => Promise<void>;
logger: ReporterLogger;
/** An ACP gateway login is active: the gateway, not the CLI store, is the identity. */
gatewayLogin: () => boolean;
@@ -72,11 +77,18 @@ export class AuthStatusReporter {
/** Same, for the "the CLI probe timed out" warning: a wedged CLI stays wedged
* and would otherwise warn once per probe, i.e. once per user prompt. */
private loggedProbeTimeout = false;
/** Numbers the probe runs in start order. */
private probeRuns = 0;
/** The latest-started probe run that published its read. */
private publishedRun = 0;
/** The last `auth/status_update` payload sent, as JSON. */
private sentAcp?: string;
constructor(private readonly options: AuthStatusReporterOptions) {}
/**
* Stores `next` and pushes it to the client.
* Stores `next` and pushes it to the client, as `_auth/status_update` and
* as the ACP `auth/status_update`.
*
* A push goes out only when the payload changed. The identity is read on many
* occasions — each session create, each guarded turn, the start of each user
@@ -103,6 +115,44 @@ export class AuthStatusReporter {
.catch((error) => {
this.options.logger.error("Failed to send _auth/status_update:", error);
});
// Compared on its own payload: two identities can map to the same one.
const acp = toAcpAuthStatus(next);
const sent = JSON.stringify(acp);
if (sent === this.sentAcp) {
return;
}
this.sentAcp = sent;
void Promise.resolve()
.then(() => this.options.pushAcp(acp))
.catch((error) => {
this.options.logger.error("Failed to send auth/status_update:", error);
});
}
/**
* The `auth/status` answer: the identity the connection holds, which is
* also what the last `auth/status_update` said.
*
* - No identity yet: it waits for a probe, joining the one `initialize`
* started, so a query right after `initialize` gets the identity. The
* probe is bounded by {@link AUTH_STATUS_PROBE_TIMEOUT_MS}. The answer is
* `unknown` when that probe publishes nothing: the CLI failed, printed
* no JSON, or timed out, or a login or logout overtook the read.
* - An identity is known: it answers at once and starts a new probe that
* nothing waits for. A login or logout made outside the client then
* arrives as `auth/status_update`.
*
* The probe is an ordinary one: it runs under the settings of the session
* that last started or prompted, pushes a change, and reports its kind to
* `onProbe`. Under an ACP gateway login it reads nothing.
*/
async status(): Promise<AcpAuthStatus> {
if (this.current) {
void this.probe({ fresh: true });
} else {
await this.probe();
}
return toAcpAuthStatus(this.current);
}
/** A login or a logout: any probe already running read the world before it. */
@@ -138,8 +188,8 @@ export class AuthStatusReporter {
/**
* Report the identity behind a session's `AccountInfo`.
*
* `AccountInfo` is richer than the CLI probe (it is what the live query
* actually authenticates with). The v1 core calls it before the
* `AccountInfo` describes what the live query authenticates with, but lacks
* the CLI's plan tier. The v1 core calls it before the
* `--hide-claude-auth` guard may refuse the session or the turn: that flag
* blocks the *use* of a subscription, it does not make the state secret, and
* a refusal is when the client most needs to know the account it was
@@ -175,7 +225,7 @@ export class AuthStatusReporter {
}
const fromSession = fromAccountInfo(account, apiKeyHelper);
if (fromSession) {
this.set(fromSession);
this.set(mergeAuthStatus(this.current, fromSession, "session"));
} else if (!this.loggedUninformativeAccount) {
this.loggedUninformativeAccount = true;
this.options.logger.log(
@@ -210,6 +260,7 @@ export class AuthStatusReporter {
// auth-affecting event describes a world that no longer exists. Remember
// which one this read belongs to and drop the answer if it moved on.
const epoch = this.epoch;
const run = ++this.probeRuns;
const context = this.settings;
const apiKeyHelper = context?.manager.getApiKeyHelper(context.override);
// ACP gateway auth bypasses the CLI credential store entirely, so the
@@ -276,10 +327,17 @@ export class AuthStatusReporter {
logger.log("[authStatus] discarding a probe that predates the latest auth change");
return this.current;
}
if (run < this.publishedRun) {
// Probes can overlap (`fresh`) and land out of order. A read that
// started before one already published is the older of the two.
logger.log("[authStatus] discarding a probe that a newer one overtook");
return this.current;
}
this.publishedRun = run;
// The CLI probe can be poorer than the session `AccountInfo` for the very
// same login (no organization, say). Keep those extra fields rather than
// regressing the payload; a different identity replaces it wholesale.
const merged = mergeAuthStatus(this.current, status);
const merged = mergeAuthStatus(this.current, status, "cli");
this.set(merged);
this.options.onProbe?.(status.kind);
return merged;

View File

@@ -3,10 +3,14 @@
* identity the agent process itself uses (Claude subscription, API key,
* gateway, external cloud credentials, or nothing).
*
* Push only. One carrier, connection-scoped (never per session): the
* notification `_auth/status_update` → `{authStatus}`. The client never asks;
* there is no request method. The empty `agentCapabilities._meta.authStatus`
* marker means "this agent pushes its identity", nothing more.
* Two carriers of the one identity, both connection-scoped (never per
* session):
* - the extension: the notification `_auth/status_update` → `{authStatus}`,
* advertised by the empty `agentCapabilities._meta.authStatus` marker. It
* has no request method;
* - the ACP `auth/status` RFD: the `auth/status` request and the
* `auth/status_update` notification, advertised as `auth.status`.
* {@link toAcpAuthStatus} maps the identity to their payload.
*
* The agent reads the identity at these moments:
* - `initialize`, with an asynchronous CLI probe. Nothing waits for it, and its
@@ -20,7 +24,10 @@
* login or a logout done in another terminal, including the sign-in a client
* performs after a refusal, because the retried prompt is a new prompt.
* Nothing waits for it and the push goes out mid-turn if that is when the
* read lands.
* read lands;
* - each `auth/status` request, with an asynchronous CLI probe. The request
* waits for it only while no identity is known yet (see
* `AuthStatusReporter.status`).
*
* A probe whose kind differs from a live session's account marks that session
* for recreation under `--hide-claude-auth`. The mark is a flag, consumed at
@@ -29,18 +36,18 @@
* guard decides on the account it reports.
*
* A read does not always cause a push. The agent sends `_auth/status_update`
* only when the payload is different from the last one it sent.
* only when its payload is different from the last one it sent, and
* `auth/status_update` likewise.
*
* "Cannot determine" is expressed by silence: an agent that cannot read its own
* identity pushes nothing, and the client shows "not reported". A known
* logged-out state is a payload of its own (`kind: "none"`), which is what
* distinguishes the two.
* identity pushes nothing, and `auth/status` answers `unknown`. A known
* logged-out state is a payload of its own (`kind: "none"`, `unauthenticated`),
* which is what distinguishes the two.
*
* Reporting only — there is no write path in v1. When the upstream RFD lands,
* the same payload moves from `_meta` to first-class fields and this module
* is retired.
* Reporting only — there is no write path in v1.
*/
import type { AuthAccount, AuthStatusResponse } from "@agentclientprotocol/sdk/unstable";
import type { AccountInfo } from "@anthropic-ai/claude-agent-sdk";
/** Change notification, agent → client. Fire and forget: clients that do not
@@ -85,6 +92,47 @@ export function authStatusCapability(): AuthStatusCapability {
return {};
}
/** The fields of an `auth/status` response and of an `auth/status_update`
* notification, which are the same on ACP v1 and v2. */
export type AcpAuthStatus = Pick<AuthStatusResponse, "state" | "accounts">;
/**
* Maps the identity to the ACP `auth/status` payload. The identity is the
* agent's own login, so `state` does not count a route that the client set
* with `providers/set`: a logged-out CLI is `unauthenticated` under one too.
*
* - No identity read yet (`undefined`): `unknown`, no accounts.
* - `kind: "none"`: `unauthenticated`, no accounts.
* - Any other kind: `authenticated`, with the identity as the one account,
* marked `active`. `detail` is the account's `description`, e.g. label
* "Anthropic API key" with description "ANTHROPIC_API_KEY".
* `_meta.claudeCode` carries the `kind` and `plan`, and the `detail` again
* for clients that read it there.
*
* `--hide-claude-auth` does not change the payload: it reports the login the
* CLI holds, also when the subscription guard refuses to use it.
*/
export function toAcpAuthStatus(status: AuthStatus | undefined): AcpAuthStatus {
if (!status) {
return { state: "unknown" };
}
if (status.kind === "none") {
return { state: "unauthenticated" };
}
const account: AuthAccount = { label: status.label, active: true };
if (status.detail) account.description = status.detail;
if (status.account?.email) account.email = status.account.email;
if (status.account?.organization) account.organization = status.account.organization;
account._meta = {
claudeCode: {
kind: status.kind,
...(status.detail !== undefined ? { detail: status.detail } : {}),
...(status.account?.plan !== undefined ? { plan: status.account.plan } : {}),
},
};
return { state: "authenticated", accounts: [account] };
}
/** Shape of `claude auth status --json`. Absent fields are omitted, not null,
* and the logged-out case exits 1 while still printing valid JSON. */
export type CliAuthStatus = {
@@ -96,6 +144,8 @@ export type CliAuthStatus = {
orgId?: string;
orgName?: string;
subscriptionType?: string;
/** CLI-only tier, e.g. "max_5x" or "max_20x"; not in SDK AccountInfo. */
planTier?: string;
};
const NOT_LOGGED_IN: AuthStatus = {
@@ -222,6 +272,7 @@ export function fromCliStatus(stdout: string, apiKeyHelper?: string): AuthStatus
return mapAuthFields({
apiProvider: status.apiProvider,
subscriptionType: status.subscriptionType,
planTier: status.planTier,
apiKeySource: status.apiKeySource,
email: status.email,
organization: status.orgName,
@@ -242,6 +293,7 @@ export function fromCliStatus(stdout: string, apiKeyHelper?: string): AuthStatus
function mapAuthFields(fields: {
apiProvider?: string;
subscriptionType?: string;
planTier?: string;
apiKeySource?: string;
email?: string;
organization?: string;
@@ -281,7 +333,7 @@ function mapAuthFields(fields: {
if (organization) account.organization = organization;
return {
kind: "account",
label: planLabel(subscriptionType),
label: planLabel(subscriptionType, fields.planTier),
// No detail: the client falls back to `account.email` for line 2.
account,
};
@@ -289,12 +341,14 @@ function mapAuthFields(fields: {
return notLoggedInAuthStatus();
}
/** Names the plan with exactly one "Claude". A newer CLI already reports
* "Claude Max", an older one reports "max", and both must read "Claude Max".
* Each word is title-cased; a word that already carries capitals is kept. */
function planLabel(plan: string): string {
/** Names the plan with exactly one "Claude". Without a tier, "Claude Max"
* from a newer CLI and "max" from an older one both read "Claude Max".
* Each word is title-cased; a word that already carries capitals is kept.
* The CLI's max_20x tier identifies Ultra; other tiers keep the plan label. */
function planLabel(plan: string, planTier?: string): string {
const titled = plan.replace(/\S+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1));
return /^claude(\s|$)/i.test(plan) ? titled : `Claude ${titled}`;
const label = /^claude(\s|$)/i.test(plan) ? titled : `Claude ${titled}`;
return label.toLowerCase() === "claude max" && planTier === "max_20x" ? "Claude Ultra" : label;
}
/**
@@ -322,8 +376,14 @@ export function sameIdentity(a: AuthStatus, b: AuthStatus): boolean {
* organization the CLI probe omits. So when the read describes the same
* identity, its values win field by field but the fields it lacks are kept;
* when it describes a different identity, it replaces the old one wholesale.
* Session reads lack the CLI's plan tier, so keep the label for the same plan.
* A CLI read always replaces the label, including when its tier is absent.
*/
export function mergeAuthStatus(previous: AuthStatus | undefined, next: AuthStatus): AuthStatus {
export function mergeAuthStatus(
previous: AuthStatus | undefined,
next: AuthStatus,
source: "cli" | "session",
): AuthStatus {
if (!previous || !sameIdentity(previous, next)) {
return next;
}
@@ -334,6 +394,14 @@ export function mergeAuthStatus(previous: AuthStatus | undefined, next: AuthStat
if (previous.account || next.account) {
merged.account = { ...previous.account, ...next.account };
}
if (
source === "session" &&
previous.account?.plan &&
next.account?.plan &&
planLabel(previous.account.plan).toLowerCase() === planLabel(next.account.plan).toLowerCase()
) {
merged.label = previous.label;
}
if (merged.vendor === undefined && previous.vendor !== undefined) {
merged.vendor = previous.vendor;
}

View File

@@ -6,11 +6,10 @@ import {
import {
forkSession as forkClaudeSession,
getSessionMessages,
importSessionToStore,
type SessionStore,
type SessionStoreEntry,
} from "@anthropic-ai/claude-agent-sdk";
import { createHash } from "node:crypto";
import { loadFullSessionHistory } from "./resumed-session.js";
import { assistantMessageText } from "./session-failure-extension.js";
import { SessionTiming } from "./session-timing.js";
@@ -69,19 +68,6 @@ function forkPointMessageIdCandidates(messageId: string): string[] {
return protocolMessageId === messageId ? [messageId] : [messageId, protocolMessageId];
}
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 = {
append: async (_key, batch) => {
entries.push(...batch);
},
load: async () => null,
};
await importSessionToStore(sessionId, store, { includeSubagents: false });
return entries;
}
function assistantGroups(
entries: SessionStoreEntry[],
messageIdForGrouping: ForkSessionDependencies["messageIdForGrouping"],
@@ -235,6 +221,7 @@ async function forkTranscript(
.find((message) => dependencies.messageIdForGrouping(message) === candidateId)?.uuid,
)
.find(Boolean);
// getSessionMessages returns only the active parentUuid chain. The full history keeps inactive branches that AIR can still reference.
const fullHistoryUuid = messageUuid
? undefined
: resolveFromFullHistory(

View File

@@ -2,7 +2,7 @@
* `--hide-claude-auth`: this integration must never bill a claude.ai
* subscription. Under the flag the agent hides the claude.ai login method from
* `initialize`, and refuses with `authRequired` every session and every turn
* that a subscription would pay for. `acp-agent.ts` decides when the guard
* that a subscription would pay for. `v1/acp-agent.ts` decides when the guard
* applies (flag on, no provider override); this module decides what it refuses.
*
* The guard judges the `AccountInfo` that the SDK caches at `initialize`. That
@@ -35,8 +35,10 @@
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { AccountInfo, Query } from "@anthropic-ai/claude-agent-sdk";
export const HIDE_CLAUDE_AUTH_FLAG = "--hide-claude-auth";
export function shouldHideClaudeAuth(): boolean {
return process.argv.includes("--hide-claude-auth");
return process.argv.includes(HIDE_CLAUDE_AUTH_FLAG);
}
/**
@@ -208,14 +210,14 @@ type GuardOptions = {
* covers a session created under a provider override that `providers/disable`
* later removed, so the create-time check never ran for the routing in force
* now. A sign-out ends the query (see `markSessionForSignOutRespawn` in
* `acp-agent.ts`), so the next turn recreates it and `initialize` reports the
* `v1/acp-agent.ts`), so the next turn recreates it and `initialize` reports the
* real account. This guard does not see the residual case the module header
* names either.
*
* A turn is refused when the account bills a subscription, and also when it
* holds nothing this integration accepts, which is the user signing out in
* mid-session. The refusal mirrors the signed-out path exactly (see
* `failActiveWithSessionFailure` for `auth_required` in `acp-agent.ts`): every
* `failActiveWithSessionFailure` for `auth_required` in `v1/acp-agent.ts`): every
* client gets the `authRequired` JSON-RPC rejection that starts its own auth
* flow. The guard sends no separate access message.
*

View File

@@ -5,7 +5,8 @@ export type IdleCompactionHost = {
isBusy(): boolean;
/** Context size after the last main-thread request, or undefined if unknown. */
contextTokens(): number | undefined;
/** Current merged Claude Code settings. */
/** Current Claude Code settings, the client's `settings` option included
* (`SettingsManager.getCompactionSettings`). */
settings(): {
idleCompaction?: boolean;
autoCompactEnabled?: boolean;
@@ -83,7 +84,7 @@ export class IdleCompaction {
this.setTimer = options.setTimeout ?? ((cb, ms) => setTimeout(cb, ms));
}
/** Whether an idle compaction it started is still running. `acp-agent.ts` reports
/** Whether an idle compaction it started is still running. `v1/acp-agent.ts` reports
* `trigger: "idle"` while it is true. A client's `/compact` queued behind is read
* only after the adapter's turn's `result`, which clears `compacting`; this relies
* on the CLI emitting that compaction's `compact_boundary` after the previous

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { cliArgs } from "./agent-command.js";
import { claudeCliPath } from "./claude-cli.js";
import { applyManagedPolicyEnv } from "./managed-policy.js";
import { acpProtocolsFromEnv, serveAcp } from "./serve.js";
@@ -12,7 +13,7 @@ import { join } from "node:path";
// handler below. Our version flag only applies when not delegating.
if (process.argv.includes("--cli")) {
const { spawn } = await import("node:child_process");
const args = process.argv.slice(2).filter((arg) => arg !== "--cli");
const args = cliArgs();
const child = spawn(await claudeCliPath(), args, { stdio: "inherit" });
const signals =

View File

@@ -0,0 +1,63 @@
/**
* The unstable "Session options" RFD, as both cores serve it: `session/options`
* answers with what a session would start with, and a setup request's
* `configOptions` set the initial values of the session's config options.
*/
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { Logger } from "./logger.js";
/**
* What a core advertises as `options` in its session capabilities:
* `session/options`, and `configOptions` on its setup requests.
*/
export const SESSION_OPTIONS_CAPABILITY = {};
const INVALID_PARAMS = RequestError.invalidParams().code;
/**
* The entries of a setup request's `configOptions`. Omitted and `null` are no
* entries. Two entries with the same `configId` fail with `-32602`. Call it
* before the session starts, so a refused request starts none.
*/
export function initialConfigOptions<E extends { configId: string }>(
entries: readonly E[] | null | undefined,
): readonly E[] {
const seen = new Set<string>();
for (const { configId } of entries ?? []) {
if (seen.has(configId)) {
throw RequestError.invalidParams(
{ configId },
`\`configOptions\` sets ${configId} more than once. Send one entry for each config option.`,
);
}
seen.add(configId);
}
return entries ?? [];
}
/**
* Sets each entry with `set`, in list order. `set` checks an entry against the
* options as the entries before it left them, so a model entry decides which
* effort levels a later entry can pick.
*
* An entry that `set` refuses is skipped, and the entries after it still
* apply. The option keeps its value, which the setup response reports:
*
* - `-32602`: an unknown `configId`, a value of another type than the
* option's, or a value that the option does not offer.
* - Any other failure, such as a model that Claude Code refuses. It is logged.
*/
export async function applyInitialConfigOptions<E extends { configId: string }>(
entries: readonly E[],
set: (entry: E) => Promise<unknown>,
logger: Logger,
): Promise<void> {
for (const entry of entries) {
try {
await set(entry);
} catch (error) {
if (error instanceof RequestError && error.code === INVALID_PARAMS) continue;
logger.error(`Could not set the initial value of config option ${entry.configId}:`, error);
}
}
}

View File

@@ -1,19 +1,7 @@
// Export the main agent class and utilities for library usage
export { ClaudeAcpAgent, runAcp } from "./acp-agent.js";
export { isLocalCommandMetadata, stripLocalCommandMetadata } from "./message-filters.js";
export { toAcpNotifications, streamEventToAcpNotifications } from "./sdk-to-acp.js";
export type { NewSessionMeta, SDKMessageFilter } from "./new-session-meta.js";
export type { ToolUpdateMeta } from "./tool-calls/renderer.js";
export { nodeToWebReadable, nodeToWebWritable, Pushable, unreachable } from "./utils.js";
export {
toolInfoFromToolUse,
toDisplayPath,
planEntries,
toolUpdateFromToolResult,
} from "./tools.js";
export { SettingsManager, type SettingsManagerOptions } from "./settings.js";
// The `authStatus` extension's wire surface: the notification's method name and
// the payload types needed to read the traffic. The mappers stay internal.
// The package's public API: serve ACP (v1 and v2) on stdio, and the wire
// names of the `authStatus` extension that a client needs to read its traffic.
export { serveAcp, type AcpProtocols } from "./serve.js";
export type { Logger } from "./logger.js";
export {
AUTH_STATUS_UPDATE_METHOD,
type AuthStatus,
@@ -21,6 +9,3 @@ export {
type AuthStatusKind,
type AuthStatusUpdateNotification,
} from "./auth-status.js";
// Export types
export type { ClaudePlanEntry } from "./tools.js";

View File

@@ -87,6 +87,22 @@ function hasMarkerTag(text: string, tag: string): boolean {
return markerTagText(text, tag) !== undefined;
}
/**
* Whether the content of a user message is only the output of a local
* command, such as the `<local-command-stdout>Set model to …</local-command-stdout>`
* that Claude Code echoes when the model changes.
*/
export function isLocalCommandOutput(content: unknown): boolean {
if (typeof content !== "string") return false;
if (
!hasMarkerTag(content, "local-command-stdout") &&
!hasMarkerTag(content, "local-command-stderr")
) {
return false;
}
return stripMarkerTags(content).trim() === "";
}
function commandInvocationFromMarkerOnlyText(text: string): string | null {
if (hasMarkerTag(text, "local-command-stdout") || hasMarkerTag(text, "local-command-stderr")) {
return null;
@@ -139,10 +155,6 @@ export function stripLocalCommandMetadata(content: unknown): unknown | null {
return kept;
}
export function isLocalCommandMetadata(content: unknown): boolean {
return stripLocalCommandMetadata(content) === null;
}
/**
* True for the synthetic assistant message the CLI injects into the transcript
* when a turn fails authentication (e.g. "Not logged in · Please run /login",

View File

@@ -5,3 +5,16 @@ import * as path from "node:path";
export function claudeConfigDir(): string {
return process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
}
/** `<config>/projects`, normalized like the SDK does. */
export function projectsRoot(): string {
return path.join(claudeConfigDir(), "projects").normalize("NFC");
}
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
* transcript, and only such ids are safe to use as a file name. */
export function isSessionId(value: unknown): value is string {
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
}

View File

@@ -2,7 +2,7 @@
* How Claude Code's `result` ends the work that it reports, and whether that
* work was the user's turn or a cycle Claude Code started on its own: the
* rules that both cores read a turn's result with (the v1 prompt loop in
* `acp-agent.ts` and `v2/`). Each core adds its own side effects, such as v1's
* `v1/acp-agent.ts` and `v2/`). Each core adds its own side effects, such as v1's
* session failure category.
*/
import type {

View File

@@ -1,6 +1,13 @@
import { getSessionMessages, type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
import {
getSessionMessages,
importSessionToStore,
type SessionMessage,
type SessionStore,
type SessionStoreEntry,
} from "@anthropic-ai/claude-agent-sdk";
import { access, open, readdir } from "node:fs/promises";
import path from "node:path";
import { isCompactSummaryMessage } from "./context-compaction.js";
import { claudeConfigDir } from "./paths.js";
import { SessionTiming } from "./session-timing.js";
@@ -68,6 +75,97 @@ export async function readResumedSession(
}
}
/**
* Read the messages of a session from its first prompt on, given the
* `getSessionMessages` chain of the session.
*
* `getSessionMessages` follows `parentUuid` links. Claude Code writes each
* `compact_boundary` with `parentUuid: null`, so after a compaction the chain
* starts at its summary. The boundary keeps the last message before it in
* `logicalParentUuid`. For a chain that starts at a summary, this links each
* boundary to that message and reads the transcript through
* `getSessionMessages` again. For a session compacted twice, the result has
* both summaries and the messages before, between and after them. The result
* ends at the last message of `messages`. Any other chain is returned as it
* is, and so is the chain when the read fails or when its result does not
* reach that last message (e.g. the transcript changed between the reads).
*
* `dir` is the project directory of the first read: both reads then resolve
* the same transcript file. Without it, all project directories are searched.
*
* The preserved-message metadata of each boundary is dropped. With it,
* `getSessionMessages` moves the messages that a compaction kept to after the
* summary, and the relinked boundary then leads from them back to the summary:
* the chain loops and ends there. Without it, the kept messages stay where
* they were said, before the boundary.
*/
export async function readHistoryAcrossCompactions(
sessionId: string,
messages: SessionMessage[],
logger?: ResumeLogger,
dir?: string,
): Promise<SessionMessage[]> {
if (!isCompactSummaryMessage(messages[0])) return messages;
try {
const entries = (await loadFullSessionHistory(sessionId, dir)).map(linkCompactBoundary);
const history = await getSessionMessages(sessionId, {
dir,
sessionStore: { append: async () => {}, load: async () => entries },
});
const leaf = messages.at(-1)?.uuid;
const end = history.findIndex((message) => message.uuid === leaf);
if (end < 0) {
logger?.error(
`The history before the last compaction of session ${sessionId} does not reach message ${leaf}: replaying the messages after it`,
);
return messages;
}
return history.slice(0, end + 1);
} catch (error) {
logger?.error(
`Failed to read the history before the last compaction of session ${sessionId}:`,
error,
);
return messages;
}
}
/** Every record of a session's transcript, including the inactive branches
* and the history before a compaction that `getSessionMessages` leaves out.
* Without `dir`, all project directories are searched. */
export async function loadFullSessionHistory(
sessionId: string,
dir?: string,
): Promise<SessionStoreEntry[]> {
const entries: SessionStoreEntry[] = [];
const store: SessionStore = {
append: async (_key, batch) => {
entries.push(...batch);
},
load: async () => null,
};
await importSessionToStore(sessionId, store, { dir, includeSubagents: false });
return entries;
}
/** The record unchanged, or a compaction boundary linked to the message before it. */
function linkCompactBoundary(entry: SessionStoreEntry): SessionStoreEntry {
if (
entry.type !== "system" ||
entry.subtype !== "compact_boundary" ||
typeof entry.logicalParentUuid !== "string"
) {
return entry;
}
const compactMetadata: Record<string, unknown> =
typeof entry.compactMetadata === "object" && entry.compactMetadata !== null
? { ...entry.compactMetadata }
: {};
delete compactMetadata.preservedSegment;
delete compactMetadata.preservedMessages;
return { ...entry, parentUuid: entry.logicalParentUuid, compactMetadata };
}
/** What a resume restores from the end of the local transcript. */
export type ResumedTail = {
model?: string;

View File

@@ -1,5 +1,5 @@
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
import { v1AgentApp, type ClaudeAcpAgent } from "./acp-agent.js";
import { v1AgentApp, type ClaudeAcpAgent } from "./v1/acp-agent.js";
import type { Logger } from "./logger.js";
import { nodeToWebReadable, nodeToWebWritable } from "./utils.js";
import { v2AgentApp, type AgentHandle } from "./v2/agent.js";

View File

@@ -1,6 +1,7 @@
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
import {
getSessionMessages,
type SDKAPIRetryMessage,
type SDKAssistantMessageError,
USAGE_LIMIT_ERROR_PREFIXES,
} from "@anthropic-ai/claude-agent-sdk";
@@ -446,6 +447,84 @@ export class SessionFailureController {
}
}
/** Formats a wait in whole seconds, at least 1s: `8000` → `"8s"`. */
function retrySeconds(ms: number): string {
return `${Math.max(1, Math.round(ms / 1000))}s`;
}
/** The wait before the next attempt: `0` → `"now"`, `8000` → `"in 8s"`, and
* from 120 s whole minutes rounded up: `1_800_000` → `"in 30m"`. The CLI sends
* `0` for a `no_response` retry and minutes for a background rate-limit wait. */
function nextAttemptDelay(ms: number): string {
const seconds = Math.round(ms / 1000);
if (seconds <= 0) return "now";
if (seconds < 120) return `in ${seconds}s`;
return `in ${Math.ceil(ms / 60_000)}m`;
}
/** Plain-English names of the SDK error kinds. `unknown` has none: it adds
* nothing to the HTTP status or the connection error. */
const RETRY_ERROR_KIND_LABELS: Record<Exclude<SDKAssistantMessageError, "unknown">, string> = {
authentication_failed: "authentication failed",
oauth_org_not_allowed: "organization not allowed",
account_on_hold: "account on hold",
verification_required: "verification required",
billing_error: "billing error",
rate_limit: "rate limited",
overloaded: "overloaded",
invalid_request: "invalid request",
model_not_found: "model not found",
server_error: "server error",
max_output_tokens: "output token limit",
cloud_credential_error: "cloud credential error",
};
/** The `api_retry` message fields the warning reads. `error` is optional
* because an older CLI can omit it; the warning then treats it as `unknown`. */
type ApiRetryWarningInput = Pick<
SDKAPIRetryMessage,
"attempt" | "max_retries" | "error_status" | "retry_delay_ms" | "no_response"
> & { error?: SDKAssistantMessageError };
/** The cause of a retry. A null `error_status` with a kind is not always a
* lost connection: the CLI maps an SSE `overloaded_error` event after HTTP 200
* to `overloaded` with no status. */
function apiRetryCause(message: ApiRetryWarningInput): string {
const kind = message.error ?? "unknown";
const label =
kind === "unknown"
? undefined
: (RETRY_ERROR_KIND_LABELS[kind] ?? String(kind).replaceAll("_", " "));
if (message.error_status !== null) {
return `HTTP ${message.error_status}${label ? ` (${label})` : ""}.`;
}
return label ? `No HTTP status (${label}).` : "Connection error: no HTTP response.";
}
/** The title and the `details` of an `api_retry` warning. The title says the
* attempt and, for a `no_response` retry, how long the CLI waited and will
* wait. The details say the cause and the wait before the next attempt, e.g.
* "HTTP 529 (overloaded). Next attempt in 8s." The details do not repeat the
* `no_response` wait. */
export function apiRetryWarning(message: ApiRetryWarningInput): {
title: string;
details: string;
} {
// `no_response` (SDK 0.3.261+): the API sent no response headers
// within the first-byte window, so this retry waits longer for
// them. Say so — "attempt 1 of 1" alone reads like a final
// failure, and the wait is what the user is about to sit through.
const noResponse = message.no_response
? ` No response after ${retrySeconds(message.no_response.waited_ms)}; waiting up to ${retrySeconds(message.no_response.retry_wait_ms)}.`
: "";
const title =
message.error_status === null
? `Reconnecting to Claude, attempt ${message.attempt} of ${message.max_retries}.${noResponse}`
: `Retrying Claude, attempt ${message.attempt} of ${message.max_retries}.${noResponse}`;
const delay = nextAttemptDelay(message.retry_delay_ms);
return { title, details: `${apiRetryCause(message)} Next attempt ${delay}.` };
}
export function providerFailureCategory(
errorKind?: SDKAssistantMessageError,
isUsageLimit = false,

View File

@@ -49,7 +49,8 @@ import {
type LiveSessionRegistry,
type LiveSnapshot,
} from "./live-registry.js";
import { isExactProjectDir, isSessionId, projectDirMatches, projectsRoot } from "./project-dirs.js";
import { isSessionId, projectsRoot } from "../paths.js";
import { isExactProjectDir, projectDirMatches } from "./project-dirs.js";
import { type IndexRow, type SessionIndex, type TranscriptCandidate } from "./session-index.js";
import { changeSignature, sessionInfoOf } from "./session-info.js";

View File

@@ -14,24 +14,10 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { claudeConfigDir } from "../paths.js";
/** The SDK cuts an encoded path at this length and appends a hash. */
const MAX_SANITIZED_LENGTH = 200;
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
* transcript, and only such ids are safe to use as a file name. */
export function isSessionId(value: unknown): value is string {
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
}
/** `<config>/projects`, normalized like the SDK does. */
export function projectsRoot(): string {
return path.join(claudeConfigDir(), "projects").normalize("NFC");
}
function sanitize(value: string): string {
return value.replace(/[^a-zA-Z0-9]/g, "-");
}

115
src/session-index/rename.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* Client renames of a session: `session/set_title` (v1 and v2) and AIR's
* `_session/rename` (v1). Both cores call {@link renameSession}, which stores
* the title in every transcript of the session (see
* {@link SessionIndexService.retitle}) and, for a session loaded on the
* connection, makes it the client title that no generated title replaces.
*/
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import type { Query } from "@anthropic-ai/claude-agent-sdk";
import type { SessionTitles, TitledSession } from "../session-titles.js";
import { storedTitle } from "./archive-title.js";
import {
renameTo,
type RenameSessionRequest,
type RetitleOptions,
type SessionIndexService,
type TitleChange,
} from "./service.js";
/** A session loaded on the connection, and its title state. */
export interface RetitledSession {
readonly session: TitledSession;
readonly titles: Pick<
SessionTitles,
"setExplicitTitle" | "storedTitle" | "rememberStoredTitle" | "shownTitle"
>;
}
type RenamingQuery = Query & {
renameSession?: (title: string, sessionId?: string) => Promise<void>;
};
/**
* Names a session; no generated title replaces it. An archived session stays
* archived. `loaded` is the session loaded on this connection, if any;
* `clientTitle` turns a stored title into the one the client shows (an AIR
* client's drops the archive prefix). Returns that title, which the loaded
* session also publishes as a `session_info_update`.
*
* A session that is not loaded and has no transcript is unknown (`-32002`).
*/
export async function renameSession(
index: SessionIndexService,
request: RenameSessionRequest,
loaded: RetitledSession | undefined,
clientTitle: (stored: string) => string = (stored) => stored,
): Promise<string> {
const { sessionId } = request;
// As the CLI keeps it, and never with the archive prefix: a rename does
// not change the archive state.
const title = storedTitle(request.title, false, sessionId);
// The title as stored: an archived one is cut with its prefix.
const shown = (stored: string | undefined) => clientTitle(stored ?? title);
const stored = await retitleSession(index, sessionId, renameTo(title), shown, loaded, {
mayBeUnwritten: true,
sidecar: "always",
});
return shown(stored);
}
/**
* Retitles a session (see {@link SessionIndexService.retitle}) and returns
* the title stored. A session that runs here is titled through its CLI
* (`rename_session`). A loaded session's title state takes the change as a
* client title, so no generated title replaces it; `publish` is the title it
* then publishes.
*/
export async function retitleSession(
index: SessionIndexService,
sessionId: string,
change: TitleChange,
publish: Parameters<SessionTitles["setExplicitTitle"]>[0],
loaded: RetitledSession | undefined,
options: Pick<RetitleOptions, "mayBeUnwritten" | "sidecar">,
): Promise<string | undefined> {
let stored: string | undefined;
// Decided once a title generation in flight has ended: the query may
// have closed meanwhile.
const persist = async () => {
const session = loaded?.session;
const query = session?.query as RenamingQuery | undefined;
// A closed session's CLI is gone here: only a running query titles it.
const running = session !== undefined && !session.queryClosed;
if (running && typeof query?.renameSession !== "function") {
// Its CLI holds the title and would write it back over ours.
throw RequestError.invalidRequest(
{ sessionId },
"The Claude Code CLI of this session cannot change its title while it runs.",
);
}
const live = running
? {
cwd: session.cwd,
rename: (title: string) => query!.renameSession!(title, sessionId),
stored: () => loaded!.titles.storedTitle,
remember: (title: string) => loaded!.titles.rememberStoredTitle(title),
shown: loaded!.titles.shownTitle,
}
: undefined;
stored = await index.retitle(sessionId, change, {
...options,
// Without its CLI, a session needs its transcript.
mayBeUnwritten: live !== undefined && options.mayBeUnwritten,
live,
});
return stored;
};
if (loaded) {
await loaded.titles.setExplicitTitle(publish, persist);
} else {
await persist();
}
return stored;
}

View File

@@ -36,11 +36,11 @@ import {
type ListSubscriptionDeps,
} from "./list-subscriptions.js";
import { LiveSessionRegistry } from "./live-registry.js";
import { isSessionId } from "../paths.js";
import {
canonicalPath,
exists,
isExactProjectDir,
isSessionId,
projectDirMatches,
sameProjectPath,
} from "./project-dirs.js";
@@ -466,6 +466,18 @@ export type SessionIndexDeps = {
logError: (message: string, error: unknown) => void;
};
/** The transcript reads and writes of the session index, without its list
* or watcher: what a client without `sessionIndex` uses for
* `session/set_title`, AIR's archive on delete, and delete's ordering
* after them (see {@link SessionIndexService.exclusive}). */
export function transcriptWriter(logger: {
error: (message: string, error: unknown) => void;
}): SessionIndexService {
return new SessionIndexService({
logError: (message, error) => logger.error(`[session-index] ${message}:`, error),
});
}
export class SessionIndexService {
readonly index: SessionIndex;
readonly registry: LiveSessionRegistry;
@@ -541,8 +553,10 @@ export class SessionIndexService {
};
}
/** Runs `mutation` after the previous mutation of the session ended. */
private exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
/** Runs `mutation` after the previous mutation of the session ended, such
* as a delete that does not go through {@link delete}, so that a retitle
* cannot append to a transcript the delete is removing. */
exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
const previous = this.mutations.get(sessionId) ?? Promise.resolve();
const next = previous.catch(() => undefined).then(mutation);
const settled = next.catch(() => undefined);

View File

@@ -23,18 +23,17 @@ import * as fs from "node:fs/promises";
import * as path from "node:path";
import { sanitizeTitle } from "../session-titles.js";
import { effectiveTitle, visibleTitle } from "./archive-title.js";
import { isSessionId, projectsRoot } from "../paths.js";
import {
canonicalPath,
encodeProjectPath,
errorCode,
exists,
isExactProjectDir,
isSessionId,
normalizePath,
pathAndAncestors,
projectDirMatches,
projectDirsOf,
projectsRoot,
sameProjectPath,
} from "./project-dirs.js";
import {

View File

@@ -285,7 +285,16 @@ export class SessionModeManager<S extends SessionMode> {
return true;
}
async selectMode(sessionId: string, modeId: string): Promise<PermissionMode> {
/**
* Sets the mode on the query. `auto` on a model without Auto runs as its
* fallback, with the warning. With `deferWarning`, the warning waits for
* the next prompt (see {@link deferFallbackWarning}).
*/
async selectMode(
sessionId: string,
modeId: string,
deferWarning = false,
): Promise<PermissionMode> {
const session = this.requireOpenSession(sessionId);
const requestedMode = this.parseMode(modeId);
if (!this.isAvailable(session.modes.availableModes, requestedMode)) {
@@ -308,7 +317,8 @@ export class SessionModeManager<S extends SessionMode> {
}
if (effectiveMode !== requestedMode) {
await this.publishFallbackWarning(sessionId, session);
if (deferWarning) this.deferFallbackWarning(session);
else await this.publishFallbackWarning(sessionId, session);
}
return effectiveMode;
}
@@ -352,6 +362,16 @@ export class SessionModeManager<S extends SessionMode> {
}
}
/**
* Leaves the Auto fallback's warning for the next prompt
* (`autoModeFallbackWarningPending`), as a session's start does. For a
* fallback during session setup: the client cannot place an update of a
* session before the setup response names it.
*/
deferFallbackWarning(session: SessionMode): void {
if (!session.autoModeFallbackWarningShown) session.autoModeFallbackWarningPending = true;
}
async publishFallbackState(sessionId: string, session: SessionMode): Promise<void> {
await this.publishCurrent(sessionId, AUTO_MODE_FALLBACK);
await this.publishFallbackWarning(sessionId, session);

68
src/session-status.ts Normal file
View File

@@ -0,0 +1,68 @@
/**
* `session/status` (the unstable "Session status" RFD) of a session that the
* connection does not hold: `saved` or unknown. Each core answers `open`
* itself for the sessions it holds.
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { RequestError } from "@agentclientprotocol/sdk/unstable";
import { isSessionId, projectsRoot } from "./paths.js";
/** What a core advertises as `status` in its session capabilities. */
export const SESSION_STATUS_CAPABILITY = {};
/**
* `saved` when a project directory holds a transcript file of `sessionId`
* that is not empty. Its content is not read, so a transcript without a
* conversation is `saved` too, and its resume fails with `-32002`. Fails with
* `-32002` and `data.sessionId` otherwise:
*
* - an id that is not a UUID, such as a subagent's session id;
* - an id without a transcript: one never used, a session never prompted
* (Claude Code writes the transcript at the first prompt), and a session
* whose transcripts were removed.
*
* The request names no `cwd`, so a transcript of any project counts: a
* resume with another `cwd` can still fail with `-32002`.
*
* It lists the project directories and stats the one file name in each. No
* query starts and no session is loaded. A directory or file that cannot be
* read for another reason than being absent fails the request: it does not
* make the session unknown.
*/
export async function savedSessionStatus(sessionId: string): Promise<{ status: "saved" }> {
if (!isSessionId(sessionId) || !(await hasTranscript(sessionId))) {
throw RequestError.sessionNotFound(sessionId);
}
return { status: "saved" };
}
async function hasTranscript(sessionId: string): Promise<boolean> {
const root = projectsRoot();
let projects: string[];
try {
projects = await fs.readdir(root);
} catch (error) {
// No session was ever stored.
if (isAbsent(error)) return false;
throw error;
}
const found = await Promise.all(
projects.map((project) =>
fs.stat(path.join(root, project, `${sessionId}.jsonl`)).then(
(stats) => stats.isFile() && stats.size > 0,
(error: unknown) => {
// No such transcript, or `project` is a file.
if (isAbsent(error)) return false;
throw error;
},
),
),
);
return found.includes(true);
}
function isAbsent(error: unknown): boolean {
const code = (error as { code?: unknown }).code;
return code === "ENOENT" || code === "ENOTDIR";
}

View File

@@ -12,7 +12,7 @@
* `CLAUDE_CODE_DISABLE_TERMINAL_TITLE` set), the same request generates it.
*
* {@link SessionTitles} holds the per-session state and is owned by the
* session, in the v1 core (`acp-agent.ts`) and the v2 core
* session, in the v1 core (`v1/acp-agent.ts`) and the v2 core
* (`v2/session.ts`). Each drives it from the same places:
* {@link SessionTitles.onPrompt} and {@link SessionTitles.onAssistantText} to
* collect text, {@link SessionTitles.onPromptQueued} once a prompt is sent to

View File

@@ -33,7 +33,7 @@ export const ZERO_USAGE = Object.freeze({
export const DEFAULT_CONTEXT_WINDOW = 200000;
/** `_meta.quota` for work that spent `usage` in its main agent loop and
* `modelUsage` by model (see `turnQuotaMeta` in `acp-agent.ts`). */
* `modelUsage` by model (see `turnQuotaMeta` in `v1/acp-agent.ts`). */
export function quotaMeta(usage: AccumulatedUsage, modelUsage: ModelTokenTally | undefined) {
return {
quota: {

View File

@@ -37,6 +37,15 @@ export interface SettingsManagerOptions {
logger?: { log: (...args: any[]) => void; error: (...args: any[]) => void };
}
type ModelSelectionSettings = Pick<Settings, "availableModels" | "modelOverrides">;
/** The settings that decide whether Claude Code compacts (see `idle-compaction.ts`). */
export type CompactionSettings = {
idleCompaction?: boolean;
autoCompactEnabled?: boolean;
env?: Record<string, string>;
};
/**
* Manages Claude Code settings using the SDK's `resolveSettings` merge engine
* so the values we see match what `query()` would observe.
@@ -50,7 +59,9 @@ export class SettingsManager {
private cwd: string;
private effective: Settings = {};
private managedDeniedModels: string[] = [];
private managedModelSettings: ModelSelectionSettings = {};
private managedApiKeyHelper?: string;
private managedCompactionSettings: CompactionSettings = {};
private watchers: fs.FSWatcher[] = [];
private onChange?: () => void;
private logger: { log: (...args: any[]) => void; error: (...args: any[]) => void };
@@ -112,15 +123,36 @@ export class SettingsManager {
this.managedDeniedModels = resolved.sources
.filter((entry) => entry.source === "managed")
.flatMap((entry) => entry.settings.deniedModels ?? []);
this.managedModelSettings = Object.assign(
{},
...resolved.sources
.filter((entry) => entry.source === "managed")
.map((entry) => ({
availableModels: entry.settings.availableModels,
modelOverrides: entry.settings.modelOverrides,
})),
);
this.managedApiKeyHelper =
resolved.provenance.apiKeyHelper?.source === "managed"
? resolved.effective.apiKeyHelper
: undefined;
const managed = resolved.sources
.filter((entry) => entry.source === "managed")
.map((entry) => entry.settings);
this.managedCompactionSettings = {
idleCompaction: managed.findLast((tier) => tier.idleCompaction !== undefined)
?.idleCompaction,
autoCompactEnabled: managed.findLast((tier) => tier.autoCompactEnabled !== undefined)
?.autoCompactEnabled,
env: Object.assign({}, ...managed.map((tier) => tier.env)),
};
} catch (error) {
this.logger.error("Failed to resolve settings:", error);
this.effective = {};
this.managedDeniedModels = [];
this.managedModelSettings = {};
this.managedApiKeyHelper = undefined;
this.managedCompactionSettings = {};
}
}
@@ -193,6 +225,51 @@ export class SettingsManager {
);
}
/** Model selection settings including the inline SDK settings tier. */
getModelSelectionSettings(override?: Settings): ModelSelectionSettings {
// The SDK unions non-managed allowlists, but a managed list owns the
// whole allowlist. Keep that policy when adding
// models the SDK did not include in its initialization result.
const lists = [this.effective.availableModels, override?.availableModels].filter(
(list): list is string[] => Array.isArray(list),
);
const managed = this.managedModelSettings;
return {
availableModels:
managed.availableModels ?? (lists.length > 0 ? [...new Set(lists.flat())] : undefined),
modelOverrides: {
...this.effective.modelOverrides,
...override?.modelOverrides,
...managed.modelOverrides,
},
};
}
/**
* `idleCompaction`, `autoCompactEnabled` and `env` as the CLI resolves them:
* managed settings outrank the `--settings` override, which outranks the
* user/project/local files. `env` merges entry by entry in the same order. A
* key whose value is `undefined` in the override does not hide the files' value.
*/
getCompactionSettings(settingsOverride?: Settings): CompactionSettings {
const managed = this.managedCompactionSettings;
const env: Record<string, string> = {};
for (const tier of [this.effective.env, settingsOverride?.env, managed.env]) {
for (const [name, value] of Object.entries(tier ?? {})) {
if (value !== undefined) env[name] = value;
}
}
return {
idleCompaction:
managed.idleCompaction ?? settingsOverride?.idleCompaction ?? this.effective.idleCompaction,
autoCompactEnabled:
managed.autoCompactEnabled ??
settingsOverride?.autoCompactEnabled ??
this.effective.autoCompactEnabled,
env,
};
}
/**
* `deniedModels` entries from the managed tier, the only tier the CLI
* honors them from.

View File

@@ -84,7 +84,7 @@ export type ReplayChild = {
responseModel?: string;
};
type SendMessageCall = {
export type SendMessageCall = {
toolUseId: string;
to: string;
message: string;
@@ -97,34 +97,27 @@ type SendMessageCall = {
outgoingSent: boolean;
};
export class RfdSubagentReplay {
/** The children by the id of the tool use that launched each. */
private readonly children = new Map<string, ReplayChild>();
private readonly byAgentId = new Map<string, ReplayChild>();
private readonly sendMessages: SendMessageCall[] = [];
/** Launches without metadata, which the replay leaves out. */
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 }
>();
/** A SubagentHandback call: its report, whether its result failed, and whether the replay sent it. */
type ReplayHandback = { 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,
) {}
/**
* What both replays read from the transcripts before they send anything: the
* children that launches with metadata started, the launches without metadata
* (left out), the SendMessage calls, and the SubagentHandback calls. It sends
* nothing; `RfdSubagentReplay` (v1) and `src/v2/subagent-replay.ts` send the
* replay from it.
*/
export class ReplayTranscript<Child extends ReplayChild = ReplayChild> {
/** The children by the id of the tool use that launched each. */
readonly children = new Map<string, Child>();
readonly byAgentId = new Map<string, Child>();
readonly sendMessages: SendMessageCall[] = [];
/** Launches without metadata, which the replay leaves out. */
readonly omitted = new Set<string>();
/** The SubagentHandback calls, by tool use id. */
readonly handbacks = new Map<string, ReplayHandback>();
constructor(private readonly history: SubagentHistory) {}
/**
* Registers the launches, the launch results, and the SendMessage calls in
@@ -165,9 +158,64 @@ export class RfdSubagentReplay {
}
}
private registerLaunch(
toolUseId: string,
input: Record<string, unknown>,
parentSessionId: string,
): void {
if (this.children.has(toolUseId)) return;
const record = this.history.agents.get(toolUseId);
if (!record) {
this.omitted.add(toolUseId);
return;
}
// A child type adds only optional fields, which a new child leaves unset.
const child = {
launchId: toolUseId,
sessionId: record.agentId,
record,
parentSessionId,
input,
announced: false,
promptSent: false,
} as Child;
this.children.set(toolUseId, child);
this.byAgentId.set(record.agentId, child);
}
}
export class RfdSubagentReplay {
private readonly transcript: ReplayTranscript;
/** Children whose transcript could not be read: only their association is replayed. */
private withoutHistory = 0;
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,
) {
this.transcript = new ReplayTranscript(history);
}
/**
* Registers the launches, the launch results, and the SendMessage calls in
* `messages`, the transcript of the session `ownerSessionId`.
*/
scan(messages: readonly SessionMessage[], ownerSessionId: string): void {
this.transcript.scan(messages, ownerSessionId);
}
/** The child that the tool use `toolUseId` launched, when the replay shows it. */
childOfLaunch(toolUseId: string): ReplayChild | undefined {
return this.children.get(toolUseId);
return this.transcript.children.get(toolUseId);
}
/**
@@ -211,11 +259,11 @@ export class RfdSubagentReplay {
*/
private effectiveCwd(sessionId: string): string | undefined {
const seen = new Set<string>();
let child = this.byAgentId.get(sessionId);
let child = this.transcript.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);
child = this.transcript.byAgentId.get(child.parentSessionId);
}
return this.rootCwd;
}
@@ -299,7 +347,9 @@ export class RfdSubagentReplay {
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
return false;
}
const sendMessage = this.sendMessages.find((call) => call.toolUseId === update.toolCallId);
const sendMessage = this.transcript.sendMessages.find(
(call) => call.toolUseId === update.toolCallId,
);
if (sendMessage) {
if (sendMessage.recipientSessionId === undefined) {
sendMessage.shownAsToolCall = true;
@@ -317,7 +367,7 @@ export class RfdSubagentReplay {
}
return true;
}
const handback = owner ? this.handbacks.get(update.toolCallId) : undefined;
const handback = owner ? this.transcript.handbacks.get(update.toolCallId) : undefined;
if (owner && handback && !handback.failed) {
if (!handback.sent) {
handback.sent = true;
@@ -333,7 +383,7 @@ export class RfdSubagentReplay {
}
return true;
}
const child = this.children.get(update.toolCallId);
const child = this.transcript.children.get(update.toolCallId);
if (!child) return false;
if (
update.status === "completed" &&
@@ -352,29 +402,29 @@ export class RfdSubagentReplay {
* its task id is the agent id.
*/
taskNotification(taskId: string, status: string): boolean {
const child = this.byAgentId.get(taskId);
const child = this.transcript.byAgentId.get(taskId);
if (!child) return false;
child.notified = nativeSubagentState(status) ?? child.notified;
return true;
}
/**
* Sends the end of every announced child (see `endState`), and a notice when
* Sends the end of every announced child (see `replayEndState`), and a notice when
* subagent history is missing.
*/
async finish(supportsNotices: boolean, rootSessionId: string): Promise<void> {
for (const child of [...this.children.values()].reverse()) {
for (const child of [...this.transcript.children.values()].reverse()) {
if (!child.announced) continue;
await this.publish({
sessionId: child.parentSessionId,
update: {
sessionUpdate: "subagent_update",
sessionId: child.sessionId,
state: endState(child),
state: replayEndState(child),
},
});
}
const missing = this.omitted.size + this.withoutHistory;
const missing = this.transcript.omitted.size + this.withoutHistory;
if (missing > 0 && supportsNotices) {
await this.publish({ sessionId: rootSessionId, update: missingHistoryNotice(missing) });
}
@@ -399,12 +449,14 @@ export class RfdSubagentReplay {
live: (sessionId: string) => WorkState["state"] | undefined,
liveConfiguration?: (sessionId: string) => SessionConfigOption[] | undefined,
): AcpSessionNotification[] {
return [...this.children.values()]
return [...this.transcript.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 };
liveState === undefined || liveState === "idle"
? replayEndState(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;
@@ -435,30 +487,6 @@ export class RfdSubagentReplay {
});
}
private registerLaunch(
toolUseId: string,
input: Record<string, unknown>,
parentSessionId: string,
): void {
if (this.children.has(toolUseId)) return;
const record = this.history.agents.get(toolUseId);
if (!record) {
this.omitted.add(toolUseId);
return;
}
const child: ReplayChild = {
launchId: toolUseId,
sessionId: record.agentId,
record,
parentSessionId,
input,
announced: false,
promptSent: false,
};
this.children.set(toolUseId, child);
this.byAgentId.set(record.agentId, child);
}
/** 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;
@@ -489,7 +517,7 @@ export class RfdSubagentReplay {
text: string,
): Promise<void> {
const call = deliveredCall(
this.sendMessages,
this.transcript.sendMessages,
[child.sessionId, child.record.name, child.input.name],
text,
);
@@ -553,7 +581,7 @@ export function replaySubagentTerminalState(
* `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 {
export function replayEndState(child: ReplayChild): WorkState {
const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
return end ? rfdEndState(end, undefined) : { state: "idle" };
}
@@ -586,7 +614,7 @@ function inputOf(block: Record<string, unknown>): Record<string, unknown> {
}
/** The text of user message content: a string, or its text blocks. */
function textOf(content: unknown): string {
export function textOf(content: unknown): string {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
@@ -598,7 +626,7 @@ function textOf(content: unknown): string {
}
/** Whether `dir` is an existing directory. */
function isDirectory(dir: string): boolean {
export function isDirectory(dir: string): boolean {
try {
return statSync(dir).isDirectory();
} catch {

View File

@@ -1,6 +1,6 @@
/**
* 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
* when a background task stops. The replay of a session (`v1/acp-agent.ts`) and
* the session index (`session-index/background-tasks.ts`) read them here, so
* both read the same fields.
*/

View File

@@ -1,4 +1,4 @@
{"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":"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":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"options":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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"}}}

View File

@@ -1,3 +1,3 @@
{"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":"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":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"options":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
{"kind":"newSession","payload":{"configOptions":[{"category":"mode","currentValue":"auto","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":"auto"},"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

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r1","sessionUpdate":"agent_thought"}}}

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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -1,4 +1,4 @@
{"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":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"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","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","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>"}}

View File

@@ -25,6 +25,19 @@
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.systemPrompt`, which origin/main
* did not: the agent now applies the `systemPrompt` of session requests.
* - The `initialize` response also advertises `agentCapabilities.auth.status`,
* which origin/main did not: the agent now answers `auth/status` and sends
* `auth/status_update`.
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.status`, which origin/main did
* not: the agent now serves `session/status`.
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.title`, which origin/main did not:
* the agent now serves `session/set_title`.
* - The `initialize` response also advertises
* `agentCapabilities.sessionCapabilities.options`, which origin/main did
* not: the agent now serves `session/options` and takes `configOptions` on
* session setup requests.
* - A SandboxNetworkAccess tool call is settled by a `tool_call_update` with
* only its final `status` once its permission request is answered. The
* callback runs nothing, so origin/main never settled the call (see
@@ -226,19 +239,30 @@ function isMeta(key: string): boolean {
return key.startsWith("_meta.");
}
/** `record` without the session capability `key`, when it is an `initialize` response. */
function withoutSessionCapability(record: Recorded, key: string): Recorded {
/** `record` without the session capabilities `keys`, when it is an `initialize` response. */
function withoutSessionCapabilities(record: Recorded, keys: readonly string[]): Recorded {
if (record.kind !== "initialize") return record;
const payload = record.payload as Json;
const agent = payload.agentCapabilities as Json | undefined;
const session = agent?.sessionCapabilities as Json | undefined;
if (!session || !(key in session)) return record;
if (!session || !keys.some((key) => key in session)) return record;
const remaining = keys.reduce((rest, key) => without(rest, key), session);
return {
...record,
payload: {
...payload,
agentCapabilities: { ...agent, sessionCapabilities: without(session, key) },
},
payload: { ...payload, agentCapabilities: { ...agent, sessionCapabilities: remaining } },
} as Recorded;
}
/** `record` without `agentCapabilities.auth.status`, when it is an `initialize` response. */
function withoutAuthStatusCapability(record: Recorded): Recorded {
if (record.kind !== "initialize") return record;
const payload = record.payload as Json;
const agent = payload.agentCapabilities as Json | undefined;
const auth = agent?.auth as Json | undefined;
if (!auth || !("status" in auth)) return record;
return {
...record,
payload: { ...payload, agentCapabilities: { ...agent, auth: without(auth, "status") } },
} as Recorded;
}
@@ -248,13 +272,19 @@ function withoutSessionCapability(record: Recorded, key: string): Recorded {
*/
export function compareWithBaseline(baseline: Recorded[], traffic: Recorded[]): string[] {
const expected = baseline
.map((record) => withoutSessionCapability(withoutAirOnlyKeys(record) as Recorded, "subagents"))
.map((record) =>
withoutSessionCapabilities(withoutAirOnlyKeys(record) as Recorded, ["subagents"]),
)
.filter((record) => {
const update = updateOf(record);
return !(update?.sessionUpdate === "session_info_update" && Object.keys(update).length === 1);
});
const current = withoutSandboxNetworkSettlements(
traffic.map((record) => withoutSessionCapability(record, "systemPrompt")),
traffic.map((record) =>
withoutAuthStatusCapability(
withoutSessionCapabilities(record, ["systemPrompt", "status", "title", "options"]),
),
),
);
const violations: string[] = [];
const state = new Map<string, Map<string, string>>();

View File

@@ -373,6 +373,8 @@ export async function runScenario(
async extNotification(method: string, params: unknown) {
record("extNotification", { method, params });
},
// Not recorded: no scenario reads the auth status.
async authStatusUpdate() {},
async readTextFile() {
return { content: "" };
},
@@ -559,7 +561,8 @@ export async function runScenarioV2(
"_auth/status_update",
(params) => params,
() => {},
);
)
.onNotification(v2.methods.client.auth.statusUpdate, () => {});
try {
await client.connectWith(

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { agentCommand } from "../agent-command.js";
import { agentCommand, cliArgs } from "../agent-command.js";
describe("agentCommand", () => {
it.each([
@@ -22,3 +22,44 @@ describe("agentCommand", () => {
expect(agentCommand(argv)).toEqual({ command: process.execPath, args });
});
});
describe("cliArgs", () => {
/**
* The `--cli` passthrough hands the Claude CLI the adapter's flags without `--cli` and
* `--hide-claude-auth`, under Node and in a compiled executable, and still forwards `--version`.
*/
it.each([
{
scenario: "Node, terminal-auth login rerun",
argv: [
"/usr/bin/node",
"/opt/acp/dist/index.js",
"--hide-claude-auth",
"--cli",
"auth",
"login",
"--console",
],
args: ["auth", "login", "--console"],
},
{
scenario: "a compiled executable",
argv: [
"bun",
"/$bunfs/root/claude-agent-acp",
"--hide-claude-auth",
"--cli",
"auth",
"status",
],
args: ["auth", "status"],
},
{
scenario: "--version after --cli",
argv: ["/usr/bin/node", "/opt/acp/dist/index.js", "--cli", "--version"],
args: ["--version"],
},
])("drops the adapter's own flags for $scenario", ({ argv, args }) => {
expect(cliArgs(argv)).toEqual(args);
});
});

View File

@@ -3,7 +3,7 @@ import { randomUUID } from "crypto";
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
import { Pushable } from "../utils.js";
import { discoverCustomAgents, BUILTIN_AGENT_NAMES } from "../acp-agent.js";
import { discoverCustomAgents, BUILTIN_AGENT_NAMES } from "../session-config-options.js";
// `discoverCustomAgents` distinguishes user/plugin-configured agents from
// Claude Code's built-in subagents by name (BUILTIN_AGENT_NAMES), since the

View File

@@ -15,8 +15,8 @@ import {
} from "../diff.js";
import { AcpToolCallRenderer } from "../tool-calls/renderer.js";
import { buildClaudePermissionPresentation } from "../permissions/presentation.js";
import { toolInfoFromToolUse } from "../tools.js";
import { WriteReporter } from "../tool-calls/reporters/file-edit.js";
import { toolInfoFromToolUse } from "./tool-render.js";
const tempDirectories: string[] = [];

View File

@@ -9,6 +9,8 @@ import {
type ClientCapabilities,
type InitializeRequest,
} from "@agentclientprotocol/sdk/unstable";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
/** The context-usage report the base mock query returns. `rawMaxTokens`
* matches the agent's DEFAULT_CONTEXT_WINDOW so window-related assertions
@@ -82,3 +84,73 @@ export function heldRequest<T>(_params?: unknown, signal?: AbortSignal): Promise
else signal?.addEventListener("abort", cancel, { once: true });
});
}
/** A transcript record of a user message. */
export function transcriptUser(uuid: string, content: string, extra: object = {}) {
return { type: "user", uuid, message: { role: "user", content }, ...extra };
}
/** A transcript record of an assistant text response. */
export function transcriptAssistant(uuid: string, text: string) {
return {
type: "assistant",
uuid,
message: {
id: `msg_${uuid}`,
role: "assistant",
model: "claude-opus-5",
content: [{ type: "text", text }],
},
};
}
/** A `compact_boundary` as Claude Code writes it: no parent, and the last
* message before it in `logicalParentUuid`. */
export function transcriptCompactBoundary(
uuid: string,
logicalParentUuid: string,
compactMetadata: object = {},
) {
return {
type: "system",
subtype: "compact_boundary",
uuid,
parentUuid: null,
logicalParentUuid,
compactMetadata: { trigger: "manual", preTokens: 1000, ...compactMetadata },
};
}
/** The summary message that follows a `compact_boundary`. */
export function transcriptCompactSummary(
uuid: string,
content = "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\n" +
"Summary:\n1. Primary Request and Intent:\n Say hi.\n\n" +
"Continue the conversation from where it left off without asking the user any further questions. Resume directly — do not acknowledge the summary.",
) {
return transcriptUser(uuid, content, { isCompactSummary: true });
}
/** Writes `<directory>/<sessionId>.jsonl` as Claude Code does: each record
* links to the record before it, except a record that sets its own
* `parentUuid`. */
export async function writeTranscript(
directory: string,
sessionId: string,
records: Record<string, unknown>[],
): Promise<void> {
await mkdir(directory, { recursive: true });
let parentUuid: unknown = null;
const lines = records.map((record) => {
const line = {
parentUuid,
sessionId,
isSidechain: false,
timestamp: "2026-10-11T00:00:00.000Z",
...record,
};
parentUuid = record.uuid;
return JSON.stringify(line);
});
await writeFile(path.join(directory, `${sessionId}.jsonl`), lines.join("\n") + "\n");
}

View File

@@ -0,0 +1,92 @@
import { describe, it, expect, afterEach, vi } from "vitest";
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
import { SettingsManager } from "../settings.js";
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => ({
...(await importOriginal<typeof import("@anthropic-ai/claude-agent-sdk")>()),
resolveSettings: vi.fn(),
}));
describe("model selection settings policy", () => {
let manager: SettingsManager;
afterEach(() => manager?.dispose());
it.each([{ availableModels: ["claude-haiku-4-5"] }, { availableModels: [] }])(
"keeps the managed allowlist %j and mappings above inline settings",
async ({ availableModels }) => {
const managed = {
availableModels,
modelOverrides: { "claude-haiku-4-5": "managed-haiku" },
deniedModels: ["claude-opus-5"],
};
vi.mocked(resolveSettings).mockResolvedValue({
effective: managed,
provenance: {},
sources: [{ source: "managed", settings: managed }],
});
manager = new SettingsManager(process.cwd());
await manager.initialize();
expect(
manager.getModelSelectionSettings({
availableModels: ["claude-opus-5"],
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
}),
).toEqual({
availableModels: managed.availableModels,
modelOverrides: managed.modelOverrides,
});
expect(manager.getManagedDeniedModels()).toEqual(managed.deniedModels);
},
);
it("keeps managed override keys above inline keys without discarding other inline mappings", async () => {
const managed = { modelOverrides: { "claude-haiku-4-5": "managed-haiku" } };
vi.mocked(resolveSettings).mockResolvedValue({
effective: managed,
provenance: {},
sources: [{ source: "managed", settings: managed }],
});
manager = new SettingsManager(process.cwd());
await manager.initialize();
expect(
manager.getModelSelectionSettings({
availableModels: ["claude-haiku-4-5", "claude-sonnet-4-6"],
modelOverrides: {
"claude-haiku-4-5": "inline-haiku",
"claude-sonnet-4-6": "inline-sonnet",
},
}),
).toEqual({
availableModels: ["claude-haiku-4-5", "claude-sonnet-4-6"],
modelOverrides: { "claude-haiku-4-5": "managed-haiku", "claude-sonnet-4-6": "inline-sonnet" },
});
});
it("preserves file and inline mappings when the managed allowlist supplies no mappings", async () => {
vi.mocked(resolveSettings).mockResolvedValue({
effective: {
availableModels: ["claude-haiku-4-5"],
modelOverrides: { "claude-haiku-4-5": "file-haiku" },
},
provenance: {},
sources: [
{ source: "user", settings: { modelOverrides: { "claude-haiku-4-5": "file-haiku" } } },
{ source: "managed", settings: { availableModels: ["claude-haiku-4-5"] } },
],
});
manager = new SettingsManager(process.cwd());
await manager.initialize();
expect(manager.getModelSelectionSettings()).toEqual({
availableModels: ["claude-haiku-4-5"],
modelOverrides: { "claude-haiku-4-5": "file-haiku" },
});
expect(
manager.getModelSelectionSettings({
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
}),
).toEqual({
availableModels: ["claude-haiku-4-5"],
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
});
});
});

View File

@@ -1,74 +1,22 @@
import { describe, expect, it } from "vitest";
import packageJson from "../../package.json" with { type: "json" };
describe("module runtime exports", () => {
/**
* The public runtime exports of lib.ts must change only deliberately; consumers import these names.
* The public runtime exports of lib.ts, the package's only entry point, must change only deliberately; consumers import these names.
*/
it("lib.ts exports the expected names", async () => {
expect(Object.keys(await import("../lib.js")).sort()).toEqual([
"AUTH_STATUS_UPDATE_METHOD",
"ClaudeAcpAgent",
"Pushable",
"SettingsManager",
"isLocalCommandMetadata",
"nodeToWebReadable",
"nodeToWebWritable",
"planEntries",
"runAcp",
"streamEventToAcpNotifications",
"stripLocalCommandMetadata",
"toAcpNotifications",
"toDisplayPath",
"toolInfoFromToolUse",
"toolUpdateFromToolResult",
"unreachable",
"serveAcp",
]);
});
/**
* Moving code out of acp-agent.ts must keep every name re-exported, since consumers deep-import
* dist/acp-agent.js; change this list only deliberately.
*/
it("acp-agent.ts exports the expected names", async () => {
expect(Object.keys(await import("../acp-agent.js")).sort()).toEqual([
"AGENT_CONFIG_ID",
"AUTONOMOUS_RESULT_ORIGINS",
"BUILTIN_AGENT_NAMES",
"ClaudeAcpAgent",
"DEFAULT_AGENT_ID",
"EFFORT_CONFIG_ID",
"FAST_MODE_CONFIG_ID",
"FAST_MODE_OFF",
"FAST_MODE_ON",
"MODEL_CONFIG_ID",
"MODE_CONFIG_ID",
"applyAvailableModelsAllowlist",
"buildConfigOptions",
"clientSupportsBooleanConfigOptions",
"clientSupportsRecommendedConfigValue",
"computeSessionFingerprint",
"createFastModeConfigOption",
"discoverCustomAgents",
"fastModeStateEnabled",
"getAvailableSlashCommands",
"isAbortedResult",
"isLocalCommandMetadata",
"isLoginRequiredText",
"isReplayHiddenMetaMessage",
"isSyntheticLoginMessage",
"matchResumedModel",
"messageIdForGrouping",
"normalizeFastModeDisabledReason",
"promptToClaude",
"resolveFastModeEnabled",
"resolveModelPreference",
"runAcp",
"runPromptWithCancellation",
"settingsEffortForModel",
"streamEventToAcpNotifications",
"stripLocalCommandMetadata",
"toAcpNotifications",
"v1AgentApp",
]);
it("package.json exposes lib.ts as the only module entry point", () => {
expect(Object.keys(packageJson.exports)).toEqual([".", "./package.json"]);
expect(packageJson.exports["."]).toEqual({
types: "./dist/lib.d.ts",
import: "./dist/lib.js",
});
});
});

View File

@@ -1,7 +1,10 @@
import { describe, expect, it } from "vitest";
import type { ClientCapabilities } from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
import { buildConfigOptions, clientSupportsRecommendedConfigValue } from "../acp-agent.js";
import {
buildConfigOptions,
clientSupportsRecommendedConfigValue,
} from "../session-config-options.js";
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY } from "../air-extension.js";
const MODES = {

View File

@@ -1,9 +1,20 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getSessionMessages, type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import {
transcriptAssistant,
transcriptCompactBoundary,
transcriptCompactSummary,
transcriptUser,
writeTranscript,
} from "./helpers.js";
import os from "node:os";
import path from "node:path";
import { readResumedTail, resumedModelFromTranscript } from "../resumed-session.js";
import {
readHistoryAcrossCompactions,
readResumedTail,
resumedModelFromTranscript,
} from "../resumed-session.js";
function assistant(
model: unknown,
@@ -203,3 +214,187 @@ describe("readResumedTail", () => {
expect(await readResumedTail("no-mode")).toEqual({ model: "claude-opus-5" });
});
});
describe("readHistoryAcrossCompactions", () => {
const sessionId = "33333333-3333-4333-8333-333333333333";
let configDir: string;
let originalConfigDir: string | undefined;
beforeEach(async () => {
configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-compacted-"));
originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.CLAUDE_CONFIG_DIR = configDir;
});
afterEach(async () => {
if (originalConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR;
else process.env.CLAUDE_CONFIG_DIR = originalConfigDir;
await rm(configDir, { recursive: true, force: true });
});
const transcript = (records: Record<string, unknown>[], project = "-workspace") =>
writeTranscript(path.join(configDir, "projects", project), sessionId, records);
const user = transcriptUser;
const assistant = transcriptAssistant;
const boundary = transcriptCompactBoundary;
const summary = (uuid: string) => transcriptCompactSummary(uuid, `Summary ${uuid}`);
const texts = (messages: SessionMessage[]) =>
messages.map((message) => {
const content = (message.message as { content: unknown }).content;
return typeof content === "string" ? content : (content as { text: string }[])[0]!.text;
});
it("reads the messages before each compaction", async () => {
await transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "next"),
assistant("a2", "Next"),
boundary("b2", "a2"),
summary("s2"),
user("u3", "identify yourself"),
assistant("a3", "Claude"),
]);
// getSessionMessages stops at the last boundary.
const chain = await getSessionMessages(sessionId);
expect(texts(chain)).toEqual(["Summary s2", "identify yourself", "Claude"]);
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
"hi",
"Hello",
"Summary s1",
"next",
"Next",
"Summary s2",
"identify yourself",
"Claude",
]);
});
it("keeps the messages that a compaction preserved where they were said", async () => {
// Claude Code 2.1.293 keeps u2 and a2 in the context after the summary,
// and records them in both forms of preserved-message metadata.
await transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
user("u2", "keep this"),
assistant("a2", "Kept"),
boundary("b1", "a2", {
preservedSegment: { headUuid: "u2", anchorUuid: "s1", tailUuid: "a2" },
preservedMessages: { anchorUuid: "s1", uuids: ["u2", "a2"] },
}),
summary("s1"),
user("u3", "after"),
assistant("a3", "After"),
]);
// getSessionMessages moves the preserved messages after the summary.
const chain = await getSessionMessages(sessionId);
expect(texts(chain)).toEqual(["Summary s1", "keep this", "Kept", "after", "After"]);
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
"hi",
"Hello",
"keep this",
"Kept",
"Summary s1",
"after",
"After",
]);
});
it("returns the chain of a session without a compaction as it is", async () => {
await transcript([user("u1", "hi"), assistant("a1", "Hello")]);
const chain = await getSessionMessages(sessionId);
expect(await readHistoryAcrossCompactions(sessionId, chain)).toBe(chain);
});
it("ends at the last message of the chain it was given", async () => {
await transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "identify yourself"),
]);
const chain = await getSessionMessages(sessionId);
// Claude Code appends a response after the first read.
await transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "identify yourself"),
assistant("a2", "Claude"),
]);
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
"hi",
"Hello",
"Summary s1",
"identify yourself",
]);
});
it.each([
["the transcript is gone", async () => rm(configDir, { recursive: true, force: true })],
[
"the transcript no longer has the last message",
() =>
transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u9", "other"),
]),
],
])("returns the chain and logs when %s", async (_case, change) => {
await transcript([
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "identify yourself"),
]);
const chain = await getSessionMessages(sessionId);
await change();
const error = vi.fn();
expect(await readHistoryAcrossCompactions(sessionId, chain, { log: () => {}, error })).toBe(
chain,
);
expect(error).toHaveBeenCalledOnce();
});
it("reads the transcript in the project directory it is given", async () => {
await transcript(
[
user("u1", "elsewhere"),
assistant("a1", "Elsewhere"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "identify yourself"),
],
"-a-other",
);
await transcript(
[
user("u1", "hi"),
assistant("a1", "Hello"),
boundary("b1", "a1"),
summary("s1"),
user("u2", "identify yourself"),
],
"-z-workspace",
);
const chain = await getSessionMessages(sessionId, { dir: "/z/workspace" });
expect(
texts(await readHistoryAcrossCompactions(sessionId, chain, undefined, "/z/workspace")),
).toEqual(["hi", "Hello", "Summary s1", "identify yourself"]);
});
});

View File

@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";
import { apiRetryWarning } from "../session-failure-extension.js";
describe("apiRetryWarning", () => {
// Each input is one the bundled CLI sends: the CLI derives `error` from the
// status (`IIt`), and an error with no HTTP response has a null status.
it.each([
{
name: "a no_response retry, which the CLI sends with a 0 ms delay",
message: {
error_status: null,
error: "unknown" as const,
retry_delay_ms: 0,
no_response: { waited_ms: 180_000, retry_wait_ms: 600_000 },
},
details: "Connection error: no HTTP response. Next attempt now.",
},
{
name: "a background rate-limit wait in minutes, rounded up",
message: { error_status: 429, error: "rate_limit" as const, retry_delay_ms: 1_740_500 },
details: "HTTP 429 (rate limited). Next attempt in 30m.",
},
{
name: "a wait just under 120 s in seconds",
message: { error_status: 529, error: "overloaded" as const, retry_delay_ms: 119_000 },
details: "HTTP 529 (overloaded). Next attempt in 119s.",
},
{
name: "a wait of 120 s in minutes",
message: { error_status: 529, error: "overloaded" as const, retry_delay_ms: 120_000 },
details: "HTTP 529 (overloaded). Next attempt in 2m.",
},
{
name: "a credential error, which has no HTTP status",
message: {
error_status: null,
error: "cloud_credential_error" as const,
retry_delay_ms: 1_100,
},
details: "No HTTP status (cloud credential error). Next attempt in 1s.",
},
{
name: "a message with no error kind as unknown",
message: { error_status: 503, error: undefined, retry_delay_ms: 600 },
details: "HTTP 503. Next attempt in 1s.",
},
])("describes $name", ({ message, details }) => {
expect(apiRetryWarning({ attempt: 1, max_retries: 10, ...message }).details).toBe(details);
});
});

View File

@@ -4,9 +4,9 @@ import * as os from "node:os";
import * as path from "node:path";
import { deriveActivity, selectCost, type ActivityState } from "../session-index/activity.js";
import { LiveSessionRegistry, type LiveRecord } from "../session-index/live-registry.js";
import { isSessionId } from "../paths.js";
import {
encodeProjectPath,
isSessionId,
pathAndAncestors,
projectDirMatches,
} from "../session-index/project-dirs.js";

View File

@@ -38,3 +38,62 @@ describe("SettingsManager.getApiKeyHelper", () => {
manager.dispose();
});
});
describe("SettingsManager.getCompactionSettings", () => {
it("ranks managed settings over the --settings override over the files, env entry by entry", async () => {
const manager = await managerResolving({
effective: {
idleCompaction: true,
autoCompactEnabled: false,
env: {
DISABLE_COMPACT: "1",
DISABLE_AUTO_COMPACT: "1",
CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS: "1",
},
},
provenance: {},
sources: [
{ source: "userSettings", settings: { autoCompactEnabled: false } },
{
source: "managed",
settings: { idleCompaction: true, env: { DISABLE_AUTO_COMPACT: "1" } },
},
],
});
expect(
manager.getCompactionSettings({
idleCompaction: false,
autoCompactEnabled: true,
env: { DISABLE_COMPACT: "0", DISABLE_AUTO_COMPACT: "0" },
}),
).toEqual({
idleCompaction: true,
autoCompactEnabled: true,
env: {
DISABLE_COMPACT: "0",
DISABLE_AUTO_COMPACT: "1",
CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS: "1",
},
});
manager.dispose();
});
it("keeps the files' value for a key the override sets to undefined", async () => {
const manager = await managerResolving({
effective: { idleCompaction: false, env: { DISABLE_COMPACT: "1" } },
provenance: {},
sources: [],
});
expect(
manager.getCompactionSettings({
idleCompaction: undefined,
env: { DISABLE_COMPACT: undefined as unknown as string },
}),
).toEqual({
idleCompaction: false,
autoCompactEnabled: undefined,
env: { DISABLE_COMPACT: "1" },
});
manager.dispose();
});
});

63
src/tests/tool-render.ts Normal file
View File

@@ -0,0 +1,63 @@
/**
* Test shorthands for the shared tool call renderer: one tool use or tool
* result, rendered for a client with the given capabilities. Production code
* renders through {@link AcpToolCallRenderer} directly.
*/
import type {
ToolCallContent,
ToolCallLocation,
ToolKind,
} from "@agentclientprotocol/sdk/unstable";
import { stripLocalCommandMetadata } from "../message-filters.js";
import { ClientCapabilities } from "../tool-calls/client-capabilities.js";
import type { ToolResultBlock } from "../tool-calls/content.js";
import { AcpToolCallRenderer, type RenderedResult } from "../tool-calls/renderer.js";
/**
* The title, kind, content, and locations of a tool use, for a client with the
* given terminal and patch capabilities. The {@link AcpToolCallRenderer} builds
* them from the facts of the tool reporter.
*/
export function toolInfoFromToolUse(
toolUse: any,
supportsTerminalOutput: boolean = false,
cwd?: string,
supportsDiffPatch: boolean = false,
): {
title: string;
kind: ToolKind;
content: ToolCallContent[];
locations?: ToolCallLocation[];
} {
const renderer = new AcpToolCallRenderer(
new ClientCapabilities(supportsTerminalOutput, false, supportsDiffPatch),
);
return renderer.toolInfo({ id: toolUse?.id, name: toolUse?.name, input: toolUse?.input }, cwd);
}
/**
* The result fields of a tool result, for a client with the given terminal
* capabilities. The {@link AcpToolCallRenderer} builds them from the facts of
* the tool reporter.
*/
export function toolUpdateFromToolResult(
toolResult: ToolResultBlock,
toolUse: any | undefined,
supportsTerminalOutput: boolean = false,
toolUseResult?: unknown,
preferTerminalOutputDelta: boolean = false,
): RenderedResult {
const renderer = new AcpToolCallRenderer(
new ClientCapabilities(supportsTerminalOutput, preferTerminalOutputDelta),
);
return renderer.resultFields(
{ id: toolUse?.id, name: toolUse?.name ?? "", input: toolUse?.input },
toolResult as Parameters<AcpToolCallRenderer["resultFields"]>[1],
toolUseResult,
);
}
/** Whether `content` is only Claude Code's local-command metadata, which the agent drops. */
export function isLocalCommandMetadata(content: unknown): boolean {
return stripLocalCommandMetadata(content) === null;
}

View File

@@ -26,12 +26,10 @@ import { AcpClient } from "../acp-client.js";
import { toAcpNotifications, ToolUseCache } from "../sdk-to-acp.js";
import { Logger } from "../logger.js";
import {
toolUpdateFromToolResult,
createPostToolUseHook,
createTaskHook,
clearHookCallbacks,
registerHookCallback,
toolInfoFromToolUse,
planEntries,
applyTaskCreate,
applyTaskList,
@@ -43,6 +41,7 @@ import {
} from "../tools.js";
import { ClientCapabilities as ToolCallCapabilities } from "../tool-calls/client-capabilities.js";
import { AcpToolCallRenderer } from "../tool-calls/renderer.js";
import { toolInfoFromToolUse, toolUpdateFromToolResult } from "./tool-render.js";
/** An AIR client: it gets the ACP tool call contract (`docs/air-extensions.md`). */
const AIR_CLIENT: ClientCapabilities = {

View File

@@ -2,9 +2,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
import type { AcpClient } from "../acp-client.js";
import { makeMockQuery } from "./helpers.js";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../../v1/acp-agent.js";
import type { AcpClient } from "../../acp-client.js";
import { makeMockQuery } from "../helpers.js";
const { querySpy } = vi.hoisted(() => ({
querySpy: vi.fn(),
@@ -85,7 +85,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -129,7 +129,7 @@ describe("ClaudeAcpAgent settings", () => {
applyFlagSettings,
}),
);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
if (recommended) {
(agent as any).clientCapabilities = {
@@ -180,7 +180,7 @@ describe("ClaudeAcpAgent settings", () => {
applyFlagSettings,
}),
);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
(agent as any).clientCapabilities = {
_meta: { jetbrains: { air: { version: 1, capabilities: ["recommendedValue"] } } },
@@ -227,7 +227,7 @@ describe("ClaudeAcpAgent settings", () => {
applyFlagSettings,
}),
);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
(agent as any).clientCapabilities = {
_meta: { jetbrains: { air: { version: 1, capabilities: ["recommendedValue"] } } },
@@ -271,7 +271,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -296,7 +296,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -319,7 +319,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
(agent as any).logger = { log: () => {}, error: () => {} };
@@ -363,7 +363,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
(agent as any).logger = { log: () => {}, error: () => {} };
@@ -388,7 +388,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -414,7 +414,7 @@ describe("ClaudeAcpAgent settings", () => {
mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
(agent as any).logger = { log: () => {}, error: () => {} };
@@ -448,7 +448,7 @@ describe("ClaudeAcpAgent settings", () => {
);
const { getCapturedOptions, setModelSpy } = mockQuery(models);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
(agent as any).logger = { log: () => {}, error: () => {} };
return { agent, projectDir, getCapturedOptions, setModelSpy };
@@ -647,7 +647,7 @@ describe("ClaudeAcpAgent settings", () => {
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -674,7 +674,7 @@ describe("ClaudeAcpAgent settings", () => {
const { setModelSpy } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -729,7 +729,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -756,7 +756,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -808,7 +808,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "claude-haiku-4-5", displayName: "Claude Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -845,7 +845,7 @@ describe("ClaudeAcpAgent settings", () => {
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
try {
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -892,7 +892,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -921,6 +921,136 @@ describe("ClaudeAcpAgent settings", () => {
return { setModelSpy };
}
it.each(["environment", "caller object", "caller file"])(
"includes configured models omitted by the SDK from %s settings",
async (source) => {
const configured = {
availableModels: ["claude-haiku-4-5", "claude-haiku-5-5"],
modelOverrides: {
"claude-haiku-4-5": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
"claude-haiku-5-5": "global.anthropic.claude-haiku-5-5",
},
};
let settings: object | string | undefined;
if (source === "environment") {
vi.stubEnv("CLAUDE_MODEL_CONFIG", JSON.stringify(configured));
} else if (source === "caller file") {
settings = "caller-settings.json";
await fs.promises.writeFile(path.join(tempDir, settings), JSON.stringify(configured));
} else {
settings = configured;
}
const { setModelSpy } = mockQueryWithModels([
{ value: "default", displayName: "Default", description: "" },
{ value: "sonnet", displayName: "Sonnet", description: "" },
]);
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
try {
const response = await (agent as any).createSession({
cwd: tempDir,
mcpServers: [],
_meta: { claudeCode: { options: { settings } } },
});
expect(querySpy.mock.calls[0][0].options.settings).toEqual(configured);
const ids = ["default", ...Object.values(configured.modelOverrides)];
expect(
agent.sessions[response.sessionId].models.availableModels.map(
(model: any) => model.modelId,
),
).toEqual(ids);
const modelOption = response.configOptions.find((option: any) => option.id === "model");
expect(modelOption.options.map((option: any) => option.value)).toEqual(ids);
await agent.setSessionConfigOption({
sessionId: response.sessionId,
configId: "model",
value: ids[2],
});
expect(setModelSpy).toHaveBeenLastCalledWith(ids[2]);
expect(agent.sessions[response.sessionId].modelInfos.map((model) => model.value)).toEqual(
ids,
);
} finally {
await agent.dispose();
vi.unstubAllEnvs();
}
},
);
it("merges file settings with caller settings after caller keys replace the env config", async () => {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-sonnet-4-6", "claude-haiku-4-5"],
modelOverrides: {
"claude-sonnet-4-6": "file-sonnet",
"claude-haiku-4-5": "file-haiku",
},
}),
);
vi.stubEnv(
"CLAUDE_MODEL_CONFIG",
JSON.stringify({
availableModels: ["claude-opus-4-6"],
modelOverrides: { "claude-sonnet-4-6": "env-sonnet", "claude-haiku-4-5": "env-haiku" },
}),
);
mockQueryWithModels([{ value: "default", displayName: "Default", description: "" }]);
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
const settings = {
availableModels: ["claude-haiku-4-5", "claude-haiku-5-5"],
modelOverrides: { "claude-haiku-4-5": "caller-haiku" },
};
try {
const response = await (agent as any).createSession({
cwd: tempDir,
mcpServers: [],
_meta: { claudeCode: { options: { settings } } },
});
expect(querySpy.mock.calls[0][0].options.settings).toEqual(settings);
const modelOption = response.configOptions.find((option: any) => option.id === "model");
expect(modelOption.options.map((option: any) => option.value)).toEqual([
"default",
"file-sonnet",
"caller-haiku",
"claude-haiku-5-5",
]);
} finally {
await agent.dispose();
vi.unstubAllEnvs();
}
});
it.each([[], undefined])(
"preserves empty and absent configured allowlists: %j",
async (availableModels) => {
vi.stubEnv(
"CLAUDE_MODEL_CONFIG",
JSON.stringify({
availableModels,
modelOverrides: { "claude-haiku-4-5": "provider-haiku" },
}),
);
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "" },
{ value: "sonnet", displayName: "Sonnet", description: "" },
]);
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent = new ClaudeAcpAgent(createMockClient());
try {
const response = await (agent as any).createSession({ cwd: tempDir, mcpServers: [] });
const modelOption = response.configOptions.find((option: any) => option.id === "model");
expect(modelOption.options.map((option: any) => option.value)).toEqual(
availableModels ? ["default"] : ["default", "sonnet"],
);
} finally {
await agent.dispose();
vi.unstubAllEnvs();
}
},
);
it("restricts configOptions to the user's allowlist using their exact IDs", async () => {
// Reproduces the scenario from
// https://github.com/agentclientprotocol/claude-agent-acp/issues/620:
@@ -957,7 +1087,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -997,7 +1127,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1029,7 +1159,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1051,7 +1181,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1085,7 +1215,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1136,7 +1266,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1197,7 +1327,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1249,7 +1379,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1304,7 +1434,7 @@ describe("ClaudeAcpAgent settings", () => {
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1355,7 +1485,7 @@ describe("ClaudeAcpAgent settings", () => {
{ value: "sonnet", displayName: "Sonnet", description: "Claude Sonnet 4.6" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1410,7 +1540,7 @@ describe("ClaudeAcpAgent settings", () => {
}) as any;
});
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1451,7 +1581,7 @@ describe("ClaudeAcpAgent settings", () => {
});
try {
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
@@ -1502,7 +1632,7 @@ describe("ClaudeAcpAgent settings", () => {
}) as any;
});
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({

View File

@@ -22,48 +22,53 @@ import {
WriteTextFileRequest,
WriteTextFileResponse,
} from "@agentclientprotocol/sdk/unstable";
import type { AcpSessionNotification } from "../acp-subagents.js";
import { NativeSubagentRuntime } from "../native-subagents.js";
import { nodeToWebWritable, nodeToWebReadable } from "../utils.js";
import {
createPostToolUseHook,
markdownEscape,
toolInfoFromToolUse,
toDisplayPath,
toolUpdateFromToolResult,
} from "../tools.js";
import { toolUpdateFromDiffToolResponse } from "../diff.js";
import { ToolCallFieldTracker } from "../tool-calls/field-tracker.js";
import type { AcpSessionNotification } from "../../acp-subagents.js";
import { NativeSubagentRuntime } from "../../native-subagents.js";
import { nodeToWebWritable, nodeToWebReadable } from "../../utils.js";
import { createPostToolUseHook, markdownEscape, toDisplayPath } from "../../tools.js";
import { toolUpdateFromDiffToolResponse } from "../../diff.js";
import { ToolCallFieldTracker } from "../../tool-calls/field-tracker.js";
import {
ClaudeAcpAgent,
computeSessionFingerprint,
buildConfigOptions,
createFastModeConfigOption,
discoverCustomAgents,
runPromptWithCancellation,
type SteerRequest,
} from "../acp-agent.js";
} from "../../v1/acp-agent.js";
import { buildConfigOptions, discoverCustomAgents } from "../../session-config-options.js";
import { createFastModeConfigOption } from "../../session-fast-mode.js";
import {
toAcpNotifications,
streamEventToAcpNotifications,
messageIdForGrouping,
type StreamedToolInputCache,
} from "../sdk-to-acp.js";
import { promptToClaude } from "../prompt-to-claude.js";
} from "../../sdk-to-acp.js";
import { promptToClaude } from "../../prompt-to-claude.js";
import {
isLocalCommandMetadata,
isReplayHiddenMetaMessage,
isSyntheticLoginMessage,
stripLocalCommandMetadata,
} from "../message-filters.js";
import type { AcpClient } from "../acp-client.js";
import type { SDKMessageFilter } from "../new-session-meta.js";
import { SessionTitles } from "../session-titles.js";
import { SessionBackgroundWork } from "../background-work.js";
import { formatUsageResponse, isUsageCommandText, parseUsageResponse } from "../usage-markdown.js";
import { Pushable } from "../utils.js";
import { heldRequest, initializeClient, makeMockQuery } from "./helpers.js";
import { acpProtocolRouter } from "../serve.js";
} from "../../message-filters.js";
import type { AcpClient } from "../../acp-client.js";
import type { SDKMessageFilter } from "../../new-session-meta.js";
import { SessionTitles } from "../../session-titles.js";
import { SessionBackgroundWork } from "../../v1/background-work.js";
import {
formatUsageResponse,
isUsageCommandText,
parseUsageResponse,
} from "../../usage-markdown.js";
import { Pushable } from "../../utils.js";
import {
heldRequest,
initializeClient,
makeMockQuery,
transcriptAssistant,
transcriptCompactBoundary,
transcriptCompactSummary,
transcriptUser,
writeTranscript,
} from "../helpers.js";
import { acpProtocolRouter } from "../../serve.js";
import * as agentSdk from "@anthropic-ai/claude-agent-sdk";
import {
deleteSession,
@@ -77,11 +82,12 @@ import {
PermissionUpdate,
query,
SDKAssistantMessage,
type SDKAssistantMessageError,
type Options,
type SDKControlGetUsageResponse,
} from "@anthropic-ai/claude-agent-sdk";
import { createHash, randomUUID } from "crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import {
@@ -95,7 +101,7 @@ import {
goalUpdateFromPrompt,
parseGoalRequest,
toGoalSnapshot,
} from "../goal-extension.js";
} from "../../v1/goal-extension.js";
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
const actual = await importOriginal<typeof import("@anthropic-ai/claude-agent-sdk")>();
@@ -120,7 +126,12 @@ import type {
BetaWebFetchToolResultBlockParam,
BetaCodeExecutionToolResultBlockParam,
} from "@anthropic-ai/sdk/resources/beta.mjs";
import { validateRecorded } from "./acp-scenarios/schema.js";
import { validateRecorded } from "../acp-scenarios/schema.js";
import {
isLocalCommandMetadata,
toolInfoFromToolUse,
toolUpdateFromToolResult,
} from "../tool-render.js";
/** The capabilities of an AIR client. Only AIR gets the AIR extensions of
* `docs/air-extensions.md`. */
@@ -3605,7 +3616,7 @@ describe("usage-limit failure replay", () => {
it("recognizes a sanitized raw transcript through the real SDK parser", async () => {
const raw = await readFile(
new URL("./fixtures/usage-limit-session.jsonl", import.meta.url),
new URL("../fixtures/usage-limit-session.jsonl", import.meta.url),
"utf8",
);
const entries = raw
@@ -3628,6 +3639,63 @@ describe("usage-limit failure replay", () => {
});
});
describe("compacted session replay", () => {
it("replays the messages before the compaction, then the compaction", async () => {
const configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-compacted-replay-"));
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.CLAUDE_CONFIG_DIR = configDir;
try {
const sessionId = "44444444-4444-4444-8444-444444444444";
// The records of `/compact` between two prompts, as Claude Code writes
// them: the boundary has no parent and links "Hello" logically.
await writeTranscript(path.join(configDir, "projects", "-tmp-proj"), sessionId, [
transcriptUser("u1", "hi"),
transcriptAssistant("a1", "Hello"),
transcriptCompactBoundary("b1", "a1"),
transcriptCompactSummary("s1"),
transcriptUser("u2", "identify yourself"),
transcriptAssistant("a2", "Claude"),
]);
const updates: SessionNotification[] = [];
const client = {
sessionUpdate: async (update: SessionNotification) => {
updates.push(update);
},
} as unknown as AcpClient;
const agent = new ClaudeAcpAgent(client, { log: () => {}, error: () => {} });
await initializeClient(agent, { session: { compaction: {} } } as ClientCapabilities);
await (
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
).replaySessionHistory(sessionId);
expect(
updates.flatMap(({ update }) => {
if (update.sessionUpdate === "compaction_update") return [["compaction", update.status]];
if (
(update.sessionUpdate === "user_message_chunk" ||
update.sessionUpdate === "agent_message_chunk") &&
update.content.type === "text"
) {
return [[update.sessionUpdate, update.content.text]];
}
return [];
}),
).toEqual([
["user_message_chunk", "hi"],
["agent_message_chunk", "Hello"],
["compaction", "completed"],
["user_message_chunk", "identify yourself"],
["agent_message_chunk", "Claude"],
]);
} finally {
if (originalConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR;
else process.env.CLAUDE_CONFIG_DIR = originalConfigDir;
await rm(configDir, { recursive: true, force: true });
}
});
});
describe("session/list", () => {
const sdkSession = (index: number) => ({
sessionId: `session-${index}`,
@@ -11833,7 +11901,7 @@ describe("stop reason propagation", () => {
subtype: "api_retry",
attempt: 1,
max_retries: 5,
retry_delay_ms: 100,
retry_delay_ms: 600,
error_status: 429,
error: "rate_limit",
uuid: randomUUID(),
@@ -11863,6 +11931,7 @@ describe("stop reason propagation", () => {
severity: "warning",
revision: 1,
title: "Retrying Claude, attempt 1 of 5.",
details: "HTTP 429 (rate limited). Next attempt in 1s.",
actions: [],
}),
);
@@ -11876,6 +11945,144 @@ describe("stop reason propagation", () => {
actions: ["retry"],
}),
);
expect(terminal).not.toHaveProperty("details");
});
it("keeps the api retries of one turn and its failure in one row before the echo", async () => {
const updates: SessionNotification[] = [];
const agent = new ClaudeAcpAgent(
{
sessionUpdate: async (update: SessionNotification) => updates.push(update),
} as unknown as AcpClient,
{ log: () => {}, error: () => {} },
);
await initializeClient(agent, airSessionFailureCapabilities);
const retry = (attempt: number) => ({
type: "system" as const,
subtype: "api_retry" as const,
attempt,
max_retries: 10,
retry_delay_ms: 1_000,
error_status: 503,
error: "server_error" as const,
uuid: randomUUID(),
session_id: "test-session",
});
const input = new Pushable<any>();
async function* cli() {
const iter = input[Symbol.asyncIterator]();
for (const turn of [1, 2]) {
const { value: userMessage } = await iter.next();
const echo = {
type: "user",
message: userMessage.message,
parent_tool_use_id: null,
uuid: userMessage.uuid,
session_id: "test-session",
isReplay: true,
};
// The CLI holds the echo back until the first assistant frame or
// stream event of the turn. A request that gets a 503 has neither, so
// its retries come before the echo.
if (turn === 1) {
yield retry(1);
yield retry(2);
yield echo;
yield createAssistantError("server_error", "API Error: 503");
yield createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: true,
result: "API Error: 503",
});
} else {
yield retry(1);
yield echo;
yield createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: false,
result: "ok",
});
}
}
}
agent.sessions["test-session"] = mockSessionState({ query: wrapQuery(cli()), input });
const first = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
});
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "second" }] });
const warnings = updates
.map((update) => (update.update._meta as any)?.jetbrains?.air?.sessionFailure)
.filter((failure) => failure?.severity === "warning");
expect(warnings.map((warning) => warning.revision)).toEqual([1, 2, 1]);
expect(warnings[1].id).toBe(warnings[0].id);
expect(warnings[0].id).toMatch(/:error$/);
expect(sessionFailureFromResponse(first)).toEqual(
expect.objectContaining({ id: warnings[0].id, revision: 3, severity: "error" }),
);
expect(warnings[2].id).not.toBe(warnings[0].id);
});
it("replaces the cause of an api retry warning on the next retry", async () => {
const updates: SessionNotification[] = [];
const agent = new ClaudeAcpAgent(
{
sessionUpdate: async (update: SessionNotification) => updates.push(update),
} as unknown as AcpClient,
{ log: () => {}, error: () => {} },
);
await initializeClient(agent, airSessionFailureCapabilities);
const retry = (
attempt: number,
error_status: number | null,
error: SDKAssistantMessageError,
retry_delay_ms: number,
) => ({
type: "system" as const,
subtype: "api_retry" as const,
attempt,
max_retries: 10,
retry_delay_ms,
error_status,
error,
uuid: randomUUID(),
session_id: "test-session",
});
injectSession(agent, [
retry(1, 529, "overloaded", 8_000),
// An SSE `overloaded_error` event after HTTP 200: the CLI reports
// `overloaded` with no status.
retry(2, null, "overloaded", 1_100),
createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: false,
result: "ok",
}),
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] });
const warnings = updates
.map((update) => (update.update._meta as any)?.jetbrains?.air?.sessionFailure)
.filter(Boolean);
expect(warnings).toEqual([
expect.objectContaining({
revision: 1,
title: "Retrying Claude, attempt 1 of 10.",
details: "HTTP 529 (overloaded). Next attempt in 8s.",
}),
expect.objectContaining({
id: warnings[0].id,
revision: 2,
title: "Reconnecting to Claude, attempt 2 of 10.",
details: "No HTTP status (overloaded). Next attempt in 1s.",
}),
]);
});
it("says how long a no-response retry waited and will wait", async () => {
@@ -11939,7 +12146,7 @@ describe("stop reason propagation", () => {
subtype: "api_retry",
attempt: 2,
max_retries: 5,
retry_delay_ms: 100,
retry_delay_ms: 1_100,
error_status: null,
error: "unknown",
uuid: randomUUID(),
@@ -12521,7 +12728,7 @@ describe("stop reason propagation", () => {
subtype: "api_retry",
attempt: 1,
max_retries: 10,
retry_delay_ms: 100,
retry_delay_ms: 600,
error_status: 401,
error: "authentication_failed",
uuid: randomUUID(),
@@ -14952,6 +15159,79 @@ describe("session/fork", () => {
});
});
describe("session/status", () => {
it("answers open for the sessions it holds, and the others from their stored transcripts", async () => {
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
try {
const agent = new ClaudeAcpAgent({ sessionUpdate: async () => {} } as unknown as AcpClient, {
log: () => {},
error: () => {},
});
const { agentCapabilities } = await agent.initialize({
protocolVersion: 1,
clientCapabilities: {},
});
expect(agentCapabilities?.sessionCapabilities?.status).toEqual({});
const status = (sessionId: string) => agent.sessionStatus({ sessionId });
const unknown = (sessionId: string) => ({ code: -32002, data: { sessionId } });
const sessionId = randomUUID();
agent.sessions[sessionId] = {} as ClaudeAcpAgent["sessions"][string];
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
// A held session whose query ended stays open: a load returns it as it is.
agent.sessions[sessionId]!.queryClosed = true;
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
// A subagent's session id, and a name that every object inherits.
await expect(status("agent-1")).rejects.toMatchObject(unknown("agent-1"));
await expect(status("constructor")).rejects.toMatchObject(unknown("constructor"));
// Closed, and never prompted: no projects directory exists yet.
delete agent.sessions[sessionId];
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
// A transcript of any project counts, next to a project without one.
await mkdir(path.join(configDir, "projects", "-other"), { recursive: true });
const project = path.join(configDir, "projects", "-project");
// A directory of that name is no transcript.
await mkdir(path.join(project, `${sessionId}.jsonl`), { recursive: true });
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
await rm(path.join(project, `${sessionId}.jsonl`), { recursive: true });
await writeFile(path.join(project, `${sessionId}.jsonl`), "{}\n");
await expect(status(sessionId)).resolves.toEqual({ status: "saved" });
// The request loaded nothing.
expect(agent.sessions[sessionId]).toBeUndefined();
} finally {
vi.unstubAllEnvs();
await rm(configDir, { recursive: true, force: true });
}
});
// Not on Windows, where `chmod` does not take the read permission away.
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
"fails, not -32002, when a project directory cannot be read",
async () => {
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
const project = path.join(configDir, "projects", "-project");
await mkdir(project, { recursive: true });
await chmod(project, 0o000);
try {
const agent = new ClaudeAcpAgent(
{ sessionUpdate: async () => {} } as unknown as AcpClient,
{ log: () => {}, error: () => {} },
);
await expect(agent.sessionStatus({ sessionId: randomUUID() })).rejects.toMatchObject({
code: "EACCES",
});
} finally {
await chmod(project, 0o700);
vi.unstubAllEnvs();
await rm(configDir, { recursive: true, force: true });
}
},
);
});
describe("session/close", () => {
function createMockAgent() {
const mockClient = {

View File

@@ -5,11 +5,11 @@
* - Recordings are JSON Lines: one outbound message on each line, with sorted
* keys. `normalize` in `harness.ts` replaces only exact run-specific values:
* the generated ids, the paths, and the argv and version of this process.
* - AIR golden files: `acp-scenarios/__snapshots__/air/<scenario>.jsonl`.
* Run `npx vitest run src/tests/acp-scenarios.test.ts -u` to update them.
* - AIR golden files: `../acp-scenarios/__snapshots__/air/<scenario>.jsonl`.
* Run `npx vitest run src/tests/v1/acp-scenarios.test.ts -u` to update them.
* - Schema: every message is valid against the ACP schema of the SDK.
* - Plain and Zed: the same information as origin/main
* (`acp-scenarios/origin-main/<profile>/<scenario>.jsonl`). `compare.ts`
* (`../acp-scenarios/origin-main/<profile>/<scenario>.jsonl`). `compare.ts`
* allows only the documented differences. These profiles have no golden
* files of their own. `origin-main/zed/` holds a scenario only when its
* recording differs from the plain client.
@@ -17,9 +17,9 @@
* - AIR: the AIR extensions of `docs/air-extensions.md`, each fact once.
*
* To record the origin/main baseline again, copy `src/tests/acp-scenarios/`
* and this file into a checkout of origin/main, and run this file there with
* and this file to the same paths in a checkout of origin/main, and run this file there with
* `ACP_SCENARIO_BASELINE_DIR=<dir>`. Then copy `<dir>/plain` and `<dir>/zed`
* to `acp-scenarios/origin-main/`.
* to `src/tests/acp-scenarios/origin-main/`.
*/
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import * as fs from "node:fs";
@@ -41,20 +41,20 @@ import {
streamMessage,
toolCall,
toolResult,
} from "./acp-scenarios/harness.js";
import { SCENARIOS } from "./acp-scenarios/scenarios.js";
} from "../acp-scenarios/harness.js";
import { SCENARIOS } from "../acp-scenarios/scenarios.js";
import {
AIR_ONLY_CLAUDE_CODE_KEYS,
AIR_ONLY_META_KEYS,
canonical,
compareWithBaseline,
} from "./acp-scenarios/compare.js";
import { EXTENSION_SESSION_UPDATES, validateRecorded } from "./acp-scenarios/schema.js";
import { LEGACY_AIR_CUSTOM_ANSWER_KEY } from "../air-extension.js";
} from "../acp-scenarios/compare.js";
import { EXTENSION_SESSION_UPDATES, validateRecorded } from "../acp-scenarios/schema.js";
import { LEGACY_AIR_CUSTOM_ANSWER_KEY } from "../../air-extension.js";
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
const actual = await importOriginal<typeof import("@anthropic-ai/claude-agent-sdk")>();
const harness = await import("./acp-scenarios/harness.js");
const harness = await import("../acp-scenarios/harness.js");
return {
...actual,
query: harness.mockedQuery,
@@ -66,7 +66,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
// learns each id that `randomUUID` returns.
vi.mock("node:crypto", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:crypto")>();
const harness = await import("./acp-scenarios/harness.js");
const harness = await import("../acp-scenarios/harness.js");
return {
...actual,
randomUUID: (...args: Parameters<typeof actual.randomUUID>) => {
@@ -97,7 +97,7 @@ beforeAll(async () => {
vi.stubEnv("CLAUDE_CODE_EXECUTABLE", "/usr/bin/false");
configDir = fs.mkdtempSync(path.join(os.tmpdir(), "acp-scenario-config-"));
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
Agent = ClaudeAcpAgent;
for (const profile of profiles) {
for (const scenario of SCENARIOS) {
@@ -189,7 +189,7 @@ function metaObjects(value: unknown, at = "$"): { at: string; meta: Record<strin
/** The origin/main recording of a scenario. Zed has a file only where it differs from the plain client. */
function readBaseline(profile: "plain" | "zed", scenario: string): string {
const file = (name: string) =>
path.join(here, "acp-scenarios", "origin-main", name, `${scenario}.jsonl`);
path.join(here, "..", "acp-scenarios", "origin-main", name, `${scenario}.jsonl`);
return fs.readFileSync(
profile === "zed" && !fs.existsSync(file("zed")) ? file("plain") : file(profile),
"utf8",
@@ -219,7 +219,7 @@ describe.skipIf(baselineDir)("ACP scenarios", () => {
const second = "cccccccc-0000-4000-8000-000000000003";
const scripted = "00000000-0000-4000-8000-00000000c001";
const version = JSON.parse(
fs.readFileSync(path.join(here, "..", "..", "package.json"), "utf8"),
fs.readFileSync(path.join(here, "..", "..", "..", "package.json"), "utf8"),
).version;
const argv = process.argv.slice(1);
@@ -322,7 +322,7 @@ describe.skipIf(baselineDir)("ACP scenarios", () => {
describe("air golden files", () => {
it.each(SCENARIOS.map((scenario) => scenario.name))("%s", async (scenario) => {
await expect(toJsonLines(run("air", scenario).normalized)).toMatchFileSnapshot(
path.join(here, "acp-scenarios", "__snapshots__", "air", `${scenario}.jsonl`),
path.join(here, "..", "acp-scenarios", "__snapshots__", "air", `${scenario}.jsonl`),
);
});
});

View File

@@ -3,8 +3,8 @@ import * as os from "node:os";
import * as path from "node:path";
import { SessionNotification } from "@agentclientprotocol/sdk/unstable";
import type { Options } from "@anthropic-ai/claude-agent-sdk";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
import type { AcpClient } from "../acp-client.js";
import type { ClaudeAcpAgent as ClaudeAcpAgentType } from "../../v1/acp-agent.js";
import type { AcpClient } from "../../acp-client.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let capturedOptions: Options | undefined;
@@ -12,7 +12,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
const actual = await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
"@anthropic-ai/claude-agent-sdk",
);
const { makeMockQuery } = await import("./helpers.js");
const { makeMockQuery } = await import("../helpers.js");
return {
...actual,
query: ({ options }: { options: Options }) => {
@@ -32,8 +32,8 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
},
};
});
vi.mock("../tools.js", async () => ({
...(await vi.importActual<typeof import("../tools.js")>("../tools.js")),
vi.mock("../../tools.js", async () => ({
...(await vi.importActual<typeof import("../../tools.js")>("../../tools.js")),
registerHookCallback: vi.fn(),
}));
@@ -47,7 +47,7 @@ describe("additionalRoots", () => {
capturedOptions = undefined;
tempDirs.length = 0;
vi.resetModules();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
agent = new ClaudeAcpAgent({
sessionUpdate: async (_notification: SessionNotification) => {},
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),

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