mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 20:42:10 +00:00
Compare commits
59 Commits
feat/subag
...
fix/add-di
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8efe828c76 | ||
|
|
0ad3d2063b | ||
|
|
0fe22ae5a1 | ||
|
|
59e292e29f | ||
|
|
50cd76e78d | ||
|
|
aa62f44886 | ||
|
|
9b3a5c891c | ||
|
|
ecaa2c935b | ||
|
|
5b11f470b1 | ||
|
|
e9f986b8a9 | ||
|
|
4d378f93f3 | ||
|
|
a1d0eeeaad | ||
|
|
7bcbfc7a5c | ||
|
|
2dab922644 | ||
|
|
ff01e06579 | ||
|
|
e155ecb419 | ||
|
|
bcf74c2fe8 | ||
|
|
1420994ced | ||
|
|
424db87535 | ||
|
|
dbf386d8d3 | ||
|
|
db9f50f021 | ||
|
|
f5967fd6b4 | ||
|
|
af3d7f9335 | ||
|
|
68bf74f58e | ||
|
|
26f144a2a5 | ||
|
|
872e93f11f | ||
|
|
78a29f58ba | ||
|
|
910d83ceea | ||
|
|
c0af592d25 | ||
|
|
761b4c8359 | ||
|
|
1a1f1a0e80 | ||
|
|
0c8de1380f | ||
|
|
fadbe1881c | ||
|
|
ba3c7be05e | ||
|
|
aeb69e7ed1 | ||
|
|
e8eb9d340f | ||
|
|
d528647162 | ||
|
|
594aa8c955 | ||
|
|
bcc024d2c9 | ||
|
|
b4788d61ea | ||
|
|
c1e83b46b7 | ||
|
|
c7425e4e04 | ||
|
|
1fd217f3e7 | ||
|
|
9a1b90fb9d | ||
|
|
764f339c3e | ||
|
|
ebf8b49fd6 | ||
|
|
df21b3fb56 | ||
|
|
7f7a1404a1 | ||
|
|
d6f60f512a | ||
|
|
32c23db06d | ||
|
|
c6510c27c1 | ||
|
|
21eef57655 | ||
|
|
53df1c7b22 | ||
|
|
c7393d27ff | ||
|
|
4615a9141c | ||
|
|
252126f616 | ||
|
|
bd06804b67 | ||
|
|
5758ac84a4 | ||
|
|
d6c371704f |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -24,9 +24,10 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
check-latest: true
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@@ -74,9 +74,10 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
check-latest: true
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@@ -129,9 +130,10 @@ jobs:
|
||||
# few megabytes packed, so a full fetch is cheap and sidesteps every
|
||||
# shallow-clone tag caveat.
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
check-latest: true
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
# Ahead of the version bump, so this still validates the committed lockfile.
|
||||
- run: npm ci
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.89.1"
|
||||
".": "0.94.0"
|
||||
}
|
||||
|
||||
94
CHANGELOG.md
94
CHANGELOG.md
@@ -1,5 +1,99 @@
|
||||
# Changelog
|
||||
|
||||
## [0.94.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.93.0...v0.94.0) (2026-10-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* send one end event after the session/load history replay for AIR ([#1361](https://github.com/agentclientprotocol/claude-agent-acp/issues/1361)) ([1420994](https://github.com/agentclientprotocol/claude-agent-acp/commit/1420994ced20919aad127a8bd0294de022449e35))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* match OpenRouter model ids in the gateway model listing ([#1365](https://github.com/agentclientprotocol/claude-agent-acp/issues/1365)) ([bcf74c2](https://github.com/agentclientprotocol/claude-agent-acp/commit/bcf74c2fe8e32bd24714d45122529dcf2f99a279))
|
||||
* resolve a skill's SKILL.md by its declared name, not by its directory name ([#1372](https://github.com/agentclientprotocol/claude-agent-acp/issues/1372)) ([424db87](https://github.com/agentclientprotocol/claude-agent-acp/commit/424db8753581bfaf4c5bf278f8df8c089eec35da))
|
||||
* update to ACP SDK 2.0.0 ([#1386](https://github.com/agentclientprotocol/claude-agent-acp/issues/1386)) ([ff01e06](https://github.com/agentclientprotocol/claude-agent-acp/commit/ff01e06579225e00643914692994cd60da4d6225))
|
||||
* update to Claude Code 2.1.303 ([#1384](https://github.com/agentclientprotocol/claude-agent-acp/issues/1384)) ([2dab922](https://github.com/agentclientprotocol/claude-agent-acp/commit/2dab92264414d133fd6774f7a90241ea5192ba32))
|
||||
|
||||
## [0.93.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.92.0...v0.93.0) (2026-10-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add /remote-control command to start Remote Control for a session ([#1197](https://github.com/agentclientprotocol/claude-agent-acp/issues/1197)) ([db9f50f](https://github.com/agentclientprotocol/claude-agent-acp/commit/db9f50f02145d088c0f6080672969a0673ba6359))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hold a fork back from the session list until its session/fork response is out ([#1368](https://github.com/agentclientprotocol/claude-agent-acp/issues/1368)) ([f5967fd](https://github.com/agentclientprotocol/claude-agent-acp/commit/f5967fd6b49ce4821cc829c26902ccf46f671992))
|
||||
|
||||
## [0.92.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.91.0...v0.92.0) (2026-10-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the MCP server and tool to AIR tool calls ([#1359](https://github.com/agentclientprotocol/claude-agent-acp/issues/1359)) ([872e93f](https://github.com/agentclientprotocol/claude-agent-acp/commit/872e93f11fde8abc528822d29cebcc542b05e9e8))
|
||||
* publish the predicted follow-up prompt as _meta.promptSuggestions ([#1356](https://github.com/agentclientprotocol/claude-agent-acp/issues/1356)) ([78a29f5](https://github.com/agentclientprotocol/claude-agent-acp/commit/78a29f58ba1eb1b54896d8d73f49402ee50aa087))
|
||||
* update ACP SDK to 1.9.0 and adopt the v2 spec clarifications ([#1366](https://github.com/agentclientprotocol/claude-agent-acp/issues/1366)) ([68bf74f](https://github.com/agentclientprotocol/claude-agent-acp/commit/68bf74f58ee378f21502ac93c44878b712905af2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include subagent file edits in the file-change report ([#1357](https://github.com/agentclientprotocol/claude-agent-acp/issues/1357)) ([910d83c](https://github.com/agentclientprotocol/claude-agent-acp/commit/910d83ceea78a03b39002cf2dcc275442838e2db))
|
||||
* restore resumed subagents after an adapter restart ([#1364](https://github.com/agentclientprotocol/claude-agent-acp/issues/1364)) ([26f144a](https://github.com/agentclientprotocol/claude-agent-acp/commit/26f144a2a5c60452805d11c87433963318ef4e65))
|
||||
|
||||
## [0.91.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.90.0...v0.91.0) (2026-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* keep background subagents running after a parent cancel, for clients that can cancel them ([#1348](https://github.com/agentclientprotocol/claude-agent-acp/issues/1348)) ([ba3c7be](https://github.com/agentclientprotocol/claude-agent-acp/commit/ba3c7be05e9af297fe95b0ac647da2340dd9e15b))
|
||||
* let a client cancel one subagent with session/cancel on its child session ([#1347](https://github.com/agentclientprotocol/claude-agent-acp/issues/1347)) ([aeb69e7](https://github.com/agentclientprotocol/claude-agent-acp/commit/aeb69e7ed1170e8bce236378432ef8b83cdf2554))
|
||||
* mark session list rows of worktrees Claude Code made (agentWorktree) ([#1352](https://github.com/agentclientprotocol/claude-agent-acp/issues/1352)) ([1a1f1a0](https://github.com/agentclientprotocol/claude-agent-acp/commit/1a1f1a0e802cc2ee097d9c78c1f7bafccef7c3df))
|
||||
* narrow models to what the providers/set gateway lists ([#1340](https://github.com/agentclientprotocol/claude-agent-acp/issues/1340)) ([e8eb9d3](https://github.com/agentclientprotocol/claude-agent-acp/commit/e8eb9d340fd4f6dfd9826cf58461edf5ee165f44))
|
||||
* restore a subagent from before a reload when SendMessage resumes it, for subagents RFD clients ([#1332](https://github.com/agentclientprotocol/claude-agent-acp/issues/1332)) ([df21b3f](https://github.com/agentclientprotocol/claude-agent-acp/commit/df21b3fb56c3449b6f86c9c9df558be351815ed3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close the tool calls of a background subagent that the SDK reports stopped after a cancel ([#1336](https://github.com/agentclientprotocol/claude-agent-acp/issues/1336)) ([9a1b90f](https://github.com/agentclientprotocol/claude-agent-acp/commit/9a1b90fb9db8b04ef6756cbcdc8df22afc82a2ba))
|
||||
* close the tool calls of a cycle that Claude Code started and a cancel stopped ([#1339](https://github.com/agentclientprotocol/claude-agent-acp/issues/1339)) ([1fd217f](https://github.com/agentclientprotocol/claude-agent-acp/commit/1fd217f3e77d0b7883ebd220bc19c7899d3af77f))
|
||||
* close the tool calls that a cycle Claude Code started abandoned, at its end ([#1344](https://github.com/agentclientprotocol/claude-agent-acp/issues/1344)) ([594aa8c](https://github.com/agentclientprotocol/claude-agent-acp/commit/594aa8c9556609c16946c889e9c8f1f76337b203))
|
||||
* deliver the output of a background subagent that a cancel did not stop ([#1345](https://github.com/agentclientprotocol/claude-agent-acp/issues/1345)) ([bcc024d](https://github.com/agentclientprotocol/claude-agent-acp/commit/bcc024d2c9a34d62e19999bc6cfc393ad56fbcde))
|
||||
* end the terminal of a command whose tool call is closed without a result ([#1335](https://github.com/agentclientprotocol/claude-agent-acp/issues/1335)) ([764f339](https://github.com/agentclientprotocol/claude-agent-acp/commit/764f339c3e83eac8ca34f4ddc8ed65f8199b651d))
|
||||
* end the tool calls of a cycle that Claude Code started and a cancel stopped, on v1 ([#1342](https://github.com/agentclientprotocol/claude-agent-acp/issues/1342)) ([b4788d6](https://github.com/agentclientprotocol/claude-agent-acp/commit/b4788d61eaf9372b376d56c0f6fb7c6156822ae6))
|
||||
* keep a live session that was never prompted when a resume or load changes its settings ([#1353](https://github.com/agentclientprotocol/claude-agent-acp/issues/1353)) ([0c8de13](https://github.com/agentclientprotocol/claude-agent-acp/commit/0c8de1380fd990291fc51cf17c6742402efb5c94))
|
||||
* let session/delete succeed for a session without a transcript ([#1351](https://github.com/agentclientprotocol/claude-agent-acp/issues/1351)) ([fadbe18](https://github.com/agentclientprotocol/claude-agent-acp/commit/fadbe1881cd441609a1fde8c43286d9a404dc425))
|
||||
* report no running for the session's own request that a cancel withdraws, on ACP v2 ([#1346](https://github.com/agentclientprotocol/claude-agent-acp/issues/1346)) ([d528647](https://github.com/agentclientprotocol/claude-agent-acp/commit/d528647162ba6747654125cf6ebc2297ced02ed0))
|
||||
* show nothing of a tool that cancelled work asks to run after the cancel was reported ([#1334](https://github.com/agentclientprotocol/claude-agent-acp/issues/1334)) ([ebf8b49](https://github.com/agentclientprotocol/claude-agent-acp/commit/ebf8b49fd68924f2c50680e7e73241f625801980))
|
||||
* stop waiting for a question the client leaves open after Claude Code cancels it ([#1338](https://github.com/agentclientprotocol/claude-agent-acp/issues/1338)) ([c1e83b4](https://github.com/agentclientprotocol/claude-agent-acp/commit/c1e83b46b717230a5d623c3ac591d915a776e0ae))
|
||||
* update Claude Agent SDK to 0.3.300 ([#1355](https://github.com/agentclientprotocol/claude-agent-acp/issues/1355)) ([761b4c8](https://github.com/agentclientprotocol/claude-agent-acp/commit/761b4c835985ff265d35338df35eee6e6861d8f3))
|
||||
|
||||
## [0.90.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.89.1...v0.90.0) (2026-10-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* accept IDE session hooks without dropping model settings ([#1265](https://github.com/agentclientprotocol/claude-agent-acp/issues/1265)) ([fe8b69e](https://github.com/agentclientprotocol/claude-agent-acp/commit/fe8b69edd438ee1bc4206752b029dd45e71975fe))
|
||||
* **deps:** Bump actions/setup-node from 7.0.0 to 7.1.0 ([#1329](https://github.com/agentclientprotocol/claude-agent-acp/issues/1329)) ([c6510c2](https://github.com/agentclientprotocol/claude-agent-acp/commit/c6510c27c11bb59ad10090c8c18ba60c0fa1c3fa))
|
||||
* keep a subagent's session across SendMessage resumes for subagents RFD clients ([#1321](https://github.com/agentclientprotocol/claude-agent-acp/issues/1321)) ([bd06804](https://github.com/agentclientprotocol/claude-agent-acp/commit/bd06804b67d7b4efa8180db2f36203a5d34c3ac4))
|
||||
* replay each subagent under its live session id for subagents RFD clients ([#1328](https://github.com/agentclientprotocol/claude-agent-acp/issues/1328)) ([21eef57](https://github.com/agentclientprotocol/claude-agent-acp/commit/21eef576557c845bfe9acc66499dab347f813a98))
|
||||
* report a subagent as requires_action while its request is open for subagents RFD clients ([#1322](https://github.com/agentclientprotocol/claude-agent-acp/issues/1322)) ([252126f](https://github.com/agentclientprotocol/claude-agent-acp/commit/252126f61654cf7ac2cad74be53b4c6add569e1f))
|
||||
* report subagents as the subagents RFD defines, for clients that are not AIR ([#1320](https://github.com/agentclientprotocol/claude-agent-acp/issues/1320)) ([5758ac8](https://github.com/agentclientprotocol/claude-agent-acp/commit/5758ac84a4a3b95ace7542a7e71fd58d93a581b3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close the open tool calls of a held or settling turn that a cancel ends ([#1221](https://github.com/agentclientprotocol/claude-agent-acp/issues/1221)) ([32c23db](https://github.com/agentclientprotocol/claude-agent-acp/commit/32c23db06d53d7d4e1295d78fc0129f8278d75df))
|
||||
* display active Central helpers as JetBrains Air Gateway ([#1266](https://github.com/agentclientprotocol/claude-agent-acp/issues/1266)) ([d6c3717](https://github.com/agentclientprotocol/claude-agent-acp/commit/d6c371704fb5fd74c3b329a3d756486975e3f180))
|
||||
* drop streamed output of prompts a cancel abandoned ([#1313](https://github.com/agentclientprotocol/claude-agent-acp/issues/1313)) ([1137df3](https://github.com/agentclientprotocol/claude-agent-acp/commit/1137df33aa4fe387916596ff489c02ee48ccbba3))
|
||||
* end a cancelled turn at its interrupted result and deliver Claude Code's follow-up after a cancel ([#1309](https://github.com/agentclientprotocol/claude-agent-acp/issues/1309)) ([b007ac7](https://github.com/agentclientprotocol/claude-agent-acp/commit/b007ac7c45b85755877fee802c2c901a0e3057c6))
|
||||
* end the tool calls of a cancelled turn before it reports the cancel ([#1327](https://github.com/agentclientprotocol/claude-agent-acp/issues/1327)) ([53df1c7](https://github.com/agentclientprotocol/claude-agent-acp/commit/53df1c7b22bf08f9a2866389be812be80f34c8a3))
|
||||
* keep the terminal of a command in the background running until its task ends ([#1312](https://github.com/agentclientprotocol/claude-agent-acp/issues/1312)) ([d074558](https://github.com/agentclientprotocol/claude-agent-acp/commit/d074558e4a2ef4ee6f4b39aecb5ebf36d8c0f84b))
|
||||
* report a command whose result says interrupted as cancelled on the experimental ACP v2 surface ([#1301](https://github.com/agentclientprotocol/claude-agent-acp/issues/1301)) ([b3bed2f](https://github.com/agentclientprotocol/claude-agent-acp/commit/b3bed2f7630f4ddb97978246255ba89ca6e4613e))
|
||||
* route a subagent's PostToolUse hook update to its child session ([#1324](https://github.com/agentclientprotocol/claude-agent-acp/issues/1324)) ([4615a91](https://github.com/agentclientprotocol/claude-agent-acp/commit/4615a9141c12e8cd9093a5bbc0e62a7d778cf0cf))
|
||||
* start a forked session so the client can prompt it at once ([#1317](https://github.com/agentclientprotocol/claude-agent-acp/issues/1317)) ([383bc7d](https://github.com/agentclientprotocol/claude-agent-acp/commit/383bc7dcf0d4d624d2d74405eb3cee6453eb9b11))
|
||||
|
||||
## [0.89.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.89.0...v0.89.1) (2026-10-10)
|
||||
|
||||
|
||||
|
||||
71
README.md
71
README.md
@@ -42,22 +42,57 @@ Task subagent as its own child session, following the [subagents RFD](https://gi
|
||||
|
||||
- `subagent_update` on the parent announces the child before any of its traffic, with the
|
||||
parent's `title` and `description` for it, and reports its work state: `running`,
|
||||
`requires_action` while one of its requests is open, and `idle` when its work ends, with
|
||||
`requires_action` while one of its requests is open (and `running` again when the last one ends,
|
||||
unless a cancel withdrew it: the child is stopping then), and `idle` when its work ends, with
|
||||
`end_turn` or `cancelled` when the SDK gives the reason. A failure is `idle` with the `error`
|
||||
stop reason and the SDK's error text as its `error`. The child keeps its session for every
|
||||
delegation to it.
|
||||
- The prompt of the Agent or Task call that launches the child, and the message of each
|
||||
SendMessage call that resumes it, is a `session_message` in the child's transcript. The tool
|
||||
call stays a tool call of the session that made it.
|
||||
- Session messages replace the Agent or Task call that launches the child, which its parent is
|
||||
not shown:
|
||||
- The launch's prompt is a `session_message` in both transcripts: the parent's outgoing view and
|
||||
the child's incoming one. Both use the launch's tool use id, and each names the other with the
|
||||
draft RFD's `recipientMessageId` and `senderMessageId`.
|
||||
- A foreground child's answer, from the launch's result, is the child's `session_message` to
|
||||
the parent. In Claude Code's hand-back mode, the child's `SubagentHandback` call is its
|
||||
message to the parent instead, in both transcripts and linked the same way.
|
||||
- A launch whose child is never exposed is shown as a tool call after all, with its final
|
||||
status.
|
||||
- The message of a SendMessage call that resumes the child is a `session_message` in the child's
|
||||
transcript. The SendMessage call stays a tool call of the session that made it.
|
||||
- `session/cancel` on the parent interrupts Claude Code, which stops the foreground children that
|
||||
the turn waits on and, currently, background children too. Each stopped child reports `idle`
|
||||
with `cancelled` from Claude Code's own events, and the open requests of the foreground ones are
|
||||
withdrawn. The adapter ends no child at a cancel itself, so a child that keeps running keeps its
|
||||
open requests. A child that still runs when Claude Code exits is `idle` with no stop reason.
|
||||
the turn waits on. Background children keep running, as the RFD prefers; a client stops one with
|
||||
its own `cancel`. Each stopped child reports `idle` with `cancelled` from Claude Code's own
|
||||
events, and its open requests are withdrawn. The adapter ends no child at a cancel itself, so a
|
||||
child that keeps running keeps its open requests and its open tool calls. To spare background
|
||||
children, the adapter declares the SDK option `perTaskStopAffordance`, which a client can turn
|
||||
off in `_meta.claudeCode.options`. That option spares background workflows too, which a client
|
||||
cannot stop, so the adapter stops them after the interrupt, as Claude Code did without it.
|
||||
Background commands keep running after a cancel either way. A tool call of a stopped child that Claude Code reports no
|
||||
result for, such as the command it was running, ends in the child's session before the cancelled
|
||||
prompt resolves. When Claude Code stops a background child after that, or while no prompt runs,
|
||||
the tool call ends right after the child reports `idle`. A child that still runs when Claude Code
|
||||
exits is `idle` with no stop reason.
|
||||
- A child whose parent is not yet known when it ends is not exposed, since the RFD forbids
|
||||
guessing its parent.
|
||||
- A client cannot prompt, cancel, or otherwise change a child yet, and a replay does not restore
|
||||
children yet.
|
||||
- A replay restores each child under its live session id, from its own transcript, with the same
|
||||
session messages as live, and its end is an `idle` before the response.
|
||||
A launch without Claude Code's metadata file for the child stays an ordinary tool call of the
|
||||
parent, and a notice, for a client that takes notices, says that subagent history is missing.
|
||||
- After a load or resume, with or without replay, a SendMessage that resumes an agent from before
|
||||
it runs that agent in its session from before. Before the agent's traffic, each of its ancestors
|
||||
that the client was not told about in this connection is announced, parent before child and
|
||||
with no work state, as the RFD requires. An agent whose launch the session's history does not
|
||||
record stays unexposed.
|
||||
- Each running child is announced with the `cancel` capability. `session/cancel` with the child's
|
||||
session id stops that child's current work, through the SDK's `Query.stopTask`, and not its
|
||||
parent's: Claude Code also stops the foreground children it waits on, and the parent's turn goes
|
||||
on. Each stopped child reports `idle` with `cancelled` from Claude Code's own events, and its
|
||||
requests that the stop withdraws report no `running` first. A replayed child, which no longer
|
||||
runs, gets no capabilities. A client cannot prompt or otherwise change a child.
|
||||
|
||||
An [ACP v2](#experimental-acp-v2) client gets the same without declaring anything, since subagents
|
||||
are part of v2's baseline. Each child also reports its work state as its own `state_update`, right
|
||||
after the parent's copy.
|
||||
|
||||
JetBrains AIR keeps the earlier draft of the RFD (`subagent_spawned`, `subagent_state_update`),
|
||||
which it enables with `nativeSubagentSessions` in `_meta.jetbrains.air.capabilities`. See
|
||||
@@ -67,6 +102,22 @@ Without either signal, Agent and Task stay ordinary tool calls and child interac
|
||||
root session. Clients that use the historical `_meta["subagent-transcript"]` capability or the
|
||||
`forwardSubagentText` session option retain the flattened child transcript.
|
||||
|
||||
### Experimental ACP v2
|
||||
|
||||
The adapter also serves the [draft ACP v2](https://agentclientprotocol.com/protocol/v2/overview):
|
||||
a client that asks for protocol version 2 or later in `initialize` gets v2, and every other client
|
||||
gets v1. The draft can still change, and this surface changes with it.
|
||||
|
||||
`CLAUDE_AGENT_ACP_PROTOCOL` limits the adapter to one version. With `v1`, a client that asks
|
||||
for v2 is negotiated down to v1. With `v2`, the adapter rejects the `initialize` of a v1 client.
|
||||
`auto`, the default, serves both.
|
||||
|
||||
On v2 the adapter serves the session baseline (`session/new`, `session/list`, `session/resume`
|
||||
with replay, `session/close`, `session/prompt`, `session/cancel`), `session/delete`,
|
||||
`session/fork`, 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 and the AIR extensions are v1 only for now.
|
||||
|
||||
## Contribution Policy
|
||||
|
||||
This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms:
|
||||
|
||||
@@ -20,14 +20,17 @@ It describes only this adapter.
|
||||
- [Session hooks](#session-hooks)
|
||||
- [Permission presentation](#permission-presentation)
|
||||
- [Goal](#goal)
|
||||
- [Predicted follow-up prompt](#predicted-follow-up-prompt)
|
||||
- [Recommended config values](#recommended-config-values)
|
||||
- [Async tasks](#async-tasks)
|
||||
- [Agent file-change report](#agent-file-change-report)
|
||||
- [Session failure](#session-failure)
|
||||
- [Native subagent sessions](#native-subagent-sessions)
|
||||
- [Session index](#session-index)
|
||||
- [Background subagents](#background-subagents)
|
||||
- [Context compaction](#context-compaction)
|
||||
- [Question custom answers](#question-custom-answers)
|
||||
- [Load replay end](#load-replay-end)
|
||||
- [Session fork point](#session-fork-point)
|
||||
- [Presentation hints](#presentation-hints)
|
||||
- [Removed keys](#removed-keys)
|
||||
@@ -123,7 +126,8 @@ The `initialize` response of an AIR client carries the agent side of the extensi
|
||||
"asyncTasks",
|
||||
"recommendedValue",
|
||||
"diffPatch",
|
||||
"planFile"
|
||||
"planFile",
|
||||
"backgroundSubagents"
|
||||
],
|
||||
"goal": {
|
||||
"version": 1,
|
||||
@@ -137,26 +141,29 @@ The `initialize` response of an AIR client carries the agent side of the extensi
|
||||
}
|
||||
```
|
||||
|
||||
The agent list does not depend on the capabilities that AIR declares, except `sessionIndex`: the agent lists it, together with `sessionArchive`, `sessionRename` and `sessionListSubscribe`, only for a client that declared `sessionIndex`.
|
||||
An extension is active only when the client declared its capability. The client does not declare `sessionArchive`, `sessionRename` and `sessionListSubscribe`.
|
||||
The agent list does not depend on the capabilities that AIR declares, except `sessionIndex`: the agent lists it, together with `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`, only for a client that declared `sessionIndex`.
|
||||
An extension is active only when the client declared its capability. The client does not declare `sessionArchive`, `sessionRename`, `sessionListSubscribe` and `sessionWorktrees`.
|
||||
The response to a client that is not AIR has no `_meta.jetbrains` key.
|
||||
|
||||
### Capabilities
|
||||
|
||||
| Capability | Advertised | What the adapter does when the client declares it | Section |
|
||||
| ------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
|
||||
| `diffPatch` | yes | Sends an exact Git patch in the diff block of an Edit or a Write. | [Diff patch](#diff-patch) |
|
||||
| `sessionFailure` | yes | Sends warnings and errors as typed transcript records. | [Session failure](#session-failure) |
|
||||
| `recommendedValue` | yes | Replaces the `default` model and effort rows with concrete values and a recommendation. | [Recommended config values](#recommended-config-values) |
|
||||
| `asyncTasks` | yes | Publishes background work that is not a subagent as async tasks. | [Async tasks](#async-tasks) |
|
||||
| `agentFileChangeReport` | yes | Accepts a report request on `session/prompt` and sends the changed file list. | [Agent file-change report](#agent-file-change-report) |
|
||||
| `nativeSubagentSessions` | yes | Reports an Agent or Task subagent as a native ACP child session. | [Native subagent sessions](#native-subagent-sessions) |
|
||||
| `planFile` | yes | Sends the path of the plan file in place of the plan text of an ExitPlanMode. | [Plan file](#plan-file) |
|
||||
| `sessionIndex` | on request | Pages, orders and annotates `session/list`; adds rename, archive and a list subscription. | [Session index](#session-index) |
|
||||
| `sessionArchive` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/archive` and `_session/unarchive`. | [Archive](#archive) |
|
||||
| `sessionRename` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/rename`. | [Rename](#rename) |
|
||||
| `sessionListSubscribe` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/list/subscribe`. | [List subscription](#list-subscription) |
|
||||
| `rawInputRendering` | no | Sends no display copy of readable input in `content`. The client renders `rawInput`. | [Tool call contract](#tool-call-contract) |
|
||||
| Capability | Advertised | What the adapter does when the client declares it | Section |
|
||||
| ------------------------ | ------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| `diffPatch` | yes | Sends an exact Git patch in the diff block of an Edit or a Write. | [Diff patch](#diff-patch) |
|
||||
| `sessionFailure` | yes | Sends warnings and errors as typed transcript records. | [Session failure](#session-failure) |
|
||||
| `recommendedValue` | yes | Replaces the `default` model and effort rows with concrete values and a recommendation. | [Recommended config values](#recommended-config-values) |
|
||||
| `asyncTasks` | yes | Publishes background work that is not a subagent as async tasks. | [Async tasks](#async-tasks) |
|
||||
| `agentFileChangeReport` | yes | Accepts a report request on `session/prompt` and sends the changed file list. | [Agent file-change report](#agent-file-change-report) |
|
||||
| `nativeSubagentSessions` | yes | Reports an Agent or Task subagent as a native ACP child session. | [Native subagent sessions](#native-subagent-sessions) |
|
||||
| `planFile` | yes | Sends the path of the plan file in place of the plan text of an ExitPlanMode. | [Plan file](#plan-file) |
|
||||
| `backgroundSubagents` | yes | Ends a prompt turn at its result while background subagents still run. | [Background subagents](#background-subagents) |
|
||||
| `sessionIndex` | on request | Pages, orders and annotates `session/list`; adds rename, archive and a list subscription. | [Session index](#session-index) |
|
||||
| `sessionArchive` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/archive` and `_session/unarchive`. | [Archive](#archive) |
|
||||
| `sessionRename` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/rename`. | [Rename](#rename) |
|
||||
| `sessionListSubscribe` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that it supports `_session/list/subscribe`. | [List subscription](#list-subscription) |
|
||||
| `sessionWorktrees` | with `sessionIndex` | Nothing on its own: the agent lists it to tell that rows carry `agentWorktree` for a worktree Claude Code made. | [List response](#list-response) |
|
||||
| `loadReplayEnd` | no | Sends one `session_info_update` after the whole history replay of a loaded session. | [Load replay end](#load-replay-end) |
|
||||
| `rawInputRendering` | no | Sends no display copy of readable input in `content`. The client renders `rawInput`. | [Tool call contract](#tool-call-contract) |
|
||||
|
||||
The adapter ignores `planContentDelta`.
|
||||
Claude does not stream a plan, so the adapter never sends `contentDelta` (see [Plan file](#plan-file)).
|
||||
@@ -185,11 +192,13 @@ The adapter sends these keys only to an AIR client. "AIR" in the Gate column mea
|
||||
| `commandTitle` | Bash and PowerShell tool call `_meta.jetbrains.air` | the `description` input string | AIR |
|
||||
| `subagent` | Agent and Task tool call `_meta.jetbrains.air` | `true` | AIR |
|
||||
| `skill` | Skill tool call `_meta.jetbrains.air` | `{name, path?}` | AIR |
|
||||
| `mcp` | MCP tool call `_meta.jetbrains.air` | `{server, tool}` | AIR |
|
||||
| `contextCompaction` | compaction tool call or `compaction_update`, `_meta.jetbrains.air` | `{version: 1, trigger?, preTokens?, postTokens?, durationMs?, error?}` | AIR |
|
||||
| `customAnswer` | elicitation schema property `_meta.jetbrains.air` | `{questionId, isCustomAnswer: true}` | AIR, when the client supports form elicitation |
|
||||
| `kind` | session mode `_meta.jetbrains.air` and mode config option value `_meta.jetbrains.air` | `standard`, `plan`, `auto_review`, or `full_access` | AIR |
|
||||
| `kind` | available command `_meta.jetbrains.air` of a skill | `skill` | AIR, when the skill's `SKILL.md` is found; not for a built-in command or an MCP prompt |
|
||||
| `skillPath` | available command `_meta.jetbrains.air` of a skill | absolute path of the skill's `SKILL.md` | AIR, together with `kind: "skill"` |
|
||||
| `loadReplayEnd` | `session_info_update._meta.jetbrains.air` | `true` | `loadReplayEnd` |
|
||||
| `fork` | `session/fork` request `_meta.jetbrains.air` (client to agent) | `{version: 1, messageId, messageFingerprint?, messageOccurrence?}` | none |
|
||||
|
||||
## JetBrains shared keys
|
||||
@@ -205,6 +214,21 @@ The adapter keeps them where they are.
|
||||
| `steering` | `initialize` response `_meta.steering = {supported: true}` | The agent accepts `_session/steering` for a running turn. |
|
||||
| `quota` | `PromptResponse._meta.quota` | Token usage of the turn, also split by model. |
|
||||
| `authStatus` | `initialize` response `agentCapabilities._meta.authStatus` | The agent pushes `_auth/status_update`. The object carries no payload. |
|
||||
| `promptSuggestions` | `session_info_update._meta.promptSuggestions = {suggestions: [{text}]}`, AIR only | The prompt the agent predicts for the next turn. An empty list clears it. |
|
||||
|
||||
## 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.
|
||||
|
||||
A command containing the whole word `central` and the word sequence `proxy start` is displayed as:
|
||||
|
||||
```json
|
||||
{ "authStatus": { "kind": "gateway", "label": "JetBrains Air Gateway" } }
|
||||
```
|
||||
|
||||
Matching is case-insensitive and accepts multiple whitespace characters between `proxy` and `start`. The command's flags, URL, host and route are not checked. This is a weak display heuristic, not proof of configuration provenance, account ownership or entitlement. Inactive helpers and external cloud backends retain their existing classification. No helper command, key, headers, private route or inferred account is included in this payload.
|
||||
|
||||
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.
|
||||
|
||||
## Zed conventions
|
||||
|
||||
@@ -215,6 +239,7 @@ An AIR client that declares `terminal_output_delta` gets appends instead of `ter
|
||||
A command tool call then has `content: [{type: "terminal", terminalId}]` and `_meta.terminal_info = {terminal_id}`.
|
||||
The output goes in `_meta.terminal_output = {terminal_id, data}`.
|
||||
The end sends `_meta.terminal_exit = {terminal_id, exit_code, signal: null}`.
|
||||
A command that ends without a result (a cancel stopped its subagent, say) gets the exit on the report that closes its tool call, with no exit code (`null`; AIR gets `1`).
|
||||
- A client that declares neither `terminal_output` nor `terminal_output_delta` gets no terminal.
|
||||
The command output is a `console` code block in `content`.
|
||||
- A client that declares `clientCapabilities._meta["terminal-auth"]: true` gets `_meta["terminal-auth"] = {command, args, label}` on the terminal login methods.
|
||||
@@ -307,10 +332,14 @@ For every tool, a client that is not AIR also gets these upstream fields:
|
||||
| `Skill` | `kind: other`, `title: "Load skill: <name>"`. | `_meta.jetbrains.air.skill`. | none |
|
||||
| `TodoWrite`, `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | The stream reports them as a standard `plan` with entries. Only a permission request shows them as a tool call. | A plan that repeats the previous plan is not sent. | Every plan goes out. |
|
||||
| `ReportFindings` | `kind: think`, `title: "Report <n> findings"`. | With `rawInputRendering`, no copy of the findings in `content`. | none |
|
||||
| MCP tools (`mcp__*`) and other tools | `kind: other`, `title` is the tool name. The result text goes to `content`. | `_meta.is_mcp_tool_call` for MCP. | none |
|
||||
| MCP tools (`mcp__*`) and other tools | `kind: other`, `title` is the tool name. The result text goes to `content`. | `_meta.is_mcp_tool_call` for MCP, and `_meta.jetbrains.air.mcp = {server, tool}`. | none |
|
||||
| Context compaction | `compaction_update` when the client declares `session.compaction`. Otherwise a synthetic tool call. | Both forms carry `_meta.jetbrains.air.contextCompaction`. | See [Context compaction](#context-compaction). |
|
||||
| Background work that is not a subagent | none | With `asyncTasks`, async task updates. | none |
|
||||
|
||||
`_meta.jetbrains.air.mcp` comes from the tool name `mcp__<server>__<tool>`, on `tool_call` and on a `tool_call_update` that changes it.
|
||||
`server` is the part up to the next `__`. `tool` is the whole rest and can contain `__`, as in Claude Code.
|
||||
A name that does not split (`mcp__x`) gets no `mcp` key, and `_meta.is_mcp_tool_call` still marks it.
|
||||
|
||||
## Diff patch
|
||||
|
||||
The diff patch extension lets the adapter send one compact Git patch instead of file text snapshots.
|
||||
@@ -585,6 +614,7 @@ The client already has the rest of the tool call, because the adapter sends the
|
||||
- `content` is present only with an exact preview patch for a `diffPatch` client.
|
||||
- `locations` is present only when a valid `blockedPath` is not a location of the tool call yet.
|
||||
- `_meta.claudeCode.mcpServer` names the MCP server of an `mcp__*` tool.
|
||||
It is the server name as authored in the config. `_meta.jetbrains.air.mcp.server` on the tool call is the normalized name (characters outside `[a-zA-Z0-9_-]` replaced by `_`), so the two can differ.
|
||||
|
||||
A client that is not AIR gets the upstream request `toolCall`: `toolCallId`, `name`, `status: pending`, the whole `rawInput`, `title`, `kind`, `content`, and `locations`.
|
||||
Its `_meta.claudeCode` carries `toolName`, and `parentToolUseId` for a subagent tool call, next to `mcpServer`.
|
||||
@@ -800,6 +830,37 @@ While that command is pending, the adapter ignores late updates for the previous
|
||||
A matching runtime `active_goal` stays authoritative and adds the iteration, reason, and timestamp.
|
||||
A failed command restores the previous state.
|
||||
|
||||
## Predicted follow-up prompt
|
||||
|
||||
After a turn, the Claude Code CLI can predict the prompt the user is likely to send next.
|
||||
The adapter asks for it with the SDK option `promptSuggestions: true` and forwards the one prediction of the turn.
|
||||
|
||||
ACP has no field for a composer prefill, so the prediction travels outside the AIR namespace:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "session_info_update",
|
||||
"_meta": { "promptSuggestions": { "suggestions": [{ "text": "Run the tests" }] } }
|
||||
}
|
||||
```
|
||||
|
||||
The key names no vendor, so another client can read it.
|
||||
The adapter sends it only to an AIR client; see the [compatibility rule](#compatibility-rule).
|
||||
|
||||
AIR shows the first non-blank `suggestions[].text` as gray text in an empty composer.
|
||||
Tab fills the composer with it and sends nothing.
|
||||
|
||||
`suggestions: []` clears the prediction the client holds. The adapter clears it:
|
||||
|
||||
- when the next `session/prompt` arrives, because that turn's prediction is spent;
|
||||
- on a `conversation_reset`, because the prediction was drawn from the transcript the reset dropped.
|
||||
|
||||
A prediction the next prompt outran — the user sent it before the adapter read the prediction off the SDK stream — is dropped instead of published.
|
||||
|
||||
The CLI predicts nothing on the first turn of a session, after an API error, in plan mode, and at the account's usage limit, so many turns end with no prediction.
|
||||
A client must treat a turn without one as normal.
|
||||
A caller can turn the whole feature off with `_meta.claudeCode.options.promptSuggestions: false` on `session/new`; a user can turn it off in Claude Code settings or with `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false`.
|
||||
|
||||
## Recommended config values
|
||||
|
||||
The `recommendedValue` extension lets a client show concrete model and effort choices without an ambiguous `default` row.
|
||||
@@ -936,6 +997,20 @@ When an announced task leaves that list, the adapter reports `stopped`.
|
||||
A later terminal event can correct that state to `completed` or `failed`.
|
||||
At shutdown, the adapter finishes each open task as `failed` or `stopped`.
|
||||
|
||||
### Autonomous turn
|
||||
|
||||
Claude Code can start a turn of its own after the prompt ended, for example to answer the notification of a finished background task.
|
||||
ACP v1 has no session state, so the adapter publishes such a turn as an async task.
|
||||
The task has the id `autonomous-turn-<uuid>`, the name `Claude is working`, `taskType: "turn"`, and `showInTranscript: false`.
|
||||
Its description is the summary of the last task notification, when there is one.
|
||||
The task starts at the SDK `running` state while no prompt is active or queued.
|
||||
When the state stays `running`, as it does while background agents run, it starts at the first root output with no prompt open.
|
||||
It ends `completed` at the result of the turn or at the SDK `idle` state, and `failed` at an error result.
|
||||
A coalesced placeholder result (`num_turns: 0`) does not end it.
|
||||
A prompt that the turn takes in ends the task, because that prompt owns the turn now.
|
||||
A stop request interrupts the turn instead of stopping an SDK task.
|
||||
It returns `{ "stopped": false }` while a prompt waits for its turn, because the interrupt would drop that prompt.
|
||||
|
||||
### Stop request
|
||||
|
||||
The client sends `_session/async_task/stop`:
|
||||
@@ -1207,8 +1282,8 @@ The session index makes `session/list` a bounded, ordered page with row metadata
|
||||
|
||||
AIR declares `sessionIndex` in `_meta.jetbrains.air.capabilities`.
|
||||
Unlike the other capabilities, the agent advertises `sessionIndex` only to a client that declared it, and never under ACP v2.
|
||||
Exactly when it advertises `sessionIndex`, the agent also advertises `sessionArchive` (it supports `_session/archive` and `_session/unarchive`), `sessionRename` (it supports `_session/rename`) and `sessionListSubscribe` (it supports `_session/list/subscribe` and `_session/list/unsubscribe`).
|
||||
The client does not declare these three; declaring them without `sessionIndex` enables nothing.
|
||||
Exactly when it advertises `sessionIndex`, the agent also advertises `sessionArchive` (it supports `_session/archive` and `_session/unarchive`), `sessionRename` (it supports `_session/rename`), `sessionListSubscribe` (it supports `_session/list/subscribe` and `_session/list/unsubscribe`) and `sessionWorktrees` (rows carry `agentWorktree`, see [List response](#list-response)).
|
||||
The client does not declare these four; declaring them without `sessionIndex` enables nothing.
|
||||
Everything in this section applies only to such a client, except where [Delete and close](#delete-and-close) says otherwise.
|
||||
A client without the capability gets the `session/list` of before: the SDK `listSessions` call, pages of 1000 by file mtime, `offset:N` cursors, and no row `_meta`.
|
||||
It gets no new notification, and the adapter starts no watcher for it.
|
||||
@@ -1276,6 +1351,7 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
|
||||
"lastPromptAt": "2026-10-06T09:57:40.000Z",
|
||||
"model": "claude-opus-5-5",
|
||||
"forkedFrom": "8f0c1d2e-0000-4000-8000-000000000000",
|
||||
"agentWorktree": true,
|
||||
"state": "idle",
|
||||
"lastTurnEndedAt": "2026-10-06T09:58:10.000Z",
|
||||
"cost": { "amount": 1.23, "currency": "USD" }
|
||||
@@ -1285,10 +1361,19 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
|
||||
- `lastPromptAt` is the time of the last real user prompt in the transcript tail: prompt text (the predicate of the SDK title extractor) or an image or a document, but not a tool result, a meta or compact summary record, a slash command or an interrupt. When a long answer or tool output follows the last prompt, the tail window grows (256 KB, 1 MB, 4 MB) to find it; it is omitted only when the last 4 MB hold no prompt.
|
||||
- `model` is the model of the last assistant message in the tail; messages the CLI makes up (`<synthetic>`) do not count.
|
||||
- `forkedFrom` is the parent session id that the SDK `forkSession` and the CLI fork write on every copied record (`forkedFrom.sessionId`). Sessions started any other way have none.
|
||||
- `agentWorktree` is `true` when the session runs in a linked git worktree that Claude Code made for it (`claude --worktree`, or the worktree tools), and omitted otherwise, never `false`. The transcript rule is the one of AIR's native Claude provider; the adapter adds the git check of the row's `cwd`:
|
||||
- the transcript holds `worktree-state` records, `{"type":"worktree-state","worktreeSession":{originalCwd, worktreePath, worktreeName, worktreeBranch, originalBranch, originalHeadCommit, sessionId, hookBased?, enteredExisting?}}`, which the CLI writes when the session enters or leaves a worktree;
|
||||
- the last line that parses as a `worktree-state` record decides, whatever it holds: the session is marked only when its `worktreeSession` is an object without `enteredExisting: true` (`enteredExisting` means a worktree that existed before, so not made for the session). Any other `worktreeSession` (`null`, which the CLI writes when the session leaves the worktree, a missing key, or a value that is no object) means not marked, and an older record does not count then. Only lines that are not JSON, such as the cut first line of a window, are skipped;
|
||||
- the row's `cwd` must lie in a linked worktree: the adapter reads the git files, without running git. It takes the nearest `.git` above `cwd`, follows a `.git` file to its git dir, reads `commondir`, and requires the git dir to be `<git-common-dir>/worktrees/<name>`. This is checked on every `session/list` and on every row a subscription reads, not cached. The main checkout, a submodule and a directory outside a repository never get the field, and neither does a worktree the user made (it has no record). The adapter does not use `environment.isWorktree` or `git worktree list`;
|
||||
- `hookBased` is not read: a worktree a `WorktreeCreate` hook made is marked only when its directory is a linked git worktree as above, so a hook that makes a plain directory or a checkout of another VCS gives no field. The repository is the nearest one to `cwd`: a session in a repository nested in a linked worktree is not marked, and one in a linked worktree of a nested repository is;
|
||||
- the record is read from the 64 KB tail, else from the 64 KB head, and from the wider tail when the window grows to find the last prompt (see `lastPromptAt`). `claude --worktree` writes it at the start of the session, and the CLI writes it again with the other session metadata at the end of the transcript (on exit and as the transcript grows), so one of these windows almost always holds the latest one. A session that entered or left a worktree in the middle of a long transcript, with no metadata written since, keeps its old value until the CLI writes the metadata again;
|
||||
- a session that made a worktree mid-session (EnterWorktree) is marked too: the CLI moves its transcript to the worktree (a `relocated` record), so the row's `cwd` is the worktree path, not the directory the session started in. The row's `cwd` is always the session's own directory, so the worktree path;
|
||||
- a subscription does not send a row again when only git metadata changes, such as a manual `git worktree remove` or `git worktree move`: the field is checked again only when the transcript changes, or when the rescan finds that the worktrees of the scope changed and resolves the rows again. `session/list` checks it on every read.
|
||||
- `state` is `running`, `idle`, `requires_action`, or `error`, never `unknown`. `error` means the session is not running and its last turn ended with an error; a user cancel or interrupt is no error, and a new turn clears it:
|
||||
- a session this connection runs: the SDK `session_state_changed` state; `error` instead of `idle` when its last turn failed (an error result, `is_error` or an `error_*` subtype, that was not a cancel). A session whose query failed here (not a cancel) is reported as any other session, but `error` until its transcript shows a prompt or a turn end after the failure, unless a busy or waiting interactive CLI holds it;
|
||||
- a session no live Claude Code process holds: `idle`, or `error` when its last turn ended with an API error (an assistant record with `isApiErrorMessage: true`, also when the CLI's turn-end records follow it, with no prompt after it);
|
||||
- a session that an interactive CLI holds (not an `sdk-*` entrypoint) whose registry status is newer than the transcript: `busy` and `shell` are `running`, `waiting` is `requires_action`, `idle` is `idle`, or `error` after such an API error;
|
||||
- any other held session whose last turn ended with an open background task: `running`, also after 10 minutes, until the registry status is `idle`. Claude Code wakes the session again when the task ends, so the session gets one `idle` at the last turn end, not one for each task. A background shell command (`toolUseResult.backgroundTaskId`) or an async sub-agent (`isAsync` with an `agentId`) opens a task. Its `<task-notification>` with a `<status>`, as a user record or a `queue-operation` `remove`, or the result of a `TaskStop` call closes it. A notification without a `<status>`, such as the check-in of a quiet shell or a `Monitor` event, keeps it open. Sidechain records open no task. The tail gives the tasks on top of those of an earlier scan when the file only grew within the tail; else the whole transcript is read, and only for such a held session. A background task that a sub-agent starts is not seen: it is only in the transcript of the sub-agent;
|
||||
- any other held session: `idle` after a finished turn (an assistant `end_turn`, a user interrupt, or the CLI turn-end records), `error` after a turn that ended with an API error, `running` for an unfinished turn written in the last 10 minutes, and no state otherwise.
|
||||
- The live processes come from `<config>/sessions/<pid>.json`, with the liveness rules of Claude Desktop: `kill(pid, 0)` (or `EPERM`), a matching `procStart`, no foreign `pidDomain`, and records older than 24 hours only with a matching `procStart`.
|
||||
The adapter never reads the `.key` files there and never changes the directory.
|
||||
@@ -1380,7 +1465,7 @@ From then on the agent sends `_session/list/changes` notifications for the sessi
|
||||
|
||||
- `cwd` is required and must be an absolute path; a missing, non-string or relative `cwd` is `-32602`. Other parameters are ignored.
|
||||
- The scope is the sessions of `cwd` and of the same subdirectory in every existing linked worktree of its repository, as a list with `includeWorktrees: true` resolves it, in any archive state. Sidechain and subagent transcripts are left out, as in the list.
|
||||
- `sessions` holds the full current row of each session in scope that appeared, or whose `title`, `lastPromptAt`, `state`, `lastTurnEndedAt`, `cost`, `model`, `forkedFrom` or `archived` changed since the last row sent for it on this subscription. A row is exactly the row of `session/list`, `_meta.jetbrains.air` included.
|
||||
- `sessions` holds the full current row of each session in scope that appeared, or whose `title`, `lastPromptAt`, `state`, `lastTurnEndedAt`, `cost`, `model`, `forkedFrom`, `agentWorktree` or `archived` changed since the last row sent for it on this subscription. A row is exactly the row of `session/list`, `_meta.jetbrains.air` included.
|
||||
- After subscribe, the first change of each session sends its row in full, even when only `updatedAt` changed. From then on, a change of `updatedAt` alone sends nothing; the new `updatedAt` comes with the next change.
|
||||
- `removed` holds the ids of sessions that left the scope: deleted (the transcript is gone), or moved to a path outside it. It may name a session the client does not list (one it never listed, one the list hides, such as a session continued in another one when that one changes); the client ignores such an id.
|
||||
- Archive and unarchive are row changes (`archived`): their title records are a transcript change. The agent applies no archive filter: the client filters.
|
||||
@@ -1408,7 +1493,8 @@ It is idempotent, and an unknown id also returns `{}`.
|
||||
- a `sessionIndex` client: deletes every transcript of the session (empty ones too) and every `<sessionId>/` directory, also one left without a transcript by an earlier failed delete. Anything left behind fails the request. A session that runs here is closed first. As with the CLI, the session is deleted also when another Claude Code process has it open. An unknown session is `-32002`.
|
||||
- an AIR client without `sessionIndex`: archives the session instead of deleting it (the title records of [Archive](#archive)), because AIR uses delete for "Done" and can reopen the session. A session that runs here is closed first. The list hides archived sessions from such a client too: those whose custom title, as the SDK list reports it, is archived. A session without a transcript fails with the error of the SDK delete, as before.
|
||||
A `sessionIndex` client sees such a session as archived (`archived: true`), as AIR's native Claude path does.
|
||||
- any other client: the SDK delete, as before.
|
||||
- any other client: the SDK delete. A session without a transcript (unknown, deleted before, or
|
||||
never prompted) succeeds, as ACP asks.
|
||||
|
||||
`session/close` of a session that is not loaded returns `{}` for every client.
|
||||
|
||||
@@ -1426,21 +1512,22 @@ It is idempotent, and an unknown id also returns `{}`.
|
||||
The session index is the `_meta` form of two ACP RFDs: "Session list extensions: limit, order, row metadata and change hints" and "Session Archive and Unarchive" (#2161).
|
||||
Names and semantics follow them; the transport differs, and so do the `archived` list filter and the archive of a running session (see [Relation to ACP RFD #2161](#relation-to-acp-rfd-2161)):
|
||||
|
||||
| Extension | RFD |
|
||||
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| capability `sessionIndex` (`_meta.jetbrains.air`) | `sessionCapabilities.list.limit` |
|
||||
| capability `sessionArchive` (`_meta.jetbrains.air`) | `sessionCapabilities.archive` (#2161) |
|
||||
| capability `sessionRename` (`_meta.jetbrains.air`) | `sessionCapabilities.setTitle` (#1987) |
|
||||
| `_meta.jetbrains.air.list.limit` | `session/list` `limit` |
|
||||
| `_meta.jetbrains.air.list.includeWorktrees` | `session/list` `includeWorktrees` |
|
||||
| `_meta.jetbrains.air.list.archived` (`"unarchived"`, `"archived"`, `"all"`) | `session/list` `archived` (#2161), a boolean there |
|
||||
| row `_meta.jetbrains.air.lastPromptAt`, `model`, `forkedFrom`, `state`, `lastTurnEndedAt`, `cost` | `SessionInfo` fields of the same names; the RFD `state` has no `error` |
|
||||
| row `_meta.jetbrains.air.archived` | `SessionInfo.archived` (#2161) |
|
||||
| `session_info_update` `_meta.jetbrains.air.archived` | `SessionInfoUpdate.archived` (#2161) |
|
||||
| capability `sessionListSubscribe`, `_session/list/subscribe`, `_session/list/unsubscribe` | none |
|
||||
| `_session/list/changes { subscriptionId, sessions, removed }` | none |
|
||||
| `_session/archive`, `_session/unarchive` | `session/archive`, `session/unarchive` (#2161) |
|
||||
| `_session/rename` | `session/set_title` (#1987) |
|
||||
| Extension | RFD |
|
||||
| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| capability `sessionIndex` (`_meta.jetbrains.air`) | `sessionCapabilities.list.limit` |
|
||||
| capability `sessionArchive` (`_meta.jetbrains.air`) | `sessionCapabilities.archive` (#2161) |
|
||||
| capability `sessionRename` (`_meta.jetbrains.air`) | `sessionCapabilities.setTitle` (#1987) |
|
||||
| `_meta.jetbrains.air.list.limit` | `session/list` `limit` |
|
||||
| `_meta.jetbrains.air.list.includeWorktrees` | `session/list` `includeWorktrees` |
|
||||
| `_meta.jetbrains.air.list.archived` (`"unarchived"`, `"archived"`, `"all"`) | `session/list` `archived` (#2161), a boolean there |
|
||||
| row `_meta.jetbrains.air.lastPromptAt`, `model`, `forkedFrom`, `state`, `lastTurnEndedAt`, `cost` | `SessionInfo` fields of the same names; the RFD `state` has no `error` |
|
||||
| row `_meta.jetbrains.air.archived` | `SessionInfo.archived` (#2161) |
|
||||
| `session_info_update` `_meta.jetbrains.air.archived` | `SessionInfoUpdate.archived` (#2161) |
|
||||
| capability `sessionWorktrees`, row `_meta.jetbrains.air.agentWorktree` | none yet: proposed in the session list extensions RFD (agentclientprotocol/agent-client-protocol#2323) as `SessionInfo.agentWorktree`, with the capability named `agentWorktrees` there, not `sessionWorktrees` |
|
||||
| capability `sessionListSubscribe`, `_session/list/subscribe`, `_session/list/unsubscribe` | none |
|
||||
| `_session/list/changes { subscriptionId, sessions, removed }` | none |
|
||||
| `_session/archive`, `_session/unarchive` | `session/archive`, `session/unarchive` (#2161) |
|
||||
| `_session/rename` | `session/set_title` (#1987) |
|
||||
|
||||
### Relation to ACP RFD #2161
|
||||
|
||||
@@ -1455,6 +1542,45 @@ It differs in transport, until the RFD lands in the SDK, in the list filter, and
|
||||
- the list parameter is `_meta.jetbrains.air.list.archived`, and the state is `_meta.jetbrains.air.archived` on rows and in `session_info_update`, not the `archived` fields;
|
||||
- an AIR client without `sessionIndex` still archives with `session/delete` (see [Delete and close](#delete-and-close)), which the RFD asks clients not to do.
|
||||
|
||||
## Background subagents
|
||||
|
||||
A prompt turn can start a background Agent or Task subagent.
|
||||
A SendMessage tool call can also resume a subagent that ended.
|
||||
Claude Code sends the result of the turn when the main agent stops, also while such a subagent still runs.
|
||||
|
||||
### Hold without the capability
|
||||
|
||||
A client that does not declare `backgroundSubagents` gets the hold of issues #864 and #866.
|
||||
The adapter keeps the `session/prompt` request open while a subagent that the turn started or resumed runs.
|
||||
The subagent output, its permission requests and the followup summary of the model then go out inside the turn.
|
||||
The turn ends at the followup result, at an `idle` with no live subagent, at `session/cancel`, or when the next prompt starts.
|
||||
The hold protects a client that stops listening at the prompt response.
|
||||
|
||||
### Behavior with the capability
|
||||
|
||||
AIR declares `backgroundSubagents` in `initialize.clientCapabilities._meta.jetbrains.air.capabilities`.
|
||||
The capability applies to ACP v1 only. ACP v2 keeps the hold, and the agent does not advertise the capability to ACP v2.
|
||||
|
||||
- The turn ends at its result with the stop reason of the result, usually `end_turn`.
|
||||
- The subagent updates, permission requests and elicitations go out after the prompt response.
|
||||
- The routing does not change: the native child session, else the root session with the parent tool call in `_meta.claudeCode.parentToolUseId`.
|
||||
- The followup cycle after a subagent ends is an autonomous cycle. Its updates go out of a turn. Its result does not end a turn.
|
||||
- A steer with no turn in flight starts a new turn, or returns `promptRequired` when the client asks for it.
|
||||
- `session/cancel` with no turn works as before: the adapter interrupts the SDK query and reports each native subagent session as cancelled.
|
||||
- The tool call of a running subagent does not fail the incomplete tool call check of the turn.
|
||||
- The [agent file-change report](#agent-file-change-report) of the turn does not include an edit that a subagent makes after the result.
|
||||
- An open permission request of a subagent counts as an open request for the next turn and for the steer priority.
|
||||
|
||||
### Plan approval out of a turn
|
||||
|
||||
The followup cycle can call ExitPlanMode. AIR then gets the permission request out of a turn.
|
||||
The request has no clear-context option, because the fresh query must continue an ACP turn.
|
||||
"No, keep planning" stops only the followup cycle. There is no turn to end.
|
||||
|
||||
A prompt turn owns the request when the turn is active, or when Claude Code asks before the echo of its prompt.
|
||||
Then the request has the clear-context option.
|
||||
A turn that took over a followup cycle also owns the request of that cycle, and the accepted plan continues in that turn.
|
||||
|
||||
## Context compaction
|
||||
|
||||
The adapter implements the ACP session compaction RFD for a client that declares `clientCapabilities.session.compaction`.
|
||||
@@ -1510,6 +1636,32 @@ For AIR, the companion field also carries the same value under the root key `_me
|
||||
Released AIR versions read only that key.
|
||||
A client that is not AIR gets neither key.
|
||||
|
||||
## Load replay end
|
||||
|
||||
ACP has no signal that the history replay of `session/load` is over, other than the response.
|
||||
The adapter replays the transcript while the Claude Code CLI starts, so the history can arrive long before the response.
|
||||
|
||||
The client declares `loadReplayEnd` in its capabilities. The agent does not advertise it.
|
||||
After the history replay of the loaded session, the agent then sends one more update for that session:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "<loaded session>",
|
||||
"update": {
|
||||
"sessionUpdate": "session_info_update",
|
||||
"_meta": { "jetbrains": { "air": { "version": 1, "loadReplayEnd": true } } }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The update has no `title` and no `updatedAt`. A `null` there would clear the value.
|
||||
- It comes after every replayed update, the subagent history that the replay sends to the child sessions included, and before the `session/load` response.
|
||||
- It comes also for an empty history.
|
||||
- It comes for `session/load` of a new or an already live session, and for the ACP v2 `session/resume` with `replayFrom: {type: "start"}`.
|
||||
- It does not come when the replay stops or fails. The replay stops when the CLI start fails during the replay.
|
||||
- It does not come for a live update, and it does not come to a client without the capability.
|
||||
- The `session/load` can still fail after it, for example when the CLI start fails after the replay. The event means that the recorded history arrived whole. The client decides what to do with the failed load.
|
||||
|
||||
## Session fork point
|
||||
|
||||
AIR can fork a session at one agent message.
|
||||
|
||||
@@ -54,3 +54,14 @@ An ACP caller can provide `settings` via `_meta.claudeCode.options.settings` in
|
||||
- The value must be valid JSON. Invalid JSON will cause session creation to fail with a parse error.
|
||||
- Only `modelOverrides` and `availableModels` keys are read; other keys in the JSON are ignored.
|
||||
- Both fields map directly to the Claude Agent SDK's `Settings` type.
|
||||
|
||||
## Models of a `providers/set` gateway
|
||||
|
||||
When an ACP client routes the agent through a gateway with `providers/set`, the adapter lists the gateway's models before `providers/set` returns, with the provider headers:
|
||||
|
||||
- `anthropic`: `GET {baseUrl}/v1/models`, paginated by `has_more`/`last_id`.
|
||||
- `bedrock`: `GET {baseUrl}/foundation-models` (Bedrock `ListFoundationModels`, `modelSummaries[].modelId`). Only `anthropic.` ids count. The cross-region prefix `global` when listed, else that of the first listed id with one (`eu` for `eu.anthropic.claude-sonnet-5`), becomes the session's `ANTHROPIC_BEDROCK_REGION_PREFIX`, and ids with other prefixes, or without one, are dropped.
|
||||
|
||||
The model picker then offers only the SDK models whose resolved model the gateway lists, matched without dates, version suffixes (`-v1:0`), `-fast`, `@` suffixes, context hints (`[1m]`) and provider prefixes, after the `availableModels` allowlist above. On Bedrock, a model the CLI resolved to another region prefix is dropped as well. When the gateway does not list the model a session runs, that is the default model or a resumed session's last model, the session switches to the first remaining picker row.
|
||||
|
||||
A 4xx answer means the gateway has no listing: `providers/set` succeeds and nothing is narrowed. A failed listing (network error, a status other than 2xx or 4xx, unexpected body, the 10 s timeout, an invalid header, or a base URL with credentials) fails `providers/set` with a JSON-RPC internal error, and the previous provider stays in effect. A listing that matches no SDK model leaves the session with no model to offer: there is no model config option. Gateways set through `authenticate`, `vertex` providers, and native routing are not listed. Setting the same gateway again (the same protocol, base URL and headers) reuses its listing, unless it answered 4xx; `providers/set` and `providers/disable` apply in the order they arrive, and logout clears the routing after `claude auth logout`, including a `providers/set` still listing; `providers/disable` and logout drop it.
|
||||
|
||||
@@ -82,10 +82,10 @@ import {
|
||||
client as acpClient,
|
||||
methods,
|
||||
ndJsonStream,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
// Type-only: Node's type stripping does not elide value imports, and these two
|
||||
// exist only in the .d.ts — importing them normally is a runtime SyntaxError.
|
||||
import type { AnyMessage, Stream } from "@agentclientprotocol/sdk";
|
||||
import type { AnyMessage, Stream } from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
// ---------------------------------------------------------------- steering
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
ndJsonStream,
|
||||
type PromptRequest,
|
||||
type PromptResponse,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** The steering extension method, per the ACP steering wire protocol. */
|
||||
const STEERING_METHOD = "_session/steering";
|
||||
|
||||
524
package-lock.json
generated
524
package-lock.json
generated
@@ -1,69 +1,69 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.89.1",
|
||||
"version": "0.94.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.89.1",
|
||||
"version": "0.94.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.8.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.293",
|
||||
"@agentclientprotocol/sdk": "2.0.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.303",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.5"
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.132.1",
|
||||
"@anthropic-ai/sdk": "0.135.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.71.1",
|
||||
"@typescript-eslint/parser": "8.71.1",
|
||||
"@types/node": "26.6.5",
|
||||
"@typescript-eslint/eslint-plugin": "8.71.2",
|
||||
"@typescript-eslint/parser": "8.71.2",
|
||||
"ajv": "8.20.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"eslint": "10.12.0",
|
||||
"eslint": "10.13.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.13.0",
|
||||
"prettier": "3.9.9",
|
||||
"prettier": "3.9.10",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "5.0.3"
|
||||
"vitest": "5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.8.0.tgz",
|
||||
"integrity": "sha512-fbP7qHZsFyTJVT0/RPrcWJ8An26qLXHEuGCHHEKDyxD1xpXV+Ejyh2LAE6vBAFePkpVjZeeohnbgvuEhZ7wLMg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.0.0.tgz",
|
||||
"integrity": "sha512-Za5gWOPQ44aBOdegDJZ+OjCAPn7POc0cw8BlFJQ1mmUY1OoUWxRi3+QZT4gdZnyEWgKXyfeGRb0Oo1ag9YkQ1w==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
"zod": "^3.25.76 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.293.tgz",
|
||||
"integrity": "sha512-DVfn46SmpfB7xaKuUHsAC+qwY27hOLHNbvF7FoNOAXLZPRTeCr68SjhBjlFOtk+jz5Nf7pfUwnIUDFcdVih6Dg==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.303.tgz",
|
||||
"integrity": "sha512-XBql04V40xr+fmGJh8AmIpqRZKFNrs99BzXzzSVlx+uE5NjFCzKmkNKTdMD0DQtDvFJlijOLoh1siaH+au9Psw==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.293",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.293"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.303",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.303"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -72,9 +72,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.293.tgz",
|
||||
"integrity": "sha512-E2rwiiHr3LwYzBuTROVDpJkts4QmYCrW39ZIN1qixVSNQhSEiz5LjaKxzrwq0xgPgQDU0vd2fLMG1k72BgD24w==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.303.tgz",
|
||||
"integrity": "sha512-QAWV9Zd7i7FQDaUSwZNVXJirqCS9CSF4aPwbkGwLvLH83qcfFOmnyxWUg1H7dU6tJFLOCyDLQzc3xzUA4dakoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -85,9 +85,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.293.tgz",
|
||||
"integrity": "sha512-UJD8kX1XDhMqs/0LLxD012h+S9U3Q8XPbRSNcnPQEMNAoEF+lh898xPbVnkUgPKl3jUOUyqYRwFRwDiUJD+GJg==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.303.tgz",
|
||||
"integrity": "sha512-o931musaryuq04fMaEjceCEmgsBrLh39WvwsQWmv070h9jXS8EmWRFpdSILv1xrUFXiukRSq8yUDRVzg+FvTug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -98,9 +98,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.293.tgz",
|
||||
"integrity": "sha512-B3M5gw6pPH/ckMtrLtGg4ZY1adLVnPoXkEV+Ruu0j/gdFzwuLWR64Spa0RCsbo7BXVN6w5NTqkuZk+HBfsPUZA==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.303.tgz",
|
||||
"integrity": "sha512-c04i9i1BU3j9G3D7XdVzXTdM3/sDpQH+1o8jNcMPnztOTYbNFmH/cacgeX9x3N112r+5VhCEHXGILoe5GxVxeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -111,9 +111,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.293.tgz",
|
||||
"integrity": "sha512-l5Nfwox69bBMEccSQfPQFOq9hbSqQIMsi4H/UXaMSsqgnxunXuKEBXb5klXZJwk4vUt/NzeqAjB/UFbKqW29HQ==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.303.tgz",
|
||||
"integrity": "sha512-a/OsLTEx9ly9hC26cXidg9mtTMA00SQnEiTN9P4IPNj64uSp/7uBnlWl0VeUoVFbgKYYq6XrGRcD6F1ilQsYxg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -124,9 +124,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.293.tgz",
|
||||
"integrity": "sha512-MtqXROSQiuqOCNXfC4k3/3DVfgTNLEdaT5jp/omaZeiPtnRtfCz2KTtBTVG4IwbEDFaKsqfzPsPFzN7X6avwkg==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.303.tgz",
|
||||
"integrity": "sha512-lsm1sX2+9Wos8glBbn86M8leFuK60LKB1cJdS0b2HiTSwiDCqpUKYRGJNUlW1JdUTwJB5nDv6S8/FOJExm1/tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -137,9 +137,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.293.tgz",
|
||||
"integrity": "sha512-5N30l2Y17ZV9q0TGCMXtrkTleX+RVtUMoOL+48qQ7ZrKWGYYBtVDS59cIXDv+TAGqoyYVmklw0W2Sy4dA40BwQ==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.303.tgz",
|
||||
"integrity": "sha512-yixAa3ZOZd1hiI4mpoukzT9TnuDCzSEHyh7pHcbYTRmdRE2y8bAOmI0eiMFJPivnG4iyGhS2C4XNAe6hAjl30w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -150,9 +150,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.293.tgz",
|
||||
"integrity": "sha512-WvPf8cXna446bTx2U4WVQc5+iUgDn94ve8aauvWwcG8n9oa+1wNCKNGFxk8HvS7DQJhF8dfrCbcF068uNFqYNw==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.303.tgz",
|
||||
"integrity": "sha512-AnEseiw5E1JF3XRaGIZN880V/i2OxCaTOQJF9F1+oELleSdvLnCUxCE6/baGvaZjrgvffO9YbYChBpgUKj9j7A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -163,9 +163,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.293",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.293.tgz",
|
||||
"integrity": "sha512-SVaIDHtpCwM0r0m9jzue1U7yktIZ1eJhR1O3e0UCHYTJluaIXty5m6DzqhG4rjnEoNlEAl9rQLqPigS/R5+IiQ==",
|
||||
"version": "0.3.303",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.303.tgz",
|
||||
"integrity": "sha512-j5kABFEQ6x6T5AB/glmn0r9VwZRbzcH+nSEOWglB69ODNfkZA2btjcz9r794+2BLqJ9QAyswiYWcOX4sDz/RmQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -176,11 +176,12 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.132.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.132.1.tgz",
|
||||
"integrity": "sha512-Dg+Rd0effi+4FyNwgEx6JXl9COefsZ3C7BOPSKYIWboQOEIi9HXtu1WBdND0DIgMF7Qt1vuCSjsbs7sjUkzJuQ==",
|
||||
"version": "0.135.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.135.0.tgz",
|
||||
"integrity": "sha512-WWrdL57JhU5KoynpTjt9GXK6k7ud9nPw2uw/Trn+oBXh6s0KpjhAccWZm2Qxw08ARYG1UDQj5iC+JO42j1rt/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.4.1",
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
"standardwebhooks": "^1.0.0"
|
||||
},
|
||||
@@ -358,9 +359,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.4.tgz",
|
||||
"integrity": "sha512-2g1qeHl4BduwDn6L8nkAvDrPUWPMlkQ//a5NhP/zIyaXKMjZNRV+cG9Csu+BiJFf6+68pqzjf4Hi5sJGKHuS1g==",
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.5.tgz",
|
||||
"integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -529,10 +530,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
|
||||
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.153.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz",
|
||||
"integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==",
|
||||
"version": "0.154.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz",
|
||||
"integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -541,9 +551,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz",
|
||||
"integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz",
|
||||
"integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -559,9 +569,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz",
|
||||
"integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -577,9 +587,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz",
|
||||
"integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -595,9 +605,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz",
|
||||
"integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz",
|
||||
"integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -613,9 +623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz",
|
||||
"integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz",
|
||||
"integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -631,9 +641,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz",
|
||||
"integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz",
|
||||
"integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -649,9 +659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz",
|
||||
"integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -667,9 +677,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz",
|
||||
"integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz",
|
||||
"integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -685,9 +695,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz",
|
||||
"integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -703,9 +713,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz",
|
||||
"integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -721,9 +731,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz",
|
||||
"integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz",
|
||||
"integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -739,9 +749,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz",
|
||||
"integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz",
|
||||
"integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -757,9 +767,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz",
|
||||
"integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz",
|
||||
"integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -775,9 +785,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz",
|
||||
"integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz",
|
||||
"integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -793,9 +803,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz",
|
||||
"integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz",
|
||||
"integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -899,9 +909,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz",
|
||||
"integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==",
|
||||
"version": "26.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz",
|
||||
"integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -909,17 +919,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz",
|
||||
"integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz",
|
||||
"integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/type-utils": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/type-utils": "8.71.2",
|
||||
"@typescript-eslint/utils": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -932,22 +942,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.71.1",
|
||||
"@typescript-eslint/parser": "^8.71.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz",
|
||||
"integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz",
|
||||
"integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -963,14 +973,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz",
|
||||
"integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz",
|
||||
"integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.71.1",
|
||||
"@typescript-eslint/types": "^8.71.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.71.2",
|
||||
"@typescript-eslint/types": "^8.71.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -985,14 +995,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz",
|
||||
"integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz",
|
||||
"integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1"
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1003,9 +1013,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz",
|
||||
"integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1020,15 +1030,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz",
|
||||
"integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2",
|
||||
"@typescript-eslint/utils": "8.71.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1045,9 +1055,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz",
|
||||
"integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz",
|
||||
"integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1059,16 +1069,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz",
|
||||
"integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz",
|
||||
"integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.71.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"@typescript-eslint/project-service": "8.71.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/visitor-keys": "8.71.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1087,16 +1097,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz",
|
||||
"integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz",
|
||||
"integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1"
|
||||
"@typescript-eslint/scope-manager": "8.71.2",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"@typescript-eslint/typescript-estree": "8.71.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1111,13 +1121,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz",
|
||||
"integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==",
|
||||
"version": "8.71.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz",
|
||||
"integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1142,22 +1152,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz",
|
||||
"integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz",
|
||||
"integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.31",
|
||||
"@vitest/spy": "5.0.3",
|
||||
"@vitest/spy": "5.0.5",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^1.2.3"
|
||||
"magic-string": "^1.4.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"msw": "^2.4.9 || ^3.0.0",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -1181,9 +1191,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz",
|
||||
"integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz",
|
||||
"integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -1418,9 +1428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
|
||||
"integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz",
|
||||
"integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -1647,9 +1657,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz",
|
||||
"integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==",
|
||||
"version": "10.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz",
|
||||
"integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -1931,16 +1941,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz",
|
||||
"integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "^1.0.5",
|
||||
"body-parser": "^2.3.0",
|
||||
"content-disposition": "^2.0.1",
|
||||
"content-type": "^2.0.0",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "^4.4.0",
|
||||
@@ -1956,14 +1966,14 @@
|
||||
"on-finished": "^2.4.1",
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"proxy-addr": "^2.0.8",
|
||||
"qs": "^6.16.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
"serve-static": "^2.2.0",
|
||||
"statuses": "^2.0.1",
|
||||
"type-is": "^2.0.1",
|
||||
"type-is": "^2.1.0",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1994,6 +2004,20 @@
|
||||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/content-type": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
|
||||
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -2021,9 +2045,9 @@
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
||||
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
|
||||
"integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -2368,9 +2392,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
|
||||
"integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
|
||||
"version": "10.7.4",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz",
|
||||
"integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2964,9 +2988,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
|
||||
"integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz",
|
||||
"integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
@@ -3122,9 +3146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.29",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
|
||||
"integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
|
||||
"version": "8.5.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
|
||||
"integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3143,7 +3167,7 @@
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.19",
|
||||
"nanoid": "^3.3.20",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.2"
|
||||
},
|
||||
@@ -3162,9 +3186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.9",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz",
|
||||
"integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==",
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
|
||||
"integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -3226,9 +3250,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
|
||||
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
|
||||
"version": "6.16.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz",
|
||||
"integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3282,14 +3306,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz",
|
||||
"integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==",
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz",
|
||||
"integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.153.0",
|
||||
"@oxc-project/types": "=0.154.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3299,21 +3323,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.13",
|
||||
"@rolldown/binding-android-arm64": "1.2.13",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.13",
|
||||
"@rolldown/binding-darwin-x64": "1.2.13",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.13",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.13",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.13",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.13",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.13",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.13",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.13",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.13",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.13",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.13",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.13"
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.14",
|
||||
"@rolldown/binding-android-arm64": "1.2.14",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.14",
|
||||
"@rolldown/binding-darwin-x64": "1.2.14",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.14",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.14",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.14",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.14",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.14",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.14",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.14",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.14"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3543,9 +3567,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz",
|
||||
"integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz",
|
||||
"integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3767,17 +3791,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.3.tgz",
|
||||
"integrity": "sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==",
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
|
||||
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"postcss": "^8.5.28",
|
||||
"rolldown": "~1.2.11",
|
||||
"postcss": "^8.5.29",
|
||||
"rolldown": "~1.2.12",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3846,23 +3870,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz",
|
||||
"integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz",
|
||||
"integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/mocker": "5.0.3",
|
||||
"chai": "^6.2.2",
|
||||
"@vitest/mocker": "5.0.5",
|
||||
"chai": "^6.3.0",
|
||||
"es-module-lexer": "^2.3.2",
|
||||
"expect-type": "^1.4.0",
|
||||
"magic-string": "^1.2.3",
|
||||
"obug": "^2.1.4",
|
||||
"magic-string": "^1.4.3",
|
||||
"obug": "^3.0.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"std-env": "^4.2.0",
|
||||
"tinybench": "^6.1.4",
|
||||
"tinyexec": "^1.3.0",
|
||||
"std-env": "^4.3.0",
|
||||
"tinybench": "^6.2.2",
|
||||
"tinyexec": "^1.3.1",
|
||||
"tinyglobby": "^0.2.17",
|
||||
"why-is-node-running": "3.2.1"
|
||||
},
|
||||
@@ -3879,12 +3903,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "5.0.3",
|
||||
"@vitest/browser-preview": "5.0.3",
|
||||
"@vitest/browser-playwright": "5.0.5",
|
||||
"@vitest/browser-preview": "5.0.5",
|
||||
"@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0",
|
||||
"@vitest/coverage-istanbul": "5.0.3",
|
||||
"@vitest/coverage-v8": "5.0.3",
|
||||
"@vitest/ui": "5.0.3",
|
||||
"@vitest/coverage-istanbul": "5.0.5",
|
||||
"@vitest/coverage-v8": "5.0.5",
|
||||
"@vitest/ui": "5.0.5",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.4.0 || ^7.0.0 || ^8.0.0"
|
||||
@@ -3997,9 +4021,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.6.5",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
|
||||
"integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
|
||||
"version": "4.6.7",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
|
||||
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
22
package.json
22
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.89.1",
|
||||
"version": "0.94.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -65,26 +65,26 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.8.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.293",
|
||||
"@agentclientprotocol/sdk": "2.0.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.303",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.5"
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.132.1",
|
||||
"@anthropic-ai/sdk": "0.135.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.4",
|
||||
"@typescript-eslint/eslint-plugin": "8.71.1",
|
||||
"@typescript-eslint/parser": "8.71.1",
|
||||
"@types/node": "26.6.5",
|
||||
"@typescript-eslint/eslint-plugin": "8.71.2",
|
||||
"@typescript-eslint/parser": "8.71.2",
|
||||
"ajv": "8.20.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"eslint": "10.12.0",
|
||||
"eslint": "10.13.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.13.0",
|
||||
"prettier": "3.9.9",
|
||||
"prettier": "3.9.10",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "5.0.3"
|
||||
"vitest": "5.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
1909
src/acp-agent.ts
1909
src/acp-agent.ts
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import type {
|
||||
ClientCapabilities,
|
||||
SessionCapabilities,
|
||||
SessionNotification,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY,
|
||||
clientSupportsAirCapability,
|
||||
@@ -17,7 +17,8 @@ export { AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY } from "./air-extension.js";
|
||||
* - `rfd`: the subagents RFD (agentclientprotocol/agent-client-protocol#1992,
|
||||
* as revised): `subagent_update` and `session_message`, which the SDK types.
|
||||
* A client that declares `clientCapabilities.subagents` and is not AIR gets
|
||||
* it. (ACP v2 clients do not get subagents yet.)
|
||||
* it. (An ACP v2 client gets it without declaring anything, since subagents
|
||||
* are part of v2's baseline.)
|
||||
* - `air`: the earlier draft of that RFD, which AIR implements:
|
||||
* `subagent_spawned` and `subagent_state_update`, typed below. AIR gets it,
|
||||
* whichever capability it declares (`docs/air-extensions.md`).
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export const AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY = "nativeSubagentSessions";
|
||||
export const AIR_ASYNC_TASKS_CAPABILITY = "asyncTasks";
|
||||
/**
|
||||
* A prompt turn ends at its result also while its background subagents run.
|
||||
* The subagent updates and requests then go out after the prompt response.
|
||||
*/
|
||||
export const AIR_BACKGROUND_SUBAGENTS_CAPABILITY = "backgroundSubagents";
|
||||
export const AIR_SESSION_FAILURE_CAPABILITY = "sessionFailure";
|
||||
export const AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY = "recommendedValue";
|
||||
export const AIR_CUSTOM_INSTRUCTIONS_CAPABILITY = "customInstructions";
|
||||
@@ -34,11 +39,26 @@ export const AIR_SESSION_RENAME_CAPABILITY = "sessionRename";
|
||||
* {@link AIR_SESSION_INDEX_CAPABILITY} is; the client does not declare it.
|
||||
*/
|
||||
export const AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY = "sessionListSubscribe";
|
||||
/**
|
||||
* Session index rows carry `agentWorktree: true` when the session runs in a
|
||||
* linked git worktree that Claude Code made for it. Advertised exactly when
|
||||
* {@link AIR_SESSION_INDEX_CAPABILITY} is; the client does not declare it.
|
||||
*/
|
||||
export const AIR_SESSION_WORKTREES_CAPABILITY = "sessionWorktrees";
|
||||
/**
|
||||
* The client wants to know when the history replay of a loaded session is
|
||||
* whole. After the last replayed update, the agent sends one
|
||||
* `session_info_update` with `_meta.jetbrains.air.loadReplayEnd: true` (see
|
||||
* {@link AIR_LOAD_REPLAY_END_KEY}). The client declares it; the agent does not
|
||||
* advertise it.
|
||||
*/
|
||||
export const AIR_LOAD_REPLAY_END_CAPABILITY = "loadReplayEnd";
|
||||
|
||||
/** The `_meta.jetbrains.air` keys that the ACP tool call contract defines. */
|
||||
export const AIR_COMMAND_TITLE_KEY = "commandTitle";
|
||||
export const AIR_SUBAGENT_KEY = "subagent";
|
||||
export const AIR_SKILL_KEY = "skill";
|
||||
export const AIR_MCP_KEY = "mcp";
|
||||
export const AIR_CONTEXT_COMPACTION_KEY = "contextCompaction";
|
||||
export const AIR_GOAL_KEY = "goal";
|
||||
export const AIR_KIND_KEY = "kind";
|
||||
@@ -46,6 +66,8 @@ export const AIR_KIND_KEY = "kind";
|
||||
export const AIR_SKILL_PATH_KEY = "skillPath";
|
||||
export const AIR_PERMISSION_KEY = "permission";
|
||||
export const AIR_CUSTOM_ANSWER_KEY = "customAnswer";
|
||||
/** The `_meta.jetbrains.air` key of the update that ends a whole history replay. */
|
||||
export const AIR_LOAD_REPLAY_END_KEY = "loadReplayEnd";
|
||||
/**
|
||||
* The root `_meta` spelling of {@link AIR_CUSTOM_ANSWER_KEY} that released
|
||||
* AIR versions read. Remove it when the ACP registry no longer serves AIR
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ClientCapabilities } from "@agentclientprotocol/sdk";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { ClientCapabilities } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AcpSessionNotification, AsyncTaskState } from "./acp-subagents.js";
|
||||
import { AIR_ASYNC_TASKS_CAPABILITY, clientSupportsAirCapability } from "./air-extension.js";
|
||||
import { noticeOrTranscriptUpdate } from "./session-notices.js";
|
||||
@@ -39,6 +40,12 @@ type AsyncTask = {
|
||||
terminalSource?: TerminalSource;
|
||||
/** The optional fields that the client holds now, as JSON. */
|
||||
published: Map<string, string>;
|
||||
/**
|
||||
* Whether the task stands for a model turn that Claude Code started on its
|
||||
* own, see {@link AsyncTaskRuntime.autonomousTurnStarted}. The SDK knows no
|
||||
* such task, so its background task list never ends it.
|
||||
*/
|
||||
autonomousTurn: boolean;
|
||||
};
|
||||
|
||||
type TaskIdentity = { taskId?: unknown; task_id?: unknown };
|
||||
@@ -121,6 +128,10 @@ export class AsyncTaskRuntime {
|
||||
* The oldest entries are dropped.
|
||||
*/
|
||||
private readonly outputPathsByToolCall = new Map<string, string[]>();
|
||||
/** The task of the autonomous turn that runs now. */
|
||||
private autonomousTurn?: AsyncTask;
|
||||
/** The summary of the last task notification, which usually starts the next autonomous turn. */
|
||||
private lastNotificationSummary?: string;
|
||||
|
||||
constructor(
|
||||
readonly enabled: boolean,
|
||||
@@ -300,6 +311,7 @@ export class AsyncTaskRuntime {
|
||||
const state = taskState(message.status);
|
||||
if (!taskId || !state || state === "running" || state === "paused") return;
|
||||
|
||||
this.lastNotificationSummary = nonBlankString(message.summary) ?? this.lastNotificationSummary;
|
||||
const task = this.task(taskId);
|
||||
const wasTerminal = isTerminal(task.state);
|
||||
const correctsLevelState = wasTerminal && task.terminalSource === "level";
|
||||
@@ -363,6 +375,7 @@ export class AsyncTaskRuntime {
|
||||
if (
|
||||
(task.announced || task.held) &&
|
||||
!task.ignored &&
|
||||
!task.autonomousTurn &&
|
||||
!isTerminal(task.state) &&
|
||||
!live.has(task.id)
|
||||
) {
|
||||
@@ -378,6 +391,7 @@ export class AsyncTaskRuntime {
|
||||
}
|
||||
|
||||
async finishAll(state: Extract<AsyncTaskState, "failed" | "stopped">): Promise<void> {
|
||||
this.autonomousTurn = undefined;
|
||||
const errors: unknown[] = [];
|
||||
for (const task of this.tasks.values()) {
|
||||
if ((task.announced || task.held) && !task.ignored && !isTerminal(task.state)) {
|
||||
@@ -412,6 +426,7 @@ export class AsyncTaskRuntime {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task || !task.announced || task.ignored || task.stopAnnounced) return;
|
||||
task.stopAnnounced = true;
|
||||
if (this.autonomousTurn === task) this.autonomousTurn = undefined;
|
||||
// No terminal summary: a stopped task leaves the Async Tasks panel at once,
|
||||
// so anything said there is said to nobody.
|
||||
await this.finish(task, "stopped", undefined, "event");
|
||||
@@ -438,6 +453,47 @@ export class AsyncTaskRuntime {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a model turn that Claude Code started with no prompt open, for
|
||||
* example to answer a task notification. ACP v1 has no session state, and
|
||||
* the client already got the stop reason of its last prompt, so this task is
|
||||
* the only sign that the agent works. The tool calls of the turn are in the
|
||||
* transcript already, so the task is not shown there. Does nothing while
|
||||
* such a task is open.
|
||||
*/
|
||||
async autonomousTurnStarted(): Promise<void> {
|
||||
if (!this.enabled || this.autonomousTurn) return;
|
||||
const task = this.task(`autonomous-turn-${randomUUID()}`);
|
||||
task.autonomousTurn = true;
|
||||
task.startedObserved = true;
|
||||
task.name = "Claude is working";
|
||||
task.taskType = "turn";
|
||||
task.description =
|
||||
this.lastNotificationSummary ?? "Claude Code started a turn without a prompt.";
|
||||
task.showInTranscript = false;
|
||||
this.lastNotificationSummary = undefined;
|
||||
this.autonomousTurn = task;
|
||||
await this.announce(task, { withoutToolCall: true });
|
||||
}
|
||||
|
||||
/** Whether the task of an autonomous turn is open. */
|
||||
get autonomousTurnOpen(): boolean {
|
||||
return this.autonomousTurn !== undefined;
|
||||
}
|
||||
|
||||
/** Ends the task of the autonomous turn that runs now, if there is one. */
|
||||
async autonomousTurnEnded(state: "completed" | "failed"): Promise<void> {
|
||||
const task = this.autonomousTurn;
|
||||
if (!task) return;
|
||||
this.autonomousTurn = undefined;
|
||||
await this.finish(task, state, undefined, "event");
|
||||
}
|
||||
|
||||
/** Whether the task stands for an autonomous turn, which a stop interrupts. */
|
||||
isAutonomousTurn(taskId: string): boolean {
|
||||
return this.tasks.get(taskId)?.autonomousTurn === true;
|
||||
}
|
||||
|
||||
/** Whether a task of the tool call went to the background, so the client gets it. */
|
||||
isBackgroundedToolCall(toolCallId: string): boolean {
|
||||
for (const task of this.tasks.values()) {
|
||||
@@ -488,6 +544,8 @@ export class AsyncTaskRuntime {
|
||||
clear(): void {
|
||||
this.tasks.clear();
|
||||
this.outputPathsByToolCall.clear();
|
||||
this.autonomousTurn = undefined;
|
||||
this.lastNotificationSummary = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -520,6 +578,7 @@ export class AsyncTaskRuntime {
|
||||
stopAnnounced: false,
|
||||
state: "running",
|
||||
published: new Map(),
|
||||
autonomousTurn: false,
|
||||
};
|
||||
this.tasks.set(taskId, task);
|
||||
return task;
|
||||
|
||||
@@ -103,6 +103,13 @@ const NOT_LOGGED_IN: AuthStatus = {
|
||||
label: "Not logged in",
|
||||
};
|
||||
|
||||
const API_KEY_SOURCE_DETAILS = new Map([
|
||||
["apiKeyHelper", "API key helper"],
|
||||
["env", "env"],
|
||||
["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY"],
|
||||
["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_AUTH_TOKEN"],
|
||||
]);
|
||||
|
||||
/** Display names for the non-firstParty backends, where auth lives outside the
|
||||
* agent (AWS credentials, gcloud ADC, …). */
|
||||
const EXTERNAL_PROVIDER_LABELS: Record<string, string> = {
|
||||
@@ -162,7 +169,10 @@ export function accountInfoHasIdentitySignal(account: AccountInfo | undefined):
|
||||
/** Session-time mapping from the SDK's `AccountInfo`, the richest source when
|
||||
* it is populated. Returns `undefined` when the account carries no identity
|
||||
* signal, so callers keep the status they already know. */
|
||||
export function fromAccountInfo(account: AccountInfo | undefined): AuthStatus | undefined {
|
||||
export function fromAccountInfo(
|
||||
account: AccountInfo | undefined,
|
||||
apiKeyHelper?: string,
|
||||
): AuthStatus | undefined {
|
||||
if (!account || !accountInfoHasIdentitySignal(account)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -172,13 +182,14 @@ export function fromAccountInfo(account: AccountInfo | undefined): AuthStatus |
|
||||
apiKeySource: account.apiKeySource,
|
||||
email: account.email,
|
||||
organization: account.organization,
|
||||
apiKeyHelper,
|
||||
});
|
||||
}
|
||||
|
||||
/** Pre-session mapping from `claude auth status --json` stdout. Returns
|
||||
* `undefined` when the output is not the expected JSON object, so callers can
|
||||
* report "not known" instead of guessing. */
|
||||
export function fromCliStatus(stdout: string): AuthStatus | undefined {
|
||||
export function fromCliStatus(stdout: string, apiKeyHelper?: string): AuthStatus | undefined {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(stdout);
|
||||
@@ -205,6 +216,7 @@ export function fromCliStatus(stdout: string): AuthStatus | undefined {
|
||||
apiKeySource: status.apiKeySource,
|
||||
email: status.email,
|
||||
organization: status.orgName,
|
||||
apiKeyHelper,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,6 +236,7 @@ function mapAuthFields(fields: {
|
||||
apiKeySource?: string;
|
||||
email?: string;
|
||||
organization?: string;
|
||||
apiKeyHelper?: string;
|
||||
}): AuthStatus {
|
||||
const { apiProvider, subscriptionType, apiKeySource, email, organization } = fields;
|
||||
|
||||
@@ -237,11 +250,19 @@ function mapAuthFields(fields: {
|
||||
};
|
||||
}
|
||||
if (apiKeySource) {
|
||||
if (
|
||||
apiKeySource === "apiKeyHelper" &&
|
||||
typeof fields.apiKeyHelper === "string" &&
|
||||
/\bcentral\b/i.test(fields.apiKeyHelper) &&
|
||||
/\bproxy\s+start\b/i.test(fields.apiKeyHelper)
|
||||
) {
|
||||
return { kind: "gateway", label: "JetBrains Air Gateway" };
|
||||
}
|
||||
const detail = API_KEY_SOURCE_DETAILS.get(apiKeySource);
|
||||
return {
|
||||
kind: "api_key",
|
||||
label: "Anthropic API key",
|
||||
// The source ("apiKeyHelper", "env", …) is the specifics line.
|
||||
detail: apiKeySource,
|
||||
...(detail ? { detail } : {}),
|
||||
};
|
||||
}
|
||||
if (subscriptionType) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NewSessionRequest, SessionConfigOption } from "@agentclientprotocol/sdk";
|
||||
import type { NewSessionRequest, SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type {
|
||||
EffortLevel,
|
||||
Options,
|
||||
@@ -44,6 +44,7 @@ export type ClearContextSession<Turn extends ClearContextTurn = ClearContextTurn
|
||||
turnQueue?: Turn[];
|
||||
pendingExitPlanContextReset?: ClearContextReset;
|
||||
contextUsedTokens?: number;
|
||||
sentUserUuids?: Set<string>;
|
||||
input: { push(message: SDKUserMessage): unknown };
|
||||
};
|
||||
|
||||
@@ -158,6 +159,7 @@ export async function continuePlanInFreshContext<
|
||||
}
|
||||
assertRestartActive(freshSession);
|
||||
|
||||
(freshSession.sentUserUuids ??= new Set()).add(turn.promptUuid);
|
||||
freshSession.input.push(host.continuationMessage(sessionId, reset.plan, turn.promptUuid));
|
||||
assertRestartActive(freshSession);
|
||||
host.ensureConsumer(freshSession, sessionId);
|
||||
|
||||
74
src/client-system-prompt.ts
Normal file
74
src/client-system-prompt.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* The client-provided system prompt of the unstable `systemPrompt` field on
|
||||
* `session/new`, `session/load`, `session/resume`, and `session/fork`.
|
||||
*
|
||||
* - `append` keeps the Claude Code preset and adds the text after it, the
|
||||
* SDK's `{ type: "preset", preset: "claude_code", append }`.
|
||||
* - `override` replaces the Claude Code preset with the text, the SDK's custom
|
||||
* string prompt. Tools, MCP servers, permissions, and settings (including the
|
||||
* CLAUDE.md files that `settingSources` loads) are unchanged.
|
||||
*
|
||||
* The CLI records the system prompt of a conversation on its first request
|
||||
* and reuses it on resume, so a different `systemPrompt` on a later
|
||||
* `session/resume` takes effect at the next compaction. The RFD allows this
|
||||
* deferral.
|
||||
*/
|
||||
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
RequestError,
|
||||
type SessionCapabilities,
|
||||
type SystemPrompt,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** The `sessionCapabilities.systemPrompt` the agent advertises: both modes. */
|
||||
export const SYSTEM_PROMPT_CAPABILITY = {
|
||||
mode: { override: {} },
|
||||
} satisfies NonNullable<SessionCapabilities["systemPrompt"]>;
|
||||
|
||||
export type ClientSystemPrompt = { mode: "append" | "override"; text: string };
|
||||
|
||||
/**
|
||||
* The mode and text of a `systemPrompt`, or `undefined` when the request has
|
||||
* none. Throws `invalid_params` for a mode other than `append` and `override`
|
||||
* and for a content block other than text: the RFD requires an error, and no
|
||||
* session, for a prompt the agent cannot apply.
|
||||
*/
|
||||
export function clientSystemPrompt(
|
||||
systemPrompt: SystemPrompt | null | undefined,
|
||||
): ClientSystemPrompt | undefined {
|
||||
if (systemPrompt == null) return undefined;
|
||||
const { mode, content } = systemPrompt;
|
||||
if (mode !== "append" && mode !== "override") {
|
||||
throw RequestError.invalidParams(
|
||||
{ systemPrompt: { mode } },
|
||||
`Unsupported systemPrompt mode: ${mode}`,
|
||||
);
|
||||
}
|
||||
const text: string[] = [];
|
||||
for (const block of content) {
|
||||
if (block.type !== "text") {
|
||||
throw RequestError.invalidParams(
|
||||
{ systemPrompt: { content: { type: block.type } } },
|
||||
`systemPrompt content must be text, but received a ${block.type} block`,
|
||||
);
|
||||
}
|
||||
text.push(block.text);
|
||||
}
|
||||
return { mode, text: text.join("\n\n") };
|
||||
}
|
||||
|
||||
/**
|
||||
* The SDK `systemPrompt` option for a client system prompt. `customInstructions`
|
||||
* (AIR's `customInstructions` on `session/new` metadata) still follows the
|
||||
* client text in both modes.
|
||||
*/
|
||||
export function sdkSystemPrompt(
|
||||
prompt: ClientSystemPrompt,
|
||||
customInstructions: string | undefined,
|
||||
): NonNullable<Options["systemPrompt"]> {
|
||||
const text = [prompt.text, customInstructions].filter((part) => part).join("\n\n");
|
||||
if (prompt.mode === "override") return text;
|
||||
return text
|
||||
? { type: "preset", preset: "claude_code", append: text }
|
||||
: { type: "preset", preset: "claude_code" };
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
ContextCompactionMetadata,
|
||||
createContextCompactionMeta,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk";
|
||||
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
||||
import { ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk/unstable";
|
||||
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import { structuredPatch } from "diff";
|
||||
import { constants } from "node:fs";
|
||||
import { type FileHandle, open } from "node:fs/promises";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
|
||||
import { CreateElicitationResponse } from "@agentclientprotocol/sdk/unstable";
|
||||
import type {
|
||||
CreateElicitationRequest,
|
||||
ElicitationAcceptAction,
|
||||
ElicitationPropertySchema,
|
||||
ElicitationSchema,
|
||||
EnumOption,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type {
|
||||
AskUserQuestionInput,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import type { SessionNotification } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
type ClearContextCoordinatorHost,
|
||||
|
||||
@@ -19,6 +19,10 @@ export type FileChangeReportTurnState = {
|
||||
export type NativeFileChangeReportTurn = {
|
||||
promptUuid: string;
|
||||
fileChangeReport?: FileChangeReportTurnState;
|
||||
/** Raw `file_path`/`notebook_path` values of the file-tool edits that
|
||||
* subagents of this turn completed. Checkpoints do not track files that
|
||||
* only a subagent edited. */
|
||||
editedPaths?: Set<string>;
|
||||
};
|
||||
|
||||
export type FileChangeReportWorkspace = {
|
||||
@@ -94,6 +98,41 @@ export function agentFileChangeReportMeta(
|
||||
return withAirMeta(undefined, AGENT_FILE_CHANGE_REPORT_CAPABILITY, result);
|
||||
}
|
||||
|
||||
const FILE_EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
||||
|
||||
/**
|
||||
* Adds to `turn.editedPaths` the `file_path` (`notebook_path` for NotebookEdit)
|
||||
* of each successful file-tool edit among the tool results of a subagent
|
||||
* message. Root-agent messages (`parentToolUseId === null`) are skipped: the
|
||||
* checkpoint already tracks their files. Must run before `toAcpNotifications`
|
||||
* drops the tool use cache entries.
|
||||
*/
|
||||
export function recordSubagentFileEdits(
|
||||
turn: (NativeFileChangeReportTurn & { settled: boolean }) | null | undefined,
|
||||
parentToolUseId: string | null,
|
||||
content: unknown,
|
||||
toolUses: Record<string, { name: string; input: unknown } | undefined>,
|
||||
): void {
|
||||
if (parentToolUseId === null || !turn || turn.settled || !turn.fileChangeReport) return;
|
||||
if (!Array.isArray(content)) return;
|
||||
for (const block of content) {
|
||||
if (
|
||||
block?.type !== "tool_result" ||
|
||||
block.is_error === true ||
|
||||
typeof block.tool_use_id !== "string"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const toolUse = toolUses[block.tool_use_id];
|
||||
if (!toolUse || !FILE_EDIT_TOOLS.has(toolUse.name)) continue;
|
||||
const input = toolUse.input as Record<string, unknown> | undefined;
|
||||
const editedPath = toolUse.name === "NotebookEdit" ? input?.notebook_path : input?.file_path;
|
||||
if (typeof editedPath === "string" && editedPath.length > 0) {
|
||||
(turn.editedPaths ??= new Set()).add(editedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createNativeFileChangeReporter(
|
||||
options: NativeFileChangeReporterOptions,
|
||||
): NativeFileChangeReporter {
|
||||
@@ -101,7 +140,7 @@ export function createNativeFileChangeReporter(
|
||||
const timeoutMs = options.timeoutMs ?? AGENT_FILE_CHANGE_REPORT_TIMEOUT_MS;
|
||||
const requestIds = new Set<string>();
|
||||
|
||||
const publish = async (state: FileChangeReportTurnState, result: AgentFileChangeReportResult) => {
|
||||
async function publish(state: FileChangeReportTurnState, result: AgentFileChangeReportResult) {
|
||||
if (state.phase === "finished") return;
|
||||
state.phase = "finished";
|
||||
try {
|
||||
@@ -109,24 +148,55 @@ export function createNativeFileChangeReporter(
|
||||
} catch (error) {
|
||||
options.logError(`Failed to publish file-change report ${state.requestId}: ${error}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const finishUnavailable = async (
|
||||
async function finishUnavailable(
|
||||
state: FileChangeReportTurnState,
|
||||
reason: FileChangeReportUnavailableReason,
|
||||
) => {
|
||||
) {
|
||||
await publish(state, {
|
||||
version: 1,
|
||||
requestId: state.requestId,
|
||||
status: "unavailable",
|
||||
reason,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const report = async (
|
||||
async function publishReported(state: FileChangeReportTurnState, reportedPaths: string[]) {
|
||||
const normalized = normalizeReportedPaths(reportedPaths, workspace);
|
||||
await publish(
|
||||
state,
|
||||
fitReportedAgentFileChangeReport({
|
||||
version: 1,
|
||||
requestId: state.requestId,
|
||||
status: "reported",
|
||||
paths: normalized.paths,
|
||||
// Checkpoints miss Bash mutations. Subagent file-tool edits are added
|
||||
// from the message stream, but the list is still not exhaustive.
|
||||
declaredComplete: false,
|
||||
truncated: normalized.truncated,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Used when the checkpoint preview fails: the subagent edits seen in the
|
||||
// stream are still worth reporting.
|
||||
async function finishWithoutPreview(
|
||||
state: FileChangeReportTurnState,
|
||||
editedPaths: Set<string> | undefined,
|
||||
reason: FileChangeReportUnavailableReason,
|
||||
) {
|
||||
if (editedPaths && editedPaths.size > 0) {
|
||||
await publishReported(state, [...editedPaths]);
|
||||
} else {
|
||||
await finishUnavailable(state, reason);
|
||||
}
|
||||
}
|
||||
|
||||
async function report(
|
||||
turn: NativeFileChangeReportTurn | null | undefined,
|
||||
query: Pick<Query, "rewindFiles">,
|
||||
) => {
|
||||
) {
|
||||
const state = turn?.fileChangeReport;
|
||||
if (!state || state.phase !== "requested") return;
|
||||
state.phase = "collecting";
|
||||
@@ -145,35 +215,22 @@ export function createNativeFileChangeReporter(
|
||||
}),
|
||||
]);
|
||||
if (preview === null) {
|
||||
await finishUnavailable(state, "timeout");
|
||||
await finishWithoutPreview(state, turn.editedPaths, "timeout");
|
||||
return;
|
||||
}
|
||||
if (!preview.canRewind || !Array.isArray(preview.filesChanged)) {
|
||||
await finishUnavailable(state, "invalidOutput");
|
||||
await finishWithoutPreview(state, turn.editedPaths, "invalidOutput");
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizeReportedPaths(preview.filesChanged, workspace);
|
||||
await publish(
|
||||
state,
|
||||
fitReportedAgentFileChangeReport({
|
||||
version: 1,
|
||||
requestId: state.requestId,
|
||||
status: "reported",
|
||||
paths: normalized.paths,
|
||||
// Checkpoints cover Claude file tools, but not every mutation source
|
||||
// (notably Bash and most subagents), so this list is never exhaustive.
|
||||
declaredComplete: false,
|
||||
truncated: normalized.truncated,
|
||||
}),
|
||||
);
|
||||
await publishReported(state, [...preview.filesChanged, ...(turn.editedPaths ?? [])]);
|
||||
} catch (error) {
|
||||
options.logError(`Failed to inspect Claude file checkpoint ${turn.promptUuid}: ${error}`);
|
||||
await finishUnavailable(state, "providerError");
|
||||
await finishWithoutPreview(state, turn.editedPaths, "providerError");
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
request(meta) {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ForkSessionRequest, ForkSessionResponse, RequestError } from "@agentclientprotocol/sdk";
|
||||
import {
|
||||
ForkSessionRequest,
|
||||
ForkSessionResponse,
|
||||
RequestError,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
forkSession as forkClaudeSession,
|
||||
getSessionMessages,
|
||||
|
||||
223
src/gateway-models.ts
Normal file
223
src/gateway-models.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Model discovery for a gateway set via `providers/set`.
|
||||
*
|
||||
* An organization can restrict which models its gateway serves, and the
|
||||
* gateway's listing returns only those: `GET /v1/models` for an `anthropic`
|
||||
* gateway, `GET /foundation-models` (Bedrock `ListFoundationModels`) for a
|
||||
* `bedrock` one. The adapter lists them once when the provider is set, and the
|
||||
* model picker then offers only the SDK models the gateway listed (see
|
||||
* `narrowToGatewayModels` in `session-model.ts`). A listing that fails fails
|
||||
* `providers/set` with a {@link GatewayListingError}.
|
||||
*/
|
||||
|
||||
const LISTING_TIMEOUT_MS = 10_000;
|
||||
const ANTHROPIC_PAGE_LIMIT = "1000";
|
||||
const MAX_LISTING_PAGES = 20;
|
||||
const MAX_LOGGED_BODY_CHARS = 500;
|
||||
const ANTHROPIC_VERSION = "2023-06-01";
|
||||
|
||||
/**
|
||||
* The bearer token `createEnvForProvider` sets as `ANTHROPIC_AUTH_TOKEN` and
|
||||
* `AWS_BEARER_TOKEN_BEDROCK`, so the CLI sends `Authorization: Bearer acp-proxy`
|
||||
* unless the provider headers replace it. The listing sends the same, so the
|
||||
* gateway sees the same auth as on chat requests.
|
||||
*/
|
||||
export const PROVIDER_AUTH_TOKEN_PLACEHOLDER = "acp-proxy";
|
||||
|
||||
/**
|
||||
* The cross-region inference profile prefixes the CLI knows:
|
||||
* `eu.anthropic.claude-sonnet-5`. `ANTHROPIC_BEDROCK_REGION_PREFIX` accepts all
|
||||
* of them but `us-gov`, which the CLI takes only from a `us-gov-*` AWS region;
|
||||
* a gateway listing only `us-gov.*` ids would then get an empty picker. That
|
||||
* case is theoretical, so it is left as is.
|
||||
*/
|
||||
const BEDROCK_REGION_PREFIXES = ["us", "eu", "apac", "jp", "au", "us-gov", "global"];
|
||||
|
||||
type ListingLogger = { log: (...args: any[]) => void; error: (...args: any[]) => void };
|
||||
|
||||
type ListingPage = { ids: string[]; nextQuery?: Record<string, string> };
|
||||
|
||||
/**
|
||||
* A gateway listing that failed: a network error, a status other than 2xx or
|
||||
* 4xx, a body that is not the expected listing, or the 10 s timeout. The
|
||||
* message names the request path and the reason, never a header value.
|
||||
*/
|
||||
export class GatewayListingError extends Error {
|
||||
override name = "GatewayListingError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the model ids an `anthropic` gateway at `baseUrl` serves, following
|
||||
* `has_more` / `last_id` pagination. Returns `undefined` when the gateway
|
||||
* answers 4xx: it has no listing, and nothing is narrowed. Throws a
|
||||
* {@link GatewayListingError} when the listing fails. Header values are never
|
||||
* logged.
|
||||
*/
|
||||
export async function listGatewayModels(
|
||||
baseUrl: string,
|
||||
providerHeaders: Record<string, string>,
|
||||
logger: ListingLogger,
|
||||
): Promise<string[] | undefined> {
|
||||
return listPages(
|
||||
`${baseUrl.replace(/\/+$/, "")}/v1/models`,
|
||||
{ limit: ANTHROPIC_PAGE_LIMIT },
|
||||
listingHeaders(providerHeaders, { "anthropic-version": ANTHROPIC_VERSION }),
|
||||
logger,
|
||||
(body) => {
|
||||
const page = body as { data?: unknown; has_more?: unknown; last_id?: unknown };
|
||||
if (!Array.isArray(page.data)) return undefined;
|
||||
return {
|
||||
ids: stringField(page.data, "id"),
|
||||
nextQuery:
|
||||
page.has_more === true && typeof page.last_id === "string"
|
||||
? { limit: ANTHROPIC_PAGE_LIMIT, after_id: page.last_id }
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the Anthropic models a `bedrock` gateway at `baseUrl` serves
|
||||
* (`GET /foundation-models`, Bedrock `ListFoundationModels`, one page), the
|
||||
* listing AI Router answers on its Bedrock route. Returns the listed ids, such
|
||||
* as `global.anthropic.claude-sonnet-5-5`; `undefined` and the errors are
|
||||
* those of {@link listGatewayModels}.
|
||||
*/
|
||||
export async function listBedrockModels(
|
||||
baseUrl: string,
|
||||
providerHeaders: Record<string, string>,
|
||||
logger: ListingLogger,
|
||||
): Promise<string[] | undefined> {
|
||||
const ids = await listPages(
|
||||
`${baseUrl.replace(/\/+$/, "")}/foundation-models`,
|
||||
{},
|
||||
listingHeaders(providerHeaders, {}),
|
||||
logger,
|
||||
(body) => {
|
||||
const page = body as { modelSummaries?: unknown };
|
||||
if (!Array.isArray(page.modelSummaries)) return undefined;
|
||||
return { ids: stringField(page.modelSummaries, "modelId") };
|
||||
},
|
||||
);
|
||||
return ids?.filter((id) => id.includes("anthropic."));
|
||||
}
|
||||
|
||||
/** The cross-region prefix of a Bedrock model id or ARN, if it has one. */
|
||||
export function bedrockRegionPrefix(modelId: string): string | undefined {
|
||||
const id = modelId.slice(modelId.lastIndexOf("/") + 1).toLowerCase();
|
||||
return BEDROCK_REGION_PREFIXES.find((prefix) => id.startsWith(`${prefix}.anthropic.`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks the region of the listed Bedrock models: `global` when the gateway
|
||||
* lists it, else the prefix of the first id that has one. A gateway that lists
|
||||
* `eu.*` and `us.*` ids gets the prefix that comes first, and the ids of the
|
||||
* other prefixes are dropped, so the picker offers only models the CLI can
|
||||
* call under that prefix. Ids without a prefix (`anthropic.claude-…`) are
|
||||
* dropped too once a prefix is picked; without any prefix every id is kept.
|
||||
*/
|
||||
export function selectBedrockRegion(modelIds: readonly string[]): {
|
||||
regionPrefix?: string;
|
||||
modelIds: string[];
|
||||
} {
|
||||
const prefixes = modelIds.map(bedrockRegionPrefix);
|
||||
const regionPrefix = prefixes.includes("global")
|
||||
? "global"
|
||||
: prefixes.find((prefix) => prefix !== undefined);
|
||||
if (!regionPrefix) return { modelIds: [...modelIds] };
|
||||
return {
|
||||
regionPrefix,
|
||||
modelIds: modelIds.filter((id) => bedrockRegionPrefix(id) === regionPrefix),
|
||||
};
|
||||
}
|
||||
|
||||
// Provider headers win over the defaults, matched case-insensitively, the way
|
||||
// `ANTHROPIC_CUSTOM_HEADERS` overrides the CLI's own headers.
|
||||
function listingHeaders(
|
||||
providerHeaders: Record<string, string>,
|
||||
protocolHeaders: Record<string, string>,
|
||||
): Headers {
|
||||
const headers = new Headers({
|
||||
authorization: `Bearer ${PROVIDER_AUTH_TOKEN_PLACEHOLDER}`,
|
||||
accept: "application/json",
|
||||
...protocolHeaders,
|
||||
});
|
||||
for (const [name, value] of Object.entries(providerHeaders)) {
|
||||
// `Headers.set` puts an invalid value into its error message, so name only the header.
|
||||
try {
|
||||
headers.set(name, value);
|
||||
} catch {
|
||||
throw new GatewayListingError(`Model listing cannot send the invalid header "${name}"`);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
function stringField(entries: unknown[], field: string): string[] {
|
||||
return entries.flatMap((entry) => {
|
||||
const value = (entry as Record<string, unknown> | null)?.[field];
|
||||
return typeof value === "string" && value.trim() !== "" ? [value] : [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads `endpoint` page by page; `parse` returns `undefined` for a body it does
|
||||
* not understand. A 4xx answer on any page means the gateway has no listing:
|
||||
* the result is `undefined`, and nothing is narrowed.
|
||||
*/
|
||||
async function listPages(
|
||||
endpoint: string,
|
||||
firstQuery: Record<string, string>,
|
||||
headers: Headers,
|
||||
logger: ListingLogger,
|
||||
parse: (body: unknown) => ListingPage | undefined,
|
||||
): Promise<string[] | undefined> {
|
||||
const { pathname: path, username, password } = new URL(endpoint);
|
||||
const failure = (reason: string): GatewayListingError => {
|
||||
const error = new GatewayListingError(`Model listing GET ${path} ${reason}`);
|
||||
logger.error(`[providers/set] ${error.message}`);
|
||||
return error;
|
||||
};
|
||||
// fetch refuses such a URL and repeats it, credentials included, in its error.
|
||||
if (username || password) throw failure("cannot be sent: the base URL carries credentials");
|
||||
const signal = AbortSignal.timeout(LISTING_TIMEOUT_MS);
|
||||
const ids: string[] = [];
|
||||
let query: Record<string, string> | undefined = firstQuery;
|
||||
for (let page = 0; page < MAX_LISTING_PAGES && query; page++) {
|
||||
const url = new URL(endpoint);
|
||||
for (const [name, value] of Object.entries(query)) url.searchParams.set(name, value);
|
||||
let response: Response;
|
||||
let body: string;
|
||||
try {
|
||||
response = await fetch(url, { headers, signal });
|
||||
body = await response.text();
|
||||
} catch (error) {
|
||||
throw failure(`failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
if (response.status >= 400 && response.status < 500) {
|
||||
logger.log(
|
||||
`[providers/set] Model listing GET ${path} returned ${response.status}: the gateway has no listing, models are not narrowed`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw failure(`returned ${response.status}: ${body.slice(0, MAX_LOGGED_BODY_CHARS)}`);
|
||||
}
|
||||
let parsed: ListingPage | undefined;
|
||||
try {
|
||||
parsed = parse(JSON.parse(body));
|
||||
} catch {
|
||||
parsed = undefined;
|
||||
}
|
||||
if (!parsed) throw failure("returned an unexpected body");
|
||||
ids.push(...parsed.ids);
|
||||
query = parsed.nextQuery;
|
||||
}
|
||||
if (query) {
|
||||
logger.error(
|
||||
`[providers/set] Model listing GET ${path} still had more pages after ${MAX_LISTING_PAGES}; using what was read`,
|
||||
);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequestError } from "@agentclientprotocol/sdk";
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { SDKActiveGoalMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
export const GOAL_EXTENSION_VERSION = 1 as const;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
* until the next prompt. Awaiting it would add a CLI run to every turn.
|
||||
*/
|
||||
|
||||
import { RequestError } from "@agentclientprotocol/sdk";
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AccountInfo, Query } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
export function shouldHideClaudeAuth(): boolean {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { claudeCliPath } from "./acp-agent.js";
|
||||
import { applyManagedPolicyEnv } from "./managed-policy.js";
|
||||
import { serveAcp } from "./serve.js";
|
||||
import { acpProtocolsFromEnv, serveAcp } from "./serve.js";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import { appendFileSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
@@ -83,9 +83,8 @@ if (process.argv.includes("--cli")) {
|
||||
: undefined;
|
||||
logger?.log("Claude ACP started");
|
||||
|
||||
// The draft ACP v2 is opt-in while it is a draft.
|
||||
const acp = serveAcp(logger, {
|
||||
experimentalV2: process.env.CLAUDE_AGENT_ACP_EXPERIMENTAL_V2 === "1",
|
||||
protocols: acpProtocolsFromEnv(process.env.CLAUDE_AGENT_ACP_PROTOCOL),
|
||||
});
|
||||
|
||||
async function shutdown() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AvailableCommand } from "@agentclientprotocol/sdk";
|
||||
import type { AvailableCommand } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { McpServerStatus, Query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { escapeMarkdown } from "./usage-markdown.js";
|
||||
import { raceTimeoutAndAbort } from "./utils.js";
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { RequestError, type SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import { RequestError, type SessionNotification } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AcpSessionNotification, SubagentForm, SubagentState } from "./acp-subagents.js";
|
||||
import { AIR_SUBAGENT_KEY, airExtensionMeta } from "./air-extension.js";
|
||||
import { isFinalToolCallStatus } from "./tool-calls/renderer.js";
|
||||
|
||||
/** The work state of a child, as the RFD's `subagent_update.state` carries it. */
|
||||
type WorkState = NonNullable<
|
||||
export type WorkState = NonNullable<
|
||||
Extract<SessionNotification["update"], { sessionUpdate: "subagent_update" }>["state"]
|
||||
>;
|
||||
|
||||
@@ -37,10 +38,15 @@ export type NativeSubagent = {
|
||||
missingError?: boolean;
|
||||
/** The RFD form: the open permission and question requests of the child. */
|
||||
openRequests?: number;
|
||||
/** The RFD form: how many times the client cancelled the child (see
|
||||
* `NativeSubagentRuntime.cancelChild`). */
|
||||
cancels?: number;
|
||||
/** The RFD form: the ids of the prompt messages in the child's transcript. */
|
||||
promptIds?: Set<string>;
|
||||
/** The RFD form: the calls that delegated to the child, whose runs ended. */
|
||||
endedRuns?: Set<string>;
|
||||
/** The RFD form: the calls that delegated to the child, whose runs handed back a report. */
|
||||
handedBack?: Set<string>;
|
||||
announced?: boolean;
|
||||
terminalState?: SubagentState;
|
||||
/** Connection-local single-flight state; never serialized on the wire. */
|
||||
@@ -86,6 +92,31 @@ type SubagentIdentity = {
|
||||
subagentType?: string;
|
||||
};
|
||||
|
||||
/** An agent of the session's subagent history, as the runtime restores it. */
|
||||
export type HistoricalSubagent = {
|
||||
agentId: string;
|
||||
/** The Agent or Task call that launched it. */
|
||||
launchId: string;
|
||||
/** The agent that made that call. None means the root session. */
|
||||
parentAgentId?: string;
|
||||
name?: string;
|
||||
agentType?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The agent `agentId` and its ancestors, root first, from the session's
|
||||
* subagent history; empty when the history cannot place it (see
|
||||
* `subagentLineage`).
|
||||
*/
|
||||
export type SubagentLineage = (agentId: string) => Promise<readonly HistoricalSubagent[]>;
|
||||
|
||||
/**
|
||||
* The agent that the Agent or Task call `launchId` launched, from the session's
|
||||
* subagent history; undefined when the history has no such call.
|
||||
*/
|
||||
export type LaunchedAgent = (launchId: string) => Promise<string | undefined>;
|
||||
|
||||
const MAX_PENDING_PARENTS = 64;
|
||||
const MAX_PENDING_UPDATES = 256;
|
||||
const MAX_PENDING_UPDATES_PER_PARENT = 32;
|
||||
@@ -114,19 +145,54 @@ export class NativeSubagentRuntime {
|
||||
*/
|
||||
private readonly childByToolCall = new Map<string, NativeSubagent>();
|
||||
private readonly taskFinishPromises = new Map<string, Promise<void>>();
|
||||
/** The RFD form: the session that made each SendMessage call. */
|
||||
/** The session that made each SendMessage call. */
|
||||
private readonly senderByToolUse = new Map<string, string>();
|
||||
/**
|
||||
* The RFD form: the Agent/Task calls that the client is not shown, until
|
||||
* their final update. Kept by id because the adapter's own report that
|
||||
* closes an unfinished call has no `toolName` to recognize it by.
|
||||
*/
|
||||
private readonly hiddenLaunches = new Map<string, { background: boolean }>();
|
||||
/**
|
||||
* The RFD form: the SubagentHandback calls of announced children that the
|
||||
* client is not shown yet, with their first report, the report from their
|
||||
* input, and the call that delegated the run.
|
||||
*/
|
||||
private readonly handbacks = new Map<
|
||||
string,
|
||||
{ initial: AcpSessionNotification; message?: string; delegationId?: string }
|
||||
>();
|
||||
private readonly generationByTaskId = new Map<string, number>();
|
||||
private readonly pending = new Map<string, AcpSessionNotification[]>();
|
||||
private pendingCount = 0;
|
||||
/** The AIR form: agents being restored from the history, by agent id. */
|
||||
private readonly agentRestores = new Map<string, Promise<void>>();
|
||||
/** The AIR form: launch ids being resolved to an agent, by launch id. */
|
||||
private readonly launchRestores = new Map<string, Promise<void>>();
|
||||
/** The AIR form: launch ids that the history has no restorable agent for. */
|
||||
private readonly unresolvedLaunches = new Set<string>();
|
||||
/** The AIR form: agents that the history could not place, so it is read once. */
|
||||
private readonly unrestorableAgents = new Set<string>();
|
||||
/** The latest generation number given to a restored agent. */
|
||||
private lastRestoredGeneration = 0;
|
||||
/** Bumped when the restore state is dropped; a restore that began before it is void. */
|
||||
private restoreEpoch = 0;
|
||||
/** The RFD form: how many times the parent was cancelled (see {@link awaitingUser}). */
|
||||
private parentCancels = 0;
|
||||
|
||||
/** `form` is undefined for a client without native subagent sessions. */
|
||||
/**
|
||||
* `form` is undefined for a client without native subagent sessions. The RFD
|
||||
* form restores an agent from before a load or resume with `lineage`. The AIR
|
||||
* form does too, and `launchedAgent` finds the agent that a launch call made.
|
||||
*/
|
||||
constructor(
|
||||
form: SubagentForm | undefined,
|
||||
private readonly rootSessionId: string,
|
||||
private readonly session: NativeSubagentSession,
|
||||
private readonly publish: Publish,
|
||||
private readonly logger: Logger,
|
||||
private readonly lineage?: SubagentLineage,
|
||||
private readonly launchedAgent?: LaunchedAgent,
|
||||
) {
|
||||
this.form = form;
|
||||
this.enabled = form !== undefined;
|
||||
@@ -146,7 +212,7 @@ export class NativeSubagentRuntime {
|
||||
forcedSessionId?: string,
|
||||
): Promise<AcpSessionNotification | null> {
|
||||
const routed = await this.routeUpdate(notification, deliver, forcedSessionId);
|
||||
if (routed && this.form === "rfd") this.rememberSendMessage(routed);
|
||||
if (routed) this.rememberSendMessage(routed);
|
||||
return routed;
|
||||
}
|
||||
|
||||
@@ -158,7 +224,7 @@ export class NativeSubagentRuntime {
|
||||
const { update } = notification;
|
||||
const claudeMeta = update._meta?.claudeCode as
|
||||
{ parentToolUseId?: string | null; toolName?: string } | undefined;
|
||||
const isControl = isNativeSubagentControlUpdate(update);
|
||||
const isControl = this.isControlUpdate(update);
|
||||
|
||||
if (!this.enabled) return notification;
|
||||
|
||||
@@ -205,7 +271,7 @@ export class NativeSubagentRuntime {
|
||||
if (forcedSessionId) {
|
||||
return { ...notification, sessionId: forcedSessionId };
|
||||
}
|
||||
return failedControlFallback(
|
||||
return unexposedControlFallback(
|
||||
initial,
|
||||
notification,
|
||||
parentSessionId ?? this.rootSessionId,
|
||||
@@ -213,8 +279,19 @@ export class NativeSubagentRuntime {
|
||||
}
|
||||
return forcedSessionId ? { ...notification, sessionId: forcedSessionId } : null;
|
||||
}
|
||||
// The RFD form: the Agent/Task tool call is the parent's record of the
|
||||
// delegation, so it stays a tool call of the session that made it.
|
||||
// The RFD form: the parent's session messages replace the Agent/Task
|
||||
// tool call, its prompt as the parent's outgoing message (see
|
||||
// `linkedSessionMessages`) and its result as the child's reply (see
|
||||
// `launchReply`). A call made inside a child that is not announced yet
|
||||
// is buffered below, as any update of that child.
|
||||
if (!parentPending) {
|
||||
return this.routeRfdLaunch(
|
||||
notification,
|
||||
child,
|
||||
parentSessionId ?? this.rootSessionId,
|
||||
forcedSessionId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const toolCallId =
|
||||
@@ -233,12 +310,27 @@ export class NativeSubagentRuntime {
|
||||
}
|
||||
|
||||
const owner = toolCallId ? this.childByToolCall.get(toolCallId) : undefined;
|
||||
const announcedChild =
|
||||
owner ??
|
||||
(claudeMeta?.parentToolUseId
|
||||
? this.childByParentToolUse.get(claudeMeta.parentToolUseId)
|
||||
: undefined);
|
||||
if (this.form === "rfd" && announcedChild?.announced) {
|
||||
const handback = await this.routeRfdHandback(notification, announcedChild);
|
||||
if (handback !== undefined) return handback;
|
||||
}
|
||||
if (owner) return this.toChild(owner, notification, toolCallId);
|
||||
|
||||
if (this.enabled && claudeMeta?.parentToolUseId) {
|
||||
const child = this.childByParentToolUse.get(claudeMeta.parentToolUseId);
|
||||
if (!child || !child.announced) {
|
||||
this.buffer(claudeMeta.parentToolUseId, notification);
|
||||
const launchId = claudeMeta.parentToolUseId;
|
||||
this.buffer(launchId, notification);
|
||||
// An id that this process never saw is the launch call of an agent
|
||||
// from before a load or resume; a new Agent call is known by now.
|
||||
if (!child && this.form === "air" && this.isUnseenLaunch(launchId)) {
|
||||
await this.restoreLaunch(launchId, deliver);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return this.toChild(child, notification, toolCallId);
|
||||
@@ -249,7 +341,11 @@ export class NativeSubagentRuntime {
|
||||
|
||||
async taskStarted(task: TaskStarted, deliver: Publish): Promise<void> {
|
||||
if (!this.enabled) return;
|
||||
const known = this.form === "rfd" ? this.children.get(task.taskId) : undefined;
|
||||
const known =
|
||||
this.form === "rfd"
|
||||
? (this.children.get(task.taskId) ??
|
||||
(await this.restore(task.taskId, task.toolUseId ?? undefined, deliver)))
|
||||
: undefined;
|
||||
if (known) {
|
||||
// A child keeps its session for every delegation to it. The SDK starts
|
||||
// a resumed agent again under the id of the call that resumed it.
|
||||
@@ -262,6 +358,24 @@ export class NativeSubagentRuntime {
|
||||
);
|
||||
return;
|
||||
}
|
||||
// The SDK starts an agent from before a restart under the id of the
|
||||
// SendMessage call that resumed it, as in the RFD form (see `restore`).
|
||||
// Its output comes under the id of the call that launched it.
|
||||
if (
|
||||
this.form === "air" &&
|
||||
task.subagentType &&
|
||||
task.toolUseId &&
|
||||
this.senderByToolUse.has(task.toolUseId) &&
|
||||
!this.children.has(task.taskId)
|
||||
) {
|
||||
await this.restoreAgent(task.taskId, deliver, promptText(task.prompt));
|
||||
const restored = this.children.get(task.taskId);
|
||||
if (restored) {
|
||||
this.taskByToolUse.set(task.toolUseId, task.taskId);
|
||||
this.childByParentToolUse.set(task.toolUseId, restored);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!task.subagentType) {
|
||||
if (task.toolUseId) {
|
||||
this.takePending(task.toolUseId);
|
||||
@@ -318,11 +432,17 @@ export class NativeSubagentRuntime {
|
||||
* In the RFD form, the running patch only reports that the child runs: its
|
||||
* prompt is a guess (the latest SendMessage to the agent), and the
|
||||
* `task_started` or the SendMessage result carries the delegation.
|
||||
*
|
||||
* In the AIR form, an agent that the runtime does not know is restored from
|
||||
* the session's history (see {@link restoreAgent}).
|
||||
*/
|
||||
async taskResumed(taskId: string, deliver: Publish, prompt?: string): Promise<void> {
|
||||
if (!this.enabled) return;
|
||||
const previous = this.children.get(taskId);
|
||||
if (!previous) return;
|
||||
if (!previous) {
|
||||
if (this.form === "air") await this.restoreAgent(taskId, deliver, prompt);
|
||||
return;
|
||||
}
|
||||
const finishing = this.taskFinishPromises.get(taskId) ?? previous.terminalPromise;
|
||||
if (finishing) await finishing.catch(() => {});
|
||||
if (this.form === "rfd") {
|
||||
@@ -358,7 +478,7 @@ export class NativeSubagentRuntime {
|
||||
toolUseId?: string,
|
||||
): Promise<void> {
|
||||
if (this.form !== "rfd") return this.taskResumed(taskId, deliver, prompt);
|
||||
const child = this.children.get(taskId);
|
||||
const child = this.children.get(taskId) ?? (await this.restore(taskId, toolUseId, deliver));
|
||||
if (!child) return;
|
||||
const finishing = this.taskFinishPromises.get(taskId);
|
||||
if (finishing) await finishing.catch(() => {});
|
||||
@@ -476,6 +596,7 @@ export class NativeSubagentRuntime {
|
||||
this.identityByToolUse.clear();
|
||||
this.controlByToolUse.clear();
|
||||
this.parentByToolUse.clear();
|
||||
this.forgetRestores();
|
||||
}
|
||||
if (errors.length === 1) throw errors[0];
|
||||
if (errors.length > 1) throw new AggregateError(errors, "Failed to finish native subagents");
|
||||
@@ -488,20 +609,59 @@ export class NativeSubagentRuntime {
|
||||
* foreground children that the turn waits on and, currently, background
|
||||
* children too, and the SDK's task events report each as cancelled. A child
|
||||
* that keeps running keeps its open requests
|
||||
* (agentclientprotocol/agent-client-protocol#2308).
|
||||
* (agentclientprotocol/agent-client-protocol#2308). The RFD form only counts
|
||||
* the cancel, for {@link awaitingUser}.
|
||||
*/
|
||||
async parentCancelled(deliver: Publish): Promise<void> {
|
||||
if (this.form === "air") await this.finishAll("cancelled", deliver);
|
||||
else this.parentCancels++;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: the client sent `session/cancel` for the child session
|
||||
* `sessionId`, which the child's `cancel` capability permits (see
|
||||
* `announceNativeSubagent`). `stop` stops the child's task, as
|
||||
* `Query.stopTask` does: Claude Code stops the child's current work and the
|
||||
* foreground subagents that it waits on, and the parent's work goes on. The
|
||||
* SDK's task events report each stopped child `idle` with `cancelled` (see
|
||||
* {@link finishTask}). Accepting the stop is no evidence that the child
|
||||
* stopped, so nothing is reported here, and a child whose work already ended
|
||||
* keeps its outcome. Returns false when `sessionId` is not a child session
|
||||
* of this runtime. AIR's draft grants no `cancel`.
|
||||
*/
|
||||
async cancelChild(sessionId: string, stop: (taskId: string) => Promise<void>): Promise<boolean> {
|
||||
if (this.form !== "rfd") return false;
|
||||
for (const [taskId, child] of this.children) {
|
||||
if (child.sessionId !== sessionId || !child.announced) continue;
|
||||
child.cancels = (child.cancels ?? 0) + 1;
|
||||
await stop(taskId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `request`, a permission or question request of the session
|
||||
* `sessionId`. In the RFD form, a child waiting on it is `requires_action`
|
||||
* until its last open request ends.
|
||||
* until its last open request ends, and then `running` again.
|
||||
*
|
||||
* `signal` aborts when Claude Code withdraws the request, which is not an
|
||||
* answer. If the root session, the child, or an ancestor of the child was
|
||||
* cancelled while the request was open, Claude Code withdrew it as it
|
||||
* stopped the child, and the SDK's events report the child `idle` next. The
|
||||
* end of the request then reports no `running`, which would show a child
|
||||
* that is being stopped as working. Without a cancel, something else
|
||||
* answered the request in the user's place, such as a `PermissionRequest`
|
||||
* hook, and the child runs on, so the end reports `running`.
|
||||
*/
|
||||
async awaitingUser<T>(sessionId: string, request: () => Promise<T>): Promise<T> {
|
||||
async awaitingUser<T>(
|
||||
sessionId: string,
|
||||
request: () => Promise<T>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
const child = this.form === "rfd" ? this.childOfSession(sessionId) : undefined;
|
||||
if (!child?.announced) return request();
|
||||
const cancelsAtOpen = this.cancelsAbove(child);
|
||||
child.openRequests = (child.openRequests ?? 0) + 1;
|
||||
if (child.openRequests === 1 && child.workState === "running") {
|
||||
await this.reportState(child, { state: "requires_action" }).catch((error) =>
|
||||
@@ -512,7 +672,9 @@ export class NativeSubagentRuntime {
|
||||
return await request();
|
||||
} finally {
|
||||
child.openRequests = (child.openRequests ?? 1) - 1;
|
||||
if (child.openRequests === 0 && child.workState === "requires_action") {
|
||||
// Withdrawn after a cancel that came while the request was open.
|
||||
const stopped = signal?.aborted === true && this.cancelsAbove(child) !== cancelsAtOpen;
|
||||
if (child.openRequests === 0 && child.workState === "requires_action" && !stopped) {
|
||||
await this.reportState(child, { state: "running" }).catch((error) =>
|
||||
this.logger.log(`Failed to report that subagent ${child.sessionId} runs: ${error}`),
|
||||
);
|
||||
@@ -524,6 +686,180 @@ export class NativeSubagentRuntime {
|
||||
this.takePending(parentToolUseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: restores the agent `agentId` when the SendMessage call
|
||||
* `toolUseId` resumes it and the runtime does not know it, because it ran
|
||||
* before the session was loaded or resumed. A new launch is no resume: its
|
||||
* call is an Agent or Task call.
|
||||
*
|
||||
* The agent keeps its session id from before, its agent id. The session's
|
||||
* subagent history (see {@link SubagentLineage}) gives the call that
|
||||
* launched it and its ancestors. The launch call is its delegation again:
|
||||
* Claude Code sends the resumed run's messages under that call's id. Before the agent's traffic, the RFD requires its complete
|
||||
* ancestor chain, parent before child. So each ancestor that the client was
|
||||
* not told about in this connection is announced, with no work state since
|
||||
* nothing says that it runs; then the agent, as running. The RFD forbids
|
||||
* guessing a parent, so an agent that the history cannot place stays
|
||||
* unexposed, as before.
|
||||
*/
|
||||
private async restore(
|
||||
agentId: string,
|
||||
toolUseId: string | undefined,
|
||||
deliver: Publish,
|
||||
): Promise<NativeSubagent | undefined> {
|
||||
if (!this.lineage || !toolUseId || !this.senderByToolUse.has(toolUseId)) return undefined;
|
||||
const lineage = await this.lineage(agentId).catch((error: unknown) => {
|
||||
this.logger.log(
|
||||
`Session ${this.rootSessionId}: failed to read the history of subagent ${agentId}: ${error}`,
|
||||
);
|
||||
return [];
|
||||
});
|
||||
for (const entry of lineage) {
|
||||
let child = this.children.get(entry.agentId);
|
||||
if (!child) {
|
||||
child = {
|
||||
sessionId: entry.agentId,
|
||||
parentSessionId: entry.parentAgentId
|
||||
? (this.children.get(entry.parentAgentId)?.sessionId ?? entry.parentAgentId)
|
||||
: this.rootSessionId,
|
||||
parentToolUseId: entry.launchId,
|
||||
name: subagentDisplayName(entry.name, entry.description, entry.agentType, entry.agentId),
|
||||
task: subagentDescription(undefined, entry.description),
|
||||
...rfdLabels(entry.name, entry.agentType, entry.description),
|
||||
};
|
||||
this.children.set(entry.agentId, child);
|
||||
this.taskByToolUse.set(entry.launchId, entry.agentId);
|
||||
this.childByParentToolUse.set(entry.launchId, child);
|
||||
}
|
||||
if (child.announced) continue;
|
||||
if (entry.agentId === agentId) {
|
||||
await this.announce(child);
|
||||
} else {
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: {
|
||||
sessionUpdate: "subagent_update",
|
||||
sessionId: child.sessionId,
|
||||
...(child.title ? { title: child.title } : {}),
|
||||
...(child.description ? { description: child.description } : {}),
|
||||
},
|
||||
});
|
||||
child.announced = true;
|
||||
}
|
||||
for (const pending of this.takePending(entry.launchId)) await deliver(pending);
|
||||
}
|
||||
return this.children.get(agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The AIR form: whether no call of this process made `launchId`. A new Agent
|
||||
* call has a control update before its `task_started`, and a launch that was
|
||||
* restored or looked up is registered or remembered.
|
||||
*/
|
||||
private isUnseenLaunch(launchId: string): boolean {
|
||||
return (
|
||||
!!this.launchedAgent && !this.unresolvedLaunches.has(launchId) && this.isUnseenCall(launchId)
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether this runtime has no record of the tool call `toolUseId`. */
|
||||
private isUnseenCall(toolUseId: string): boolean {
|
||||
return (
|
||||
!this.taskByToolUse.has(toolUseId) &&
|
||||
!this.childByParentToolUse.has(toolUseId) &&
|
||||
!this.controlByToolUse.has(toolUseId) &&
|
||||
!this.parentByToolUse.has(toolUseId) &&
|
||||
!this.identityByToolUse.has(toolUseId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The AIR form: restores the agent that the call `launchId` launched, once.
|
||||
* Concurrent callers wait for the same lookup. Without an agent, the updates
|
||||
* buffered under `launchId` stay buffered and `launchId` is not looked up again.
|
||||
*/
|
||||
private restoreLaunch(launchId: string, deliver: Publish): Promise<void> {
|
||||
const existing = this.launchRestores.get(launchId);
|
||||
if (existing) return existing;
|
||||
const epoch = this.restoreEpoch;
|
||||
const restoring = (async () => {
|
||||
const agentId = await this.launchedAgent?.(launchId).catch((error: unknown) => {
|
||||
this.logger.log(
|
||||
`Session ${this.rootSessionId}: failed to read the history of launch ${launchId}: ${error}`,
|
||||
);
|
||||
return undefined;
|
||||
});
|
||||
if (epoch !== this.restoreEpoch) return;
|
||||
if (agentId) await this.restoreAgent(agentId, deliver);
|
||||
if (epoch !== this.restoreEpoch) return;
|
||||
const child = this.childByParentToolUse.get(launchId);
|
||||
if (!child) {
|
||||
this.unresolvedLaunches.add(launchId);
|
||||
return;
|
||||
}
|
||||
// Updates that arrived while the generation was announced.
|
||||
if (child.announced) for (const pending of this.takePending(launchId)) await deliver(pending);
|
||||
})().finally(() => {
|
||||
if (this.launchRestores.get(launchId) === restoring) this.launchRestores.delete(launchId);
|
||||
});
|
||||
this.launchRestores.set(launchId, restoring);
|
||||
return restoring;
|
||||
}
|
||||
|
||||
/**
|
||||
* The AIR form: opens a generation for the agent `agentId` that the runtime
|
||||
* does not know, because it ran before the session was loaded or resumed.
|
||||
* The session's subagent history (see {@link SubagentLineage}) gives the
|
||||
* call that launched it: Claude Code sends the resumed run's messages under
|
||||
* that call's id, so it is the generation's parent tool use, and the updates
|
||||
* buffered under it are delivered. The parent is the agent that made the call
|
||||
* if it is live, else the root. An agent that the history cannot place is
|
||||
* not restored. Concurrent signals for one agent open one generation.
|
||||
*/
|
||||
private restoreAgent(agentId: string, deliver: Publish, prompt?: string): Promise<void> {
|
||||
if (!this.lineage || this.children.has(agentId) || this.unrestorableAgents.has(agentId)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const existing = this.agentRestores.get(agentId);
|
||||
if (existing) return existing;
|
||||
const lineage = this.lineage;
|
||||
const epoch = this.restoreEpoch;
|
||||
const restoring = (async () => {
|
||||
const entries = await lineage(agentId).catch((error: unknown) => {
|
||||
this.logger.log(
|
||||
`Session ${this.rootSessionId}: failed to read the history of subagent ${agentId}: ${error}`,
|
||||
);
|
||||
return [];
|
||||
});
|
||||
if (epoch !== this.restoreEpoch || this.children.has(agentId)) return;
|
||||
const entry = entries.at(-1);
|
||||
if (!entry || entry.agentId !== agentId) {
|
||||
this.unrestorableAgents.add(agentId);
|
||||
return;
|
||||
}
|
||||
const parent = entry.parentAgentId ? this.children.get(entry.parentAgentId) : undefined;
|
||||
await this.openGeneration(
|
||||
agentId,
|
||||
undefined,
|
||||
{
|
||||
parentSessionId:
|
||||
parent && this.isLiveSession(parent.sessionId) ? parent.sessionId : this.rootSessionId,
|
||||
parentToolUseId: entry.launchId,
|
||||
name: subagentDisplayName(entry.name, entry.description, entry.agentType, agentId),
|
||||
task: subagentDescription(undefined, entry.description),
|
||||
...promptField(promptText(prompt)),
|
||||
},
|
||||
true,
|
||||
deliver,
|
||||
true,
|
||||
);
|
||||
})().finally(() => {
|
||||
if (this.agentRestores.get(agentId) === restoring) this.agentRestores.delete(agentId);
|
||||
});
|
||||
this.agentRestores.set(agentId, restoring);
|
||||
return restoring;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: the child runs again, and keeps its session. `toolUseId` is
|
||||
* the call that delegated, usually a SendMessage: the SDK runs the resumed
|
||||
@@ -675,6 +1011,109 @@ export class NativeSubagentRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: an update of the Agent/Task call that launches `child`,
|
||||
* which the parent's session messages replace. Only a launch whose child
|
||||
* was never exposed stays a tool call of the parent, with its final status,
|
||||
* since the RFD lets the Agent represent unexposed work through the parent.
|
||||
* A background launch completes before its child starts, so its completion
|
||||
* is not such a case.
|
||||
*/
|
||||
private async routeRfdLaunch(
|
||||
notification: AcpSessionNotification,
|
||||
child: NativeSubagent | undefined,
|
||||
parentSessionId: string,
|
||||
forcedSessionId: string | undefined,
|
||||
): Promise<AcpSessionNotification | null> {
|
||||
const { update } = notification;
|
||||
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
||||
return null;
|
||||
}
|
||||
const toolCallId = update.toolCallId;
|
||||
// A permission request already showed the call to the client, so it
|
||||
// keeps getting its updates.
|
||||
if (forcedSessionId) return { ...notification, sessionId: forcedSessionId };
|
||||
const launch = this.hiddenLaunches.get(toolCallId) ?? { background: false };
|
||||
if (isBackgroundLaunch(update.rawInput)) launch.background = true;
|
||||
if (!isFinalToolCallStatus(update.status)) {
|
||||
this.hiddenLaunches.set(toolCallId, launch);
|
||||
return null;
|
||||
}
|
||||
this.hiddenLaunches.delete(toolCallId);
|
||||
if (child?.announcePromise) await child.announcePromise;
|
||||
const background = launch.background;
|
||||
if (child?.announced) {
|
||||
const reply =
|
||||
update.status === "completed" && !background && !child.handedBack?.has(toolCallId)
|
||||
? launchReply(toolCallId, child.sessionId, child.parentSessionId, update)
|
||||
: undefined;
|
||||
if (reply) await this.publish(reply);
|
||||
return null;
|
||||
}
|
||||
if (background && update.status === "completed") return null;
|
||||
this.takePending(toolCallId);
|
||||
const initial = this.controlByToolUse.get(toolCallId);
|
||||
this.cleanupControl(toolCallId);
|
||||
return unexposedControlFallback(initial, notification, parentSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: an update of a SubagentHandback call of the announced
|
||||
* `child`, which its report replaces: the child's message to its parent,
|
||||
* see {@link linkedSessionMessages}. A failed call delivered nothing, so the
|
||||
* child is shown the call after all. `undefined` for any other update.
|
||||
*/
|
||||
private async routeRfdHandback(
|
||||
notification: AcpSessionNotification,
|
||||
child: NativeSubagent,
|
||||
): Promise<AcpSessionNotification | null | undefined> {
|
||||
const { update } = notification;
|
||||
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
||||
return undefined;
|
||||
}
|
||||
const claudeMeta = update._meta?.claudeCode as
|
||||
{ parentToolUseId?: string | null; toolName?: string } | undefined;
|
||||
let handback = this.handbacks.get(update.toolCallId);
|
||||
if (!handback) {
|
||||
if (claudeMeta?.toolName !== HANDBACK_TOOL) return undefined;
|
||||
handback = {
|
||||
initial: notification,
|
||||
delegationId: claudeMeta.parentToolUseId ?? child.parentToolUseId,
|
||||
};
|
||||
this.handbacks.set(update.toolCallId, handback);
|
||||
}
|
||||
handback.message = handbackMessage(update.rawInput) ?? handback.message;
|
||||
if (!isFinalToolCallStatus(update.status)) return null;
|
||||
this.handbacks.delete(update.toolCallId);
|
||||
if (update.status === "completed" && handback.message) {
|
||||
for (const view of linkedSessionMessages(
|
||||
update.toolCallId,
|
||||
child.sessionId,
|
||||
child.parentSessionId,
|
||||
handback.message,
|
||||
)) {
|
||||
await this.publish(view);
|
||||
}
|
||||
if (handback.delegationId) (child.handedBack ??= new Set()).add(handback.delegationId);
|
||||
return null;
|
||||
}
|
||||
return unexposedControlFallback(handback.initial, notification, child.sessionId);
|
||||
}
|
||||
|
||||
/** Whether `update` is of an Agent/Task call, by its tool name or, in the
|
||||
* RFD form, by the id of a call the client is not shown. */
|
||||
private isControlUpdate(
|
||||
update: AcpSessionNotification["update"],
|
||||
): update is Extract<
|
||||
AcpSessionNotification["update"],
|
||||
{ sessionUpdate: "tool_call" | "tool_call_update" }
|
||||
> {
|
||||
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
||||
return false;
|
||||
}
|
||||
return this.hiddenLaunches.has(update.toolCallId) || isNativeSubagentControlUpdate(update);
|
||||
}
|
||||
|
||||
private rememberToolCallOwner(toolCallId: string, child: NativeSubagent): void {
|
||||
this.childByToolCall.delete(toolCallId);
|
||||
this.childByToolCall.set(toolCallId, child);
|
||||
@@ -684,6 +1123,24 @@ export class NativeSubagentRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: how many cancels the client sent for `child`, its
|
||||
* ancestors, and the root session. Each cancel stops the foreground work
|
||||
* under it, and the counts only grow, so a changed sum shows that a cancel
|
||||
* that reaches the child came.
|
||||
*/
|
||||
private cancelsAbove(child: NativeSubagent): number {
|
||||
let cancels = this.parentCancels;
|
||||
const seen = new Set<NativeSubagent>();
|
||||
let next: NativeSubagent | undefined = child;
|
||||
while (next && !seen.has(next)) {
|
||||
seen.add(next);
|
||||
cancels += next.cancels ?? 0;
|
||||
next = this.childOfSession(next.parentSessionId);
|
||||
}
|
||||
return cancels;
|
||||
}
|
||||
|
||||
/** The child generation with the ACP session `sessionId`, if one exists. */
|
||||
private childOfSession(sessionId: string): NativeSubagent | undefined {
|
||||
if (sessionId === this.rootSessionId) return undefined;
|
||||
@@ -703,9 +1160,21 @@ export class NativeSubagentRuntime {
|
||||
this.childByParentToolUse.clear();
|
||||
this.taskFinishPromises.clear();
|
||||
this.senderByToolUse.clear();
|
||||
this.hiddenLaunches.clear();
|
||||
this.handbacks.clear();
|
||||
this.generationByTaskId.clear();
|
||||
this.pending.clear();
|
||||
this.pendingCount = 0;
|
||||
this.forgetRestores();
|
||||
}
|
||||
|
||||
/** Lets a later resume signal restore an agent again, and look up a launch again. */
|
||||
private forgetRestores(): void {
|
||||
this.agentRestores.clear();
|
||||
this.launchRestores.clear();
|
||||
this.unresolvedLaunches.clear();
|
||||
this.unrestorableAgents.clear();
|
||||
this.restoreEpoch++;
|
||||
}
|
||||
|
||||
private takePending(parentToolUseId: string): AcpSessionNotification[] {
|
||||
@@ -776,9 +1245,10 @@ export class NativeSubagentRuntime {
|
||||
>,
|
||||
announce: boolean,
|
||||
deliver: Publish,
|
||||
restored = false,
|
||||
): Promise<void> {
|
||||
const child: NativeSubagent = {
|
||||
sessionId: this.nextChildSessionId(taskId, previous),
|
||||
sessionId: this.nextChildSessionId(taskId, previous, restored),
|
||||
...fields,
|
||||
};
|
||||
const toolUseId = child.parentToolUseId;
|
||||
@@ -802,8 +1272,25 @@ export class NativeSubagentRuntime {
|
||||
* `subagents/` history, so a fork cannot resume an agent of the original.
|
||||
* If a fork ever can, the RFD requires remapping the copied ids, for
|
||||
* example by deriving the child id from the root session id.
|
||||
*
|
||||
* An agent restored from the history (`restored`) is not on its first
|
||||
* generation: before the restart the client already stored ids such as
|
||||
* `taskId` and `taskId:generation:2`. Its generation is the current time in
|
||||
* milliseconds, so it is `taskId:generation:1760000000000`, and each later
|
||||
* resume in this process adds one.
|
||||
*/
|
||||
private nextChildSessionId(taskId: string, previous: NativeSubagent | undefined): string {
|
||||
private nextChildSessionId(
|
||||
taskId: string,
|
||||
previous: NativeSubagent | undefined,
|
||||
restored = false,
|
||||
): string {
|
||||
if (restored) {
|
||||
// A clear() and a new restore within one millisecond must not repeat an id.
|
||||
const generation = Math.max(Date.now(), this.lastRestoredGeneration + 1);
|
||||
this.lastRestoredGeneration = generation;
|
||||
this.generationByTaskId.set(taskId, generation);
|
||||
return `${taskId}:generation:${generation}`;
|
||||
}
|
||||
if (!previous) {
|
||||
this.generationByTaskId.set(taskId, 1);
|
||||
return taskId;
|
||||
@@ -817,8 +1304,10 @@ export class NativeSubagentRuntime {
|
||||
/**
|
||||
* Announces the child on its parent, once, before any update of the child.
|
||||
* The RFD form announces it as running and then sends the prompt as the
|
||||
* first message of the child. The child gets no capabilities, so the client
|
||||
* may not cancel or otherwise change it.
|
||||
* first message of the child. It grants the child `cancel`: the client may
|
||||
* stop the child's current work with `session/cancel` (see
|
||||
* `NativeSubagentRuntime.cancelChild`), and nothing else. AIR's draft grants
|
||||
* no capabilities.
|
||||
*/
|
||||
export async function announceNativeSubagent(
|
||||
child: NativeSubagent,
|
||||
@@ -837,6 +1326,7 @@ export async function announceNativeSubagent(
|
||||
sessionId: child.sessionId,
|
||||
...(child.title ? { title: child.title } : {}),
|
||||
...(child.description ? { description: child.description } : {}),
|
||||
capabilities: { cancel: {} },
|
||||
state: { state: "running" },
|
||||
},
|
||||
});
|
||||
@@ -849,6 +1339,7 @@ export async function announceNativeSubagent(
|
||||
child.parentToolUseId,
|
||||
child.parentSessionId,
|
||||
publish,
|
||||
{ replacesLaunch: true },
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -909,6 +1400,10 @@ export async function finishNativeSubagent(
|
||||
* Agent/Task call or a SendMessage, so a prompt whose id is already in the
|
||||
* transcript is the same delegation and is not sent again. `sender` is the
|
||||
* session that made that call, when known.
|
||||
*
|
||||
* A launch's prompt (`replacesLaunch`) also goes to the parent, see
|
||||
* {@link linkedSessionMessages}. A SendMessage call stays a tool call of the
|
||||
* sender, so its message has only the incoming view.
|
||||
*/
|
||||
async function publishPrompt(
|
||||
child: NativeSubagent,
|
||||
@@ -916,11 +1411,18 @@ async function publishPrompt(
|
||||
toolUseId: string | undefined,
|
||||
sender: string | undefined,
|
||||
publish: Publish,
|
||||
{ replacesLaunch = false }: { replacesLaunch?: boolean } = {},
|
||||
): Promise<void> {
|
||||
child.promptIds ??= new Set();
|
||||
const messageId = toolUseId ?? `${child.sessionId}:prompt:${child.promptIds.size + 1}`;
|
||||
if (child.promptIds.has(messageId)) return;
|
||||
child.promptIds.add(messageId);
|
||||
if (replacesLaunch && sender !== undefined && toolUseId !== undefined) {
|
||||
for (const view of linkedSessionMessages(toolUseId, sender, child.sessionId, prompt)) {
|
||||
await publish(view);
|
||||
}
|
||||
return;
|
||||
}
|
||||
await publish({
|
||||
sessionId: child.sessionId,
|
||||
update: {
|
||||
@@ -933,6 +1435,114 @@ async function publishPrompt(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: a message that a tool call sent, which the client is not
|
||||
* shown, as two session messages: the sender's outgoing view, then the
|
||||
* recipient's incoming one. That call is a launch's Agent/Task call, whose
|
||||
* prompt goes from the parent to the child, or a child's SubagentHandback
|
||||
* call, whose report goes from the child to the parent. Both views use the
|
||||
* call's tool use id, and each names the other with the draft RFD's
|
||||
* `recipientMessageId` and `senderMessageId`.
|
||||
*/
|
||||
export function linkedSessionMessages(
|
||||
toolUseId: string,
|
||||
senderSessionId: string,
|
||||
recipientSessionId: string,
|
||||
text: string,
|
||||
): AcpSessionNotification[] {
|
||||
const message = {
|
||||
sessionUpdate: "session_message" as const,
|
||||
messageId: toolUseId,
|
||||
senderSessionId,
|
||||
recipientSessionId,
|
||||
content: [{ type: "text" as const, text }],
|
||||
};
|
||||
return [
|
||||
{
|
||||
sessionId: senderSessionId,
|
||||
update: { ...message, recipientMessageId: toolUseId } as AcpSessionNotification["update"],
|
||||
},
|
||||
{
|
||||
sessionId: recipientSessionId,
|
||||
update: { ...message, senderMessageId: toolUseId } as AcpSessionNotification["update"],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The tool with which a child hands its report to its parent, when Claude
|
||||
* Code runs in hand-back mode. The Agent/Task result then only says that the
|
||||
* report was delivered as a message, with `handback: "send"` in its
|
||||
* structured result.
|
||||
*/
|
||||
export const HANDBACK_TOOL = "SubagentHandback";
|
||||
|
||||
/** The report of a SubagentHandback call, from its input. */
|
||||
export function handbackMessage(input: unknown): string | undefined {
|
||||
const message = (input as { message?: unknown } | null | undefined)?.message;
|
||||
return typeof message === "string" && message.trim() ? message : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: the answer of a foreground child, from the completed result
|
||||
* of the Agent/Task call `launchId` that launched it, as the child's message
|
||||
* in the parent's transcript (its incoming view). It replaces that call's
|
||||
* result, which the parent is not shown. The child's transcript has its
|
||||
* answer as its own agent message, not as a session message, so this view
|
||||
* links to no other. `undefined` for a result without text. Not used for a
|
||||
* run whose child handed back its report: the result has no report then.
|
||||
*/
|
||||
export function launchReply(
|
||||
launchId: string,
|
||||
childSessionId: string,
|
||||
parentSessionId: string,
|
||||
result: Extract<
|
||||
AcpSessionNotification["update"],
|
||||
{ sessionUpdate: "tool_call_update" | "tool_call" }
|
||||
>,
|
||||
): AcpSessionNotification | undefined {
|
||||
const content = replyContent(result);
|
||||
if (content.length === 0) return undefined;
|
||||
return {
|
||||
sessionId: parentSessionId,
|
||||
update: {
|
||||
sessionUpdate: "session_message",
|
||||
messageId: `${launchId}:reply`,
|
||||
senderSessionId: childSessionId,
|
||||
recipientSessionId: parentSessionId,
|
||||
content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The text of a completed launch's result, from the tool call's `content`:
|
||||
* `AgentReporter` removed the parts meant for the model there (the
|
||||
* `agentId: … (use SendMessage …)` line, the `<usage>` block and the hand-back
|
||||
* frame), which `rawOutput` still has.
|
||||
*/
|
||||
function replyContent(
|
||||
result: Extract<
|
||||
AcpSessionNotification["update"],
|
||||
{ sessionUpdate: "tool_call_update" | "tool_call" }
|
||||
>,
|
||||
): Array<{ type: "text"; text: string }> {
|
||||
return (result.content ?? []).flatMap((item) =>
|
||||
item.type === "content" && item.content.type === "text" && item.content.text.trim()
|
||||
? [{ type: "text" as const, text: item.content.text }]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether an Agent/Task call's input runs its child in the background. */
|
||||
function isBackgroundLaunch(rawInput: unknown): boolean {
|
||||
return (
|
||||
typeof rawInput === "object" &&
|
||||
rawInput !== null &&
|
||||
(rawInput as { run_in_background?: unknown }).run_in_background === true
|
||||
);
|
||||
}
|
||||
|
||||
/** The RFD form: whether the child's foreground work is in progress. */
|
||||
function isWorking(child: NativeSubagent): boolean {
|
||||
return child.workState === "running" || child.workState === "requires_action";
|
||||
@@ -950,7 +1560,7 @@ function isWorking(child: NativeSubagent): boolean {
|
||||
* stream ended, is idle with no stop reason: its work ended, but the adapter
|
||||
* did not see a cancellation or a failure, which the RFD forbids claiming.
|
||||
*/
|
||||
function rfdEndState(state: SubagentState, end: TaskEnd | undefined): WorkState {
|
||||
export function rfdEndState(state: SubagentState, end: TaskEnd | undefined): WorkState {
|
||||
if (end?.unexplained || end?.reason === "worker_restart") return { state: "idle" };
|
||||
switch (state) {
|
||||
case "completed":
|
||||
@@ -976,7 +1586,7 @@ function rfdEndState(state: SubagentState, end: TaskEnd | undefined): WorkState
|
||||
* name, with its role after it when both are known, else the role. The
|
||||
* description is the task's short description, never the prompt.
|
||||
*/
|
||||
function rfdLabels(
|
||||
export function rfdLabels(
|
||||
name: unknown,
|
||||
role: unknown,
|
||||
description: unknown,
|
||||
@@ -1096,7 +1706,13 @@ function isFailedToolCallUpdate(update: AcpSessionNotification["update"]): boole
|
||||
);
|
||||
}
|
||||
|
||||
function failedControlFallback(
|
||||
/**
|
||||
* A tool call that the client was not shown, as one `tool_call` with its
|
||||
* final status, shown after all: an Agent/Task call whose child was never
|
||||
* exposed (AIR's draft shows only a failed one, the RFD form also a completed
|
||||
* one), or a failed SubagentHandback call.
|
||||
*/
|
||||
function unexposedControlFallback(
|
||||
initial: AcpSessionNotification | undefined,
|
||||
terminal: AcpSessionNotification,
|
||||
sessionId: string,
|
||||
@@ -1115,7 +1731,7 @@ function failedControlFallback(
|
||||
update: {
|
||||
...terminal.update,
|
||||
sessionUpdate: "tool_call",
|
||||
status: "failed",
|
||||
status: terminal.update.status ?? "failed",
|
||||
// The synthesized tool_call is this call's first report, so give it
|
||||
// the standard `name` the initial one would have carried.
|
||||
...(typeof claudeMeta?.toolName === "string" ? { name: claudeMeta.toolName } : {}),
|
||||
@@ -1136,7 +1752,7 @@ function failedControlFallback(
|
||||
...initial.update,
|
||||
...terminal.update,
|
||||
sessionUpdate: "tool_call",
|
||||
status: "failed",
|
||||
status: terminal.update.status ?? "failed",
|
||||
title:
|
||||
typeof terminal.update.title === "string" && terminal.update.title.trim().length > 0
|
||||
? terminal.update.title
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
|
||||
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk/unstable";
|
||||
import type {
|
||||
PermissionMode,
|
||||
PermissionResult,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
buildEditPermissionOptions,
|
||||
buildGlobPermissionOptions,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { DurablePermissionChangeSet } from "../normalization.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { DurablePermissionChangeSet } from "../normalization.js";
|
||||
|
||||
export const PERMISSION_OPTION_ID = {
|
||||
@@ -30,6 +30,9 @@ export interface PermissionOptionContext {
|
||||
/** The mode the session left when it entered plan mode. */
|
||||
prePlanMode?: string;
|
||||
contextUsedPercent?: number;
|
||||
/** False when no ACP turn can continue an accepted plan in a fresh
|
||||
* context. Then an ExitPlanMode request offers no clear-context option. */
|
||||
allowContextReset?: boolean;
|
||||
}
|
||||
|
||||
export function allowOnce(name = "Yes"): PermissionOption {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import path from "node:path";
|
||||
import type { DurablePermissionChangeSet } from "../normalization.js";
|
||||
import { plainString, type PermissionOptionContext, withOptionalUpdate } from "./shared.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
allowOnce,
|
||||
exactLocalAllowRule,
|
||||
@@ -70,7 +70,7 @@ export function buildExitPlanModePermissionOptions(
|
||||
: (["auto", "bypassPermissions"] as const)
|
||||
).filter((mode) => modes.has(mode));
|
||||
const preferredMode = elevatedModes[0] ?? "acceptEdits";
|
||||
if (plainString(context.input.plan)) {
|
||||
if (context.allowContextReset !== false && plainString(context.input.plan)) {
|
||||
const usage =
|
||||
context.contextUsedPercent === undefined ? "" : ` (${context.contextUsedPercent}% used)`;
|
||||
options.push(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RequestPermissionRequest, ToolCallContent } from "@agentclientprotocol/sdk";
|
||||
import type { RequestPermissionRequest, ToolCallContent } from "@agentclientprotocol/sdk/unstable";
|
||||
import { AIR_PERMISSION_KEY, withAirMeta } from "../air-extension.js";
|
||||
import { ClientCapabilities } from "../tool-calls/client-capabilities.js";
|
||||
import { AcpToolCallRenderer } from "../tool-calls/renderer.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk";
|
||||
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { PermissionOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { PermissionMode, PermissionResult } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { DurablePermissionChangeSet } from "./normalization.js";
|
||||
import { applyClaudePermissionSelection, parseClaudePermissionSelection } from "./effects.js";
|
||||
|
||||
44
src/prompt-suggestions.ts
Normal file
44
src/prompt-suggestions.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* The follow-up prompt the SDK predicts for the next turn.
|
||||
*
|
||||
* With `promptSuggestions` on, the CLI emits one `prompt_suggestion` message
|
||||
* after a turn's `result` holding the prompt it expects the user to send next.
|
||||
* ACP has no field for it, so it travels as
|
||||
* `session_info_update._meta.promptSuggestions`: a client that does not read
|
||||
* the key sees a session info update that says nothing and ignores it.
|
||||
*
|
||||
* AIR renders the first non-blank `suggestions[].text` as gray text in an empty
|
||||
* composer, where Tab fills the composer without sending.
|
||||
*
|
||||
* The CLI sends no suggestion on the first turn of a session, after an API
|
||||
* error, in plan mode, and at the account's usage limit, so most sessions have
|
||||
* turns that end with no prediction at all.
|
||||
*/
|
||||
|
||||
/** The `_meta` key the suggestion travels under. It names no vendor. */
|
||||
export const PROMPT_SUGGESTIONS_META_KEY = "promptSuggestions";
|
||||
|
||||
/** One predicted prompt, as the `_meta` value carries it. */
|
||||
export type PromptSuggestionsMetaValue = {
|
||||
suggestions: { text: string }[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The suggestion as it goes on the wire, or `null` for a suggestion that says
|
||||
* nothing: the CLI can predict whitespace, and an empty text would otherwise
|
||||
* reach the composer as a hint with no content.
|
||||
*/
|
||||
export function promptSuggestionText(raw: string | null | undefined): string | null {
|
||||
const text = raw?.trim();
|
||||
return text ? text : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `_meta` of the session info update that publishes `text`. `null` sends an
|
||||
* empty `suggestions` list, which clears the prompt the client holds — the turn
|
||||
* predicted nothing, so the composer must not keep the previous prediction.
|
||||
*/
|
||||
export function promptSuggestionsMeta(text: string | null): Record<string, unknown> {
|
||||
const value: PromptSuggestionsMetaValue = { suggestions: text === null ? [] : [{ text }] };
|
||||
return { [PROMPT_SUGGESTIONS_META_KEY]: value };
|
||||
}
|
||||
50
src/serve.ts
50
src/serve.ts
@@ -1,19 +1,32 @@
|
||||
import { ndJsonStream } from "@agentclientprotocol/sdk";
|
||||
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
||||
import { v1AgentApp, type ClaudeAcpAgent, type Logger } from "./acp-agent.js";
|
||||
import { nodeToWebReadable, nodeToWebWritable } from "./utils.js";
|
||||
import { v2AgentApp } from "./v2/agent.js";
|
||||
|
||||
/**
|
||||
* Serves ACP on stdio.
|
||||
*
|
||||
* With `experimentalV2`, the connection goes through {@link acpProtocolRouter}.
|
||||
* Without it, the adapter serves ACP v1 alone and never routes by protocol
|
||||
* version, exactly as before.
|
||||
* The ACP versions that the adapter serves: `v1` or `v2` alone, or both with
|
||||
* `auto`, where the client's `initialize` picks the version.
|
||||
*/
|
||||
export type AcpProtocols = "auto" | "v1" | "v2";
|
||||
|
||||
/**
|
||||
* Reads `CLAUDE_AGENT_ACP_PROTOCOL`: unset, empty, or `auto` serves both
|
||||
* versions, and `v1` or `v2` serves that version alone. Throws on any other
|
||||
* value, so that a typo such as `CLAUDE_AGENT_ACP_PROTOCOL=1` fails at startup
|
||||
* instead of silently serving both.
|
||||
*/
|
||||
export function acpProtocolsFromEnv(value: string | undefined): AcpProtocols {
|
||||
if (value === undefined || value === "") return "auto";
|
||||
if (value === "auto" || value === "v1" || value === "v2") return value;
|
||||
throw new Error(
|
||||
`CLAUDE_AGENT_ACP_PROTOCOL must be "auto", "v1", or "v2", not ${JSON.stringify(value)}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Serves ACP on stdio through {@link acpProtocolRouter}. */
|
||||
export function serveAcp(
|
||||
logger: Logger | undefined,
|
||||
options: { experimentalV2: boolean },
|
||||
options: { protocols: AcpProtocols },
|
||||
): { closed: Promise<void>; dispose(): Promise<void> } {
|
||||
const output = nodeToWebWritable(process.stdout);
|
||||
const input = nodeToWebReadable(process.stdin);
|
||||
@@ -23,9 +36,9 @@ export function serveAcp(
|
||||
const onAgent = (created: ClaudeAcpAgent) => {
|
||||
agent = created;
|
||||
};
|
||||
const { closed } = options.experimentalV2
|
||||
? acpProtocolRouter(logger, onAgent).connect(v2.ndJsonStream(output, input))
|
||||
: v1AgentApp(logger, onAgent).connect(ndJsonStream(output, input));
|
||||
const { closed } = acpProtocolRouter(logger, onAgent, options.protocols).connect(
|
||||
v2.ndJsonStream(output, input),
|
||||
);
|
||||
return {
|
||||
closed,
|
||||
dispose: async () => {
|
||||
@@ -36,16 +49,19 @@ export function serveAcp(
|
||||
|
||||
/**
|
||||
* Routes a connection to the protocol version that the client negotiates in
|
||||
* `initialize`: the experimental draft ACP v2 for a client that requests
|
||||
* version 2 or later, and ACP v1 otherwise. `onAgent` receives the agent of
|
||||
* the connection once the router has opened it.
|
||||
* `initialize`. With `auto`, a client that requests version 2 or later gets
|
||||
* the draft ACP v2, and every other client gets ACP v1. With `v1`, a v2
|
||||
* client is negotiated down to v1. With `v2`, the router rejects the
|
||||
* `initialize` of a v1 client as an unsupported protocol version. `onAgent`
|
||||
* receives the agent of the connection once the router has opened it.
|
||||
*/
|
||||
export function acpProtocolRouter(
|
||||
logger: Logger | undefined,
|
||||
onAgent: (agent: ClaudeAcpAgent) => void,
|
||||
protocols: AcpProtocols = "auto",
|
||||
): v2.AgentProtocolRouter {
|
||||
return v2
|
||||
.agentProtocolRouter()
|
||||
.withV1(v1AgentApp(logger, onAgent))
|
||||
.withV2(v2AgentApp(logger, onAgent));
|
||||
const router = v2.agentProtocolRouter();
|
||||
if (protocols !== "v2") router.withV1(v1AgentApp(logger, onAgent));
|
||||
if (protocols !== "v1") router.withV2(v2AgentApp(logger, onAgent));
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { EffortLevel, ModelInfo, Settings } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
|
||||
import { EFFORT_CONFIG_ID } from "./session-config-ids.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
getSessionMessages,
|
||||
type SDKAssistantMessageError,
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
* - an interactive CLI that is not driven by an SDK holds it, and its registry
|
||||
* status is newer than the transcript: the registry status (an idle one is
|
||||
* `error` after such a turn);
|
||||
* - a turn that ended with an open background task is `running`, also after
|
||||
* 10 minutes: Claude Code wakes the session again when the task ends. The
|
||||
* wait ends when the registry status is `idle`, because a task can end
|
||||
* without a transcript record (a stop in the task UI);
|
||||
* - otherwise the transcript tail: a finished turn is `idle` (`error` after
|
||||
* an API error), an unfinished turn written in the last 10 minutes is
|
||||
* `running`, and anything else has no known state.
|
||||
@@ -72,6 +76,14 @@ function usesRegistryStatus(record: LiveRecord, transcriptMtimeMs: number): bool
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the last turn ended while a background task of the session is
|
||||
* open. */
|
||||
function waitsForBackgroundTasks(facts: TranscriptFacts): boolean {
|
||||
if (facts.turnState !== "finished") return false;
|
||||
const openTasks = facts.backgroundTasks?.open ?? [];
|
||||
return openTasks.length > 0;
|
||||
}
|
||||
|
||||
function iso(ms: number | undefined): string | undefined {
|
||||
return ms === undefined || !Number.isFinite(ms) ? undefined : new Date(ms).toISOString();
|
||||
}
|
||||
@@ -103,6 +115,9 @@ export function deriveActivity(input: {
|
||||
// A busy or waiting CLI wins; an idle one shows how its turn ended.
|
||||
state = registryState(live.status);
|
||||
if (state === "idle" && failed) state = "error";
|
||||
} else if (waitsForBackgroundTasks(facts) && live.status !== "idle") {
|
||||
// Claude Code wakes the session again when an open task ends.
|
||||
state = "running";
|
||||
} else if (failed) {
|
||||
state = "error";
|
||||
} else if (facts.turnState === "finished") {
|
||||
|
||||
223
src/session-index/background-tasks.ts
Normal file
223
src/session-index/background-tasks.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* The background tasks of a session that did not end yet, from its transcript.
|
||||
*
|
||||
* A Claude Code session can start a background shell command or a background
|
||||
* sub-agent and end its turn at once. When the task ends, Claude Code wakes
|
||||
* the session with a `<task-notification>` input for a short turn. So a turn
|
||||
* end with an open task is no end of the work: the session list keeps such a
|
||||
* session `running` (see `activity.ts`).
|
||||
*
|
||||
* The records, as Claude Code 2.1.299 writes them, in the rules of AIR's
|
||||
* native Claude provider:
|
||||
*
|
||||
* - a tool result opens a task when its `toolUseResult` has a
|
||||
* `backgroundTaskId` (a shell command), or `isAsync: true` with an
|
||||
* `agentId` (a sub-agent);
|
||||
* - a task closes when its notification reaches the session: as a user
|
||||
* record, or as a `queue-operation` `remove` when a running turn takes it
|
||||
* in. A notification that waits in the queue (`enqueue`) keeps the task
|
||||
* open, because the session still runs a turn for it;
|
||||
* - only a notification with a `<status>` closes a task. A notification
|
||||
* without one comes while the task still runs: the check-in of a quiet
|
||||
* shell ("is still running after …", since Claude Code 2.1.303) or a
|
||||
* `Monitor` event;
|
||||
* - a `TaskStop` result closes the task of its `toolUseResult.task_id`:
|
||||
* Claude Code writes no notification for it. The result does not name its
|
||||
* tool, so its `tool_use_id` must match a `TaskStop` call. A `task_id` in
|
||||
* the result of another tool keeps the task open;
|
||||
* - a resumed CLI writes a `stopped` notification for each task of the
|
||||
* previous process;
|
||||
* - a sidechain record belongs to a sub-agent and opens or closes no task of
|
||||
* the session.
|
||||
*
|
||||
* A background shell that does not end, for example a dev server, keeps its
|
||||
* task open, so its session stays `running` while the CLI holds it.
|
||||
*
|
||||
* A task that a sub-agent starts is not seen. Its start record is only in the
|
||||
* transcript of the sub-agent, so the session can be `idle` while the
|
||||
* sub-agent waits for it.
|
||||
*/
|
||||
|
||||
import { createReadStream } from "node:fs";
|
||||
import { createInterface } from "node:readline";
|
||||
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../task-notifications.js";
|
||||
|
||||
export type BackgroundTasks = {
|
||||
/** The ids of the tasks that started and did not end, in start order. */
|
||||
open: string[];
|
||||
/** The `tool_use` ids of the `TaskStop` calls without a result. */
|
||||
stopCalls: string[];
|
||||
/** Whether the sets hold the records of the whole file. A set from the
|
||||
* tail window only does not know a task that started before it. */
|
||||
complete: boolean;
|
||||
};
|
||||
|
||||
/** The fields of a transcript record that bear on a task. */
|
||||
type TranscriptRecord = {
|
||||
type?: string;
|
||||
isSidechain?: boolean;
|
||||
/** Of a `queue-operation` record: `enqueue`, `remove`, ... */
|
||||
operation?: string;
|
||||
/** Of a `queue-operation` record: the queued input. */
|
||||
content?: string;
|
||||
message?: { content?: string | ContentBlock[] };
|
||||
toolUseResult?: ToolUseResult;
|
||||
};
|
||||
|
||||
type ContentBlock = {
|
||||
type?: string;
|
||||
text?: string;
|
||||
/** Of a `tool_use` block. */
|
||||
id?: string;
|
||||
name?: string;
|
||||
/** Of a `tool_result` block. */
|
||||
tool_use_id?: string;
|
||||
};
|
||||
|
||||
type ToolUseResult = {
|
||||
/** The task of a background shell command. */
|
||||
backgroundTaskId?: string;
|
||||
/** With `agentId`: an async sub-agent. */
|
||||
isAsync?: boolean;
|
||||
agentId?: string;
|
||||
/** The task that a `TaskStop` call stopped. Another tool can give one. */
|
||||
task_id?: string;
|
||||
};
|
||||
|
||||
const NOTIFICATION_TAG = `<${TASK_NOTIFICATION_TAG}>`;
|
||||
|
||||
/** Text that a line must hold to bear on a task, so that the other lines
|
||||
* are not parsed. */
|
||||
const TASK_MARKERS = ["backgroundTaskId", '"isAsync"', NOTIFICATION_TAG, "TaskStop", "task_id"];
|
||||
|
||||
function parseRecord(line: string): TranscriptRecord | undefined {
|
||||
try {
|
||||
return JSON.parse(line) as TranscriptRecord;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function blocksOf(record: TranscriptRecord): ContentBlock[] {
|
||||
const content = record.message?.content;
|
||||
return Array.isArray(content) ? content : [];
|
||||
}
|
||||
|
||||
/** The input of a user record: its string content, else its first text
|
||||
* block. */
|
||||
function inputTextOf(record: TranscriptRecord): string | undefined {
|
||||
const content = record.message?.content;
|
||||
if (typeof content === "string") return content;
|
||||
return blocksOf(record).find((block) => block.type === "text")?.text;
|
||||
}
|
||||
|
||||
/** The task that a `<task-notification>` input ends: only an input that
|
||||
* starts with the tag, after whitespace, as AIR's `TaskNotification` reads
|
||||
* it. Only a notification with a `<status>` ends a task (see
|
||||
* `taskNotificationsOf`). */
|
||||
export function endedTaskId(input: string | undefined): string | undefined {
|
||||
if (!input?.trimStart().startsWith(NOTIFICATION_TAG)) return undefined;
|
||||
return taskNotificationsOf(input)[0]?.task_id;
|
||||
}
|
||||
|
||||
/** The task that a tool result started: a background shell command, or an
|
||||
* async sub-agent. */
|
||||
function startedTaskId(record: TranscriptRecord): string | undefined {
|
||||
if (record.type !== "user") return undefined;
|
||||
const result = record.toolUseResult;
|
||||
if (result?.backgroundTaskId) return result.backgroundTaskId;
|
||||
if (result?.isAsync) return result.agentId;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** The task whose notification reached the session: as a user record, or
|
||||
* as a queue `remove` when a running turn takes it in. */
|
||||
function deliveredTaskId(record: TranscriptRecord): string | undefined {
|
||||
if (record.type === "user") return endedTaskId(inputTextOf(record));
|
||||
if (record.type === "queue-operation" && record.operation === "remove") {
|
||||
return endedTaskId(record.content);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Collects the tasks of the lines it is given, in file order. */
|
||||
class TaskTracker {
|
||||
private readonly open: Set<string>;
|
||||
private readonly stopCalls: Set<string>;
|
||||
|
||||
constructor(after?: BackgroundTasks) {
|
||||
this.open = new Set(after?.open);
|
||||
this.stopCalls = new Set(after?.stopCalls);
|
||||
}
|
||||
|
||||
add(line: string): void {
|
||||
const mayBearOnTask = TASK_MARKERS.some((marker) => line.includes(marker));
|
||||
if (!mayBearOnTask) return;
|
||||
const record = parseRecord(line);
|
||||
// A sidechain record belongs to a sub-agent, which receives the
|
||||
// notifications of its own tasks.
|
||||
if (!record || record.isSidechain) return;
|
||||
this.apply(record);
|
||||
}
|
||||
|
||||
result({ complete }: { complete: boolean }): BackgroundTasks {
|
||||
return { open: [...this.open], stopCalls: [...this.stopCalls], complete };
|
||||
}
|
||||
|
||||
private apply(record: TranscriptRecord): void {
|
||||
const started = startedTaskId(record);
|
||||
if (started) this.open.add(started);
|
||||
const delivered = deliveredTaskId(record);
|
||||
if (delivered) this.open.delete(delivered);
|
||||
this.applyTaskStop(record);
|
||||
}
|
||||
|
||||
/** A `task_id` stops its task only in the result of a `TaskStop` call:
|
||||
* another tool can give a `task_id` too. */
|
||||
private applyTaskStop(record: TranscriptRecord): void {
|
||||
let isTaskStopResult = false;
|
||||
for (const block of blocksOf(record)) {
|
||||
if (block.type === "tool_use" && block.name === "TaskStop" && block.id) {
|
||||
this.stopCalls.add(block.id);
|
||||
}
|
||||
const resultId = block.tool_use_id;
|
||||
if (resultId && this.stopCalls.has(resultId)) {
|
||||
this.stopCalls.delete(resultId);
|
||||
isTaskStopResult = true;
|
||||
}
|
||||
}
|
||||
const stopped = record.toolUseResult?.task_id;
|
||||
if (isTaskStopResult && stopped) this.open.delete(stopped);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The tasks of the lines of `text`, applied on the tasks `after`.
|
||||
*
|
||||
* `after` can be what an earlier scan found for a shorter copy of the file,
|
||||
* and `text` can repeat records of that copy. The result is still the same:
|
||||
* the last record that opens or closes a task decides, and `text` holds the
|
||||
* last records.
|
||||
*/
|
||||
export function backgroundTasksOf(
|
||||
text: string,
|
||||
options: { complete: boolean; after?: BackgroundTasks },
|
||||
): BackgroundTasks {
|
||||
const tracker = new TaskTracker(options.after);
|
||||
for (const line of text.split("\n")) tracker.add(line);
|
||||
return tracker.result({ complete: options.complete });
|
||||
}
|
||||
|
||||
/** The tasks of the first `size` bytes of `filePath`, from all its records.
|
||||
* Reads the whole file, so only for a session that a live process holds. */
|
||||
export async function searchBackgroundTasks(
|
||||
filePath: string,
|
||||
size: number,
|
||||
): Promise<BackgroundTasks> {
|
||||
const tracker = new TaskTracker();
|
||||
if (size === 0) return tracker.result({ complete: true });
|
||||
const stream = createReadStream(filePath, { start: 0, end: size - 1, encoding: "utf8" });
|
||||
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
||||
for await (const line of lines) tracker.add(line);
|
||||
return tracker.result({ complete: true });
|
||||
}
|
||||
@@ -15,8 +15,7 @@ import {
|
||||
type InitializeRequest,
|
||||
type ListSessionsRequest,
|
||||
type ListSessionsResponse,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import type { Query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { AcpClient, Logger, Session } from "../acp-agent.js";
|
||||
import {
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
AIR_SESSION_INDEX_CAPABILITY,
|
||||
AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY,
|
||||
AIR_SESSION_RENAME_CAPABILITY,
|
||||
AIR_SESSION_WORKTREES_CAPABILITY,
|
||||
clientSupportsAirCapability,
|
||||
withAirMeta,
|
||||
} from "../air-extension.js";
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from "./list-subscriptions.js";
|
||||
import {
|
||||
archiveInsteadOfDelete,
|
||||
deleteSessionIfStored,
|
||||
archiveTo,
|
||||
renameTo,
|
||||
parseRenameSessionRequest,
|
||||
@@ -143,8 +144,8 @@ export class SessionIndexConnection {
|
||||
}
|
||||
|
||||
/** The AIR capabilities the agent advertises for the index: the index
|
||||
* itself, archive, rename and the list subscription, all only to a client
|
||||
* that declared `sessionIndex`. */
|
||||
* itself, archive, rename, the list subscription and the `agentWorktree`
|
||||
* row field, all only to a client that declared `sessionIndex`. */
|
||||
capabilities(): string[] {
|
||||
return this.service
|
||||
? [
|
||||
@@ -152,6 +153,7 @@ export class SessionIndexConnection {
|
||||
AIR_SESSION_ARCHIVE_CAPABILITY,
|
||||
AIR_SESSION_RENAME_CAPABILITY,
|
||||
AIR_SESSION_LIST_SUBSCRIBE_CAPABILITY,
|
||||
AIR_SESSION_WORKTREES_CAPABILITY,
|
||||
]
|
||||
: [];
|
||||
}
|
||||
@@ -187,6 +189,31 @@ export class SessionIndexConnection {
|
||||
this.service?.ownSessionChanged(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `fork`, the handler of a `session/fork` request of `sourceId`. The
|
||||
* session list holds back the forks of the source until the response is
|
||||
* out. The client then knows a fork from the response before a list shows
|
||||
* it.
|
||||
*/
|
||||
async forking<T>(sourceId: string, fork: () => Promise<T>): Promise<T> {
|
||||
// The forks of this connection: the request handler reads the service
|
||||
// once, so a later `initialize` does not split the start and the answer.
|
||||
const service = this.service;
|
||||
service?.forkStarted(sourceId);
|
||||
try {
|
||||
return await fork();
|
||||
} finally {
|
||||
// The SDK writes the response in the microtasks after the handler
|
||||
// resolves, so a `setTimeout(0)` callback runs after it.
|
||||
setTimeout(() => service?.forkAnswered(sourceId), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** The `session/fork` of `sourceId` copied the transcript into `forkId`. */
|
||||
onForkCreated(sourceId: string, forkId: string): void {
|
||||
this.service?.forkCreated(sourceId, forkId);
|
||||
}
|
||||
|
||||
/** `_session/list/subscribe`. */
|
||||
subscribeList(params: ListSubscribeRequest): Promise<ListSubscribeResponse> {
|
||||
return this.requireService(LIST_SUBSCRIBE_METHOD).subscribeList(params.cwd);
|
||||
@@ -336,7 +363,8 @@ export class SessionIndexConnection {
|
||||
* - Another AIR client uses delete to mark a session done, and may reopen
|
||||
* it later: the adapter archives it instead (as `_session/archive` does,
|
||||
* after the CLI closed), so the transcript survives.
|
||||
* - Every other client: the SDK delete, as before.
|
||||
* - Every other client: the SDK delete. A session without a transcript
|
||||
* succeeds (see {@link deleteSessionIfStored}).
|
||||
*/
|
||||
async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
|
||||
const loaded = this.host.agent.sessions[params.sessionId] !== undefined;
|
||||
@@ -350,7 +378,7 @@ export class SessionIndexConnection {
|
||||
} else if (this.host.isAirClient()) {
|
||||
await archiveInsteadOfDelete(params.sessionId, this.airArchiver());
|
||||
} else {
|
||||
await sdkDeleteSession(params.sessionId);
|
||||
await deleteSessionIfStored(params.sessionId);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import { randomUUID } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import * as fsp from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { RequestError, type SessionInfo } from "@agentclientprotocol/sdk";
|
||||
import { RequestError, type SessionInfo } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { OwnSessionState } from "./activity.js";
|
||||
import {
|
||||
bySession,
|
||||
@@ -111,6 +111,8 @@ export type ListSubscriptionDeps = {
|
||||
/** What this connection knows of a session it runs. */
|
||||
own: (sessionId: string) => OwnSessionState | undefined;
|
||||
notify: (changes: ListChanges) => Promise<void>;
|
||||
/** The forks whose rows wait for their fork response. */
|
||||
forkHolds?: ForkHolds;
|
||||
logError: (message: string, error: unknown) => void;
|
||||
now?: () => number;
|
||||
debounceMs?: number;
|
||||
@@ -242,6 +244,54 @@ function transcriptId(filename: string): string | undefined {
|
||||
return isSessionId(id) ? id : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `session/fork` requests in flight. A client must get the id of a fork
|
||||
* from the fork response before a list shows the fork. Otherwise the client
|
||||
* sees an unknown session and lists it as a session of its own.
|
||||
*/
|
||||
export class ForkHolds {
|
||||
/** Source session id → the number of its forks in flight, and the forks
|
||||
* they copied the transcript into. */
|
||||
private readonly sources = new Map<string, { count: number; forks: Set<string> }>();
|
||||
|
||||
started(sourceId: string): void {
|
||||
const source = this.sources.get(sourceId);
|
||||
if (source) source.count++;
|
||||
else this.sources.set(sourceId, { count: 1, forks: new Set() });
|
||||
}
|
||||
|
||||
/** A fork of `sourceId` copied the transcript into `forkId`. */
|
||||
created(sourceId: string, forkId: string): void {
|
||||
this.sources.get(sourceId)?.forks.add(forkId);
|
||||
}
|
||||
|
||||
/** The response of a fork of `sourceId` is out. Returns whether that
|
||||
* freed rows: the last fork of the source answered. */
|
||||
answered(sourceId: string): boolean {
|
||||
const source = this.sources.get(sourceId);
|
||||
if (!source) return false;
|
||||
if (--source.count > 0) return false;
|
||||
this.sources.delete(sourceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Whether `id` is a fork whose fork response is not out yet. */
|
||||
isHeld(id: string): boolean {
|
||||
for (const { forks } of this.sources.values()) if (forks.has(id)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the row of `id` waits. A fork that a fork in flight created
|
||||
* waits. Before the copy returns its id, an unsent row of a fork of a
|
||||
* source in flight waits too, by its `forkedFrom`.
|
||||
*/
|
||||
holds(id: string, sent: boolean, forkedFrom: string | undefined): boolean {
|
||||
if (this.isHeld(id)) return true;
|
||||
return !sent && forkedFrom !== undefined && this.sources.has(forkedFrom);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListSubscriptions {
|
||||
private readonly subscriptions = new Map<string, Subscription>();
|
||||
/** Sessions whose row shows this connection's SDK state: the rescan
|
||||
@@ -348,6 +398,13 @@ export class ListSubscriptions {
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends the pending sessions now: {@link ForkHolds} held some back. */
|
||||
deliverPending(): void {
|
||||
for (const subscription of this.subscriptions.values()) {
|
||||
if (subscription.pending.size > 0) this.deliver(subscription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This connection wrote the transcripts of a session: a rename, archive,
|
||||
* unarchive or delete. Its transcripts are stat'ed and its row read again
|
||||
@@ -1199,7 +1256,10 @@ export class ListSubscriptions {
|
||||
if (!watch.rows.has(id)) candidates.push(...(priorFiles.get(id) ?? []));
|
||||
}
|
||||
if (candidates.length === 0) return new Set();
|
||||
const { rows, unknown } = await this.deps.index.cachedRowsOf(paths, candidates);
|
||||
// Only the ids count: no worktree check.
|
||||
const { rows, unknown } = await this.deps.index.cachedRowsOf(paths, candidates, {
|
||||
mark: false,
|
||||
});
|
||||
for (const row of rows) unknown.add(row.sessionId);
|
||||
return unknown;
|
||||
}
|
||||
@@ -1224,6 +1284,12 @@ export class ListSubscriptions {
|
||||
subscription.pending.delete(id);
|
||||
continue;
|
||||
}
|
||||
// A fork whose fork response is not out yet stays pending.
|
||||
if (
|
||||
this.deps.forkHolds?.holds(id, subscription.sent.has(id), current?.row.facts.forkedFrom)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const dueAt = (subscription.sentAt.get(id) ?? -Infinity) + this.minIntervalMs;
|
||||
if (dueAt > now) {
|
||||
nextAt = Math.min(nextAt, dueAt);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RequestError,
|
||||
type ListSessionsRequest,
|
||||
type ListSessionsResponse,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { airExtensionMeta } from "../air-extension.js";
|
||||
import { sanitizeTitle } from "../session-titles.js";
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
visibleTitle,
|
||||
} from "./archive-title.js";
|
||||
import {
|
||||
ForkHolds,
|
||||
ListSubscriptions,
|
||||
type ListChanges,
|
||||
type ListSubscribeResponse,
|
||||
@@ -383,6 +384,28 @@ function sdkDeleteError(sessionId: string): Error {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `session/delete` of a client that is neither AIR nor a `sessionIndex` client:
|
||||
* the SDK delete of the session's transcript. A session without one succeeds,
|
||||
* as ACP asks ("Deleting an already-deleted session, or a session that never
|
||||
* existed, SHOULD succeed silently"): an id that is not a session id, a
|
||||
* session deleted before, or a session this connection ran but never prompted,
|
||||
* since Claude Code writes the transcript at the first prompt. Other failures
|
||||
* still fail the request.
|
||||
*/
|
||||
export async function deleteSessionIfStored(
|
||||
sessionId: string,
|
||||
deleteSession: (sessionId: string) => Promise<void> = sdkDeleteSession,
|
||||
): Promise<void> {
|
||||
if (!isSessionId(sessionId)) return;
|
||||
try {
|
||||
await deleteSession(sessionId);
|
||||
} catch (error) {
|
||||
if (isSdkNotFound(error)) return;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `session/delete` of an AIR client without `sessionIndex`, which uses delete
|
||||
* to mark a session done: archives it instead, so the transcript survives. A
|
||||
@@ -442,6 +465,9 @@ export class SessionIndexService {
|
||||
readonly index: SessionIndex;
|
||||
readonly registry: LiveSessionRegistry;
|
||||
private subscriptions?: ListSubscriptions;
|
||||
/** Here and not in the subscriptions, which start with the first subscribe:
|
||||
* a subscribe during a fork holds that fork back too. */
|
||||
private readonly forkHolds = new ForkHolds();
|
||||
/** Set for good by {@link dispose}: no subscription is made after it. */
|
||||
private disposed = false;
|
||||
/** The mutation in flight per session, which the next one waits for. */
|
||||
@@ -450,12 +476,18 @@ export class SessionIndexService {
|
||||
private readonly deleteSession: (sessionId: string) => Promise<void>;
|
||||
|
||||
constructor(private readonly deps: SessionIndexDeps) {
|
||||
this.index = new SessionIndex();
|
||||
this.registry = deps.registry ?? new LiveSessionRegistry();
|
||||
this.index = new SessionIndex({ isLive: (sessionId) => this.isLive(sessionId) });
|
||||
this.now = deps.now ?? Date.now;
|
||||
this.deleteSession = deps.deleteSession ?? ((id) => sdkDeleteSession(id));
|
||||
}
|
||||
|
||||
/** Whether a live process holds the session, by the live registry. */
|
||||
private async isLive(sessionId: string): Promise<boolean> {
|
||||
const live = await this.registry.snapshot();
|
||||
return live.has(sessionId);
|
||||
}
|
||||
|
||||
/** `session/list` of a `sessionIndex` client. `own` reports the sessions
|
||||
* that this connection runs. */
|
||||
async list(
|
||||
@@ -480,9 +512,10 @@ export class SessionIndexService {
|
||||
});
|
||||
const live = await livePromise;
|
||||
const now = this.now();
|
||||
const sessions = rows.map((row) =>
|
||||
sessionInfoOf(row, own(row.sessionId), live.get(row.sessionId), now),
|
||||
);
|
||||
const sessions = rows
|
||||
// A fork whose fork response is not out yet is left out, as in the list subscriptions.
|
||||
.filter((row) => !this.forkHolds.isHeld(row.sessionId))
|
||||
.map((row) => sessionInfoOf(row, own(row.sessionId), live.get(row.sessionId), now));
|
||||
const last = rows[rows.length - 1];
|
||||
return hasMore && last ? { sessions, nextCursor: encodeListCursor(last, scope) } : { sessions };
|
||||
}
|
||||
@@ -686,6 +719,7 @@ export class SessionIndexService {
|
||||
registry: this.registry,
|
||||
own: (sessionId) => this.deps.ownSessionState?.(sessionId),
|
||||
notify: (changes) => this.deps.notifyListChanges?.(changes) ?? Promise.resolve(),
|
||||
forkHolds: this.forkHolds,
|
||||
logError: this.deps.logError,
|
||||
...this.deps.listSubscriptionTiming,
|
||||
});
|
||||
@@ -702,6 +736,22 @@ export class SessionIndexService {
|
||||
this.subscriptions?.ownSessionChanged(sessionId);
|
||||
}
|
||||
|
||||
/** A `session/fork` of `sourceId` started (see {@link ForkHolds}). */
|
||||
forkStarted(sourceId: string): void {
|
||||
this.forkHolds.started(sourceId);
|
||||
}
|
||||
|
||||
/** The `session/fork` of `sourceId` copied the transcript into `forkId`. */
|
||||
forkCreated(sourceId: string, forkId: string): void {
|
||||
this.forkHolds.created(sourceId, forkId);
|
||||
}
|
||||
|
||||
/** The response of a `session/fork` of `sourceId` is out: the list
|
||||
* subscriptions send the rows they held back. */
|
||||
forkAnswered(sourceId: string): void {
|
||||
if (this.forkHolds.answered(sourceId)) this.subscriptions?.deliverPending();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposed = true;
|
||||
this.subscriptions?.dispose();
|
||||
|
||||
@@ -52,8 +52,9 @@ import {
|
||||
type SdkTitles,
|
||||
type TranscriptFacts,
|
||||
} from "./transcript-scan.js";
|
||||
import { searchBackgroundTasks } from "./background-tasks.js";
|
||||
import { DirListings, statFiles } from "./dir-listing.js";
|
||||
import { worktreeCounterparts } from "./worktrees.js";
|
||||
import { isInLinkedWorktree, worktreeCounterparts } from "./worktrees.js";
|
||||
|
||||
export const DEFAULT_LIST_LIMIT = 50;
|
||||
export const MAX_LIST_LIMIT = 200;
|
||||
@@ -125,6 +126,10 @@ export type IndexRow = {
|
||||
facts: TranscriptFacts;
|
||||
mtimeMs: number;
|
||||
archived: boolean;
|
||||
/** The session runs in a linked git worktree that Claude Code made for
|
||||
* it: its transcript says so (`facts.agentWorktree`) and `cwd` lies in
|
||||
* a linked worktree. See {@link markAgentWorktrees}. */
|
||||
agentWorktree?: true;
|
||||
};
|
||||
|
||||
/** A keyset position: the order key and the session id of the last row. */
|
||||
@@ -249,6 +254,22 @@ function toRow({ candidate, metadata }: Resolved, cwd: string): IndexRow {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `agentWorktree` on the rows whose transcript says Claude Code made
|
||||
* their worktree and whose cwd lies in a linked worktree now. Only those rows
|
||||
* are checked; the check is not cached, so a removed worktree shows at once.
|
||||
*/
|
||||
async function markAgentWorktrees(rows: IndexRow[]): Promise<IndexRow[]> {
|
||||
await Promise.all(
|
||||
rows.map(async (row) => {
|
||||
if (row.facts.agentWorktree === true && (await isInLinkedWorktree(row.cwd))) {
|
||||
row.agentWorktree = true;
|
||||
}
|
||||
}),
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
function isAfter(row: { orderAtMs: number; sessionId: string }, cursor: ListCursor): boolean {
|
||||
return compareRows(row, cursor) > 0;
|
||||
}
|
||||
@@ -380,7 +401,15 @@ function scopeOf(
|
||||
};
|
||||
}
|
||||
|
||||
export type SessionIndexOptions = {
|
||||
/** Whether a live process holds the session. Only for such a session the
|
||||
* whole transcript is searched for its background tasks. */
|
||||
isLive?: (sessionId: string) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export class SessionIndex {
|
||||
constructor(private readonly options: SessionIndexOptions = {}) {}
|
||||
|
||||
private readonly listings = new DirListings();
|
||||
private readonly metadata = new Lru<string, CacheEntry>(METADATA_CACHE_SIZE);
|
||||
/** The metadata reads in flight, by path: a list and a subscription that
|
||||
@@ -686,11 +715,14 @@ export class SessionIndex {
|
||||
* alone gives, read from it at once, and the ids of the sessions it gives
|
||||
* no answer for: a transcript of it has nothing cached at its stat, no cwd
|
||||
* known, or is continued in another session (whether the successor hides
|
||||
* it depends on the successor now). Reads no transcript.
|
||||
* it depends on the successor now). Reads no transcript. The rows carry
|
||||
* `agentWorktree` ({@link markAgentWorktrees}, which reads git files)
|
||||
* unless `mark` is false, for a caller that uses only their ids.
|
||||
*/
|
||||
async cachedRowsOf(
|
||||
paths: readonly string[],
|
||||
candidates: readonly TranscriptCandidate[],
|
||||
{ mark = true }: { mark?: boolean } = {},
|
||||
): Promise<{ rows: IndexRow[]; unknown: Set<string> }> {
|
||||
const unknown = new Set<string>();
|
||||
const resolved: { item: Resolved; cwd: string }[] = [];
|
||||
@@ -721,7 +753,8 @@ export class SessionIndex {
|
||||
if (previous && previous.mtimeMs >= item.candidate.mtimeMs) continue;
|
||||
newest.set(id, { row: toRow(item, cwd), mtimeMs: item.candidate.mtimeMs });
|
||||
}
|
||||
return { rows: [...newest.values()].map(({ row }) => row), unknown };
|
||||
const rows = [...newest.values()].map(({ row }) => row);
|
||||
return { rows: mark ? await markAgentWorktrees(rows) : rows, unknown };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -831,7 +864,7 @@ export class SessionIndex {
|
||||
);
|
||||
await settlePending();
|
||||
}
|
||||
return rows();
|
||||
return markAgentWorktrees(rows());
|
||||
}
|
||||
|
||||
/** Whether the cached metadata of `candidate`, still current, places it
|
||||
@@ -968,14 +1001,15 @@ export class SessionIndex {
|
||||
this.reads++;
|
||||
const headTail = await readHeadTail(candidate.filePath, candidate.size);
|
||||
if (isSidechainTranscript(headTail.head)) return null;
|
||||
const facts = await scanTranscriptFile(
|
||||
const scanned = await scanTranscriptFile(
|
||||
candidate.filePath,
|
||||
candidate.size,
|
||||
headTail,
|
||||
previous,
|
||||
candidate.ino,
|
||||
);
|
||||
if (!facts.hasMessages) return null;
|
||||
if (!scanned.hasMessages) return null;
|
||||
const facts = await this.withAllBackgroundTasks(candidate, scanned);
|
||||
// The last relocation names the session's cwd, as the SDK reads it; the
|
||||
// messages before it keep the old one.
|
||||
const fileCwd = await this.recoverCwd(candidate.dirName, [
|
||||
@@ -1008,6 +1042,24 @@ export class SessionIndex {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `facts` with the background tasks of the whole transcript, when the
|
||||
* tasks of its tail are not complete: a task can start before the tail.
|
||||
* Only a finished turn of a live session waits for the tasks, so only
|
||||
* then the whole transcript is read.
|
||||
*/
|
||||
private async withAllBackgroundTasks(
|
||||
candidate: TranscriptCandidate,
|
||||
facts: TranscriptFacts,
|
||||
): Promise<TranscriptFacts> {
|
||||
if (facts.turnState !== "finished") return facts;
|
||||
if (facts.backgroundTasks?.complete) return facts;
|
||||
const isLive = await this.options.isLive?.(candidate.sessionId);
|
||||
if (!isLive) return facts;
|
||||
const backgroundTasks = await searchBackgroundTasks(candidate.filePath, candidate.size);
|
||||
return { ...facts, backgroundTasks };
|
||||
}
|
||||
|
||||
/**
|
||||
* The titles the SDK gives the listed transcript (`customTitle` and
|
||||
* `summary` of its `getSessionInfo`), by the SDK's rule from the head and
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* the RFDs in `_meta.jetbrains.air`.
|
||||
*/
|
||||
|
||||
import type { SessionInfo } from "@agentclientprotocol/sdk";
|
||||
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
|
||||
import { withAirMeta } from "../air-extension.js";
|
||||
import { deriveActivity, selectCost, type OwnSessionState } from "./activity.js";
|
||||
import type { LiveRecord } from "./live-registry.js";
|
||||
@@ -38,6 +38,7 @@ export function sessionInfoOf(
|
||||
lastPromptAt: iso(facts.lastPromptAt),
|
||||
model: facts.model,
|
||||
forkedFrom: facts.forkedFrom,
|
||||
agentWorktree: row.agentWorktree,
|
||||
state: activity?.state,
|
||||
lastTurnEndedAt: activity?.lastTurnEndedAt,
|
||||
cost: cost === undefined ? undefined : { amount: cost, currency: "USD" },
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
*
|
||||
* A last message longer than the tail window leaves no message in it: then
|
||||
* the tail window grows, up to {@link MAX_TAIL_SIZE}, until it holds one.
|
||||
*
|
||||
* The open background tasks come from the tail, on top of the tasks of an
|
||||
* earlier scan (see `background-tasks.ts`).
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import { backgroundTasksOf, type BackgroundTasks } from "./background-tasks.js";
|
||||
import { firstPrompt, isUserPrompt, mediaPrompt, promptOf } from "./first-prompt.js";
|
||||
|
||||
const CHUNK_SIZE = 64 * 1024;
|
||||
@@ -51,6 +55,14 @@ export type TranscriptFacts = {
|
||||
/** The session this one was forked from: the `forkedFrom.sessionId` that
|
||||
* the SDK and CLI fork write on every copied record. */
|
||||
forkedFrom?: string;
|
||||
/** What the last `worktree-state` record says: true when its
|
||||
* `worktreeSession` is set and not `enteredExisting` (Claude Code made the
|
||||
* worktree and the session is in it), false when it says otherwise.
|
||||
* Undefined when no such record was read. See {@link agentWorktreeOf}. */
|
||||
agentWorktree?: boolean;
|
||||
/** The background tasks that did not end. From the tail, on top of what
|
||||
* an earlier scan found when the file only grew within the tail. */
|
||||
backgroundTasks?: BackgroundTasks;
|
||||
/** Whether the head or the tail holds a user or an assistant message. A
|
||||
* transcript without one is a metadata-only stub. */
|
||||
hasMessages: boolean;
|
||||
@@ -67,12 +79,12 @@ export type HeadTail = {
|
||||
};
|
||||
|
||||
/** Facts that an earlier scan may have found before the tail window. */
|
||||
const INHERITED_FACTS = ["model", "tailCwd", "lastTurnEndedAt"] as const;
|
||||
const INHERITED_FACTS = ["model", "tailCwd", "lastTurnEndedAt", "agentWorktree"] as const;
|
||||
|
||||
/** What an earlier scan of the same file found, at `size`. */
|
||||
export type PreviousScan = { size: number } & Pick<
|
||||
TranscriptFacts,
|
||||
"lastPromptAt" | "promptSearched" | (typeof INHERITED_FACTS)[number]
|
||||
"lastPromptAt" | "promptSearched" | "backgroundTasks" | (typeof INHERITED_FACTS)[number]
|
||||
>;
|
||||
|
||||
/** The last `window` bytes of a file of `size` bytes, without the first,
|
||||
@@ -152,6 +164,14 @@ export async function scanTranscriptFile(
|
||||
}
|
||||
}
|
||||
}
|
||||
const previousTasks = grownWithinTail ? previous.backgroundTasks : undefined;
|
||||
facts.backgroundTasks = tailBackgroundTasks(read.tail, size, previousTasks);
|
||||
// No worktree record in the tail (or before it, by an earlier scan): the
|
||||
// one `claude --worktree` writes at the start may still be in the head.
|
||||
if (facts.agentWorktree === undefined) {
|
||||
const agentWorktree = agentWorktreeOf(read.head);
|
||||
if (agentWorktree !== undefined) facts.agentWorktree = agentWorktree;
|
||||
}
|
||||
if (facts.lastPromptAt !== undefined || size <= CHUNK_SIZE) {
|
||||
facts.promptSearched = true;
|
||||
} else if (grownWithinTail && (previous.lastPromptAt !== undefined || previous.promptSearched)) {
|
||||
@@ -163,7 +183,8 @@ export async function scanTranscriptFile(
|
||||
if (!facts.hasMessages || complete(facts) || size <= CHUNK_SIZE) return facts;
|
||||
const key = `${filePath}\0${size}\0${identity ?? ""}`;
|
||||
const searched = fullySearched.get(key);
|
||||
if (searched) return searched;
|
||||
// The tasks can differ by the earlier scan.
|
||||
if (searched) return { ...searched, backgroundTasks: facts.backgroundTasks };
|
||||
let result = facts;
|
||||
const handle = await fs.open(filePath, "r");
|
||||
try {
|
||||
@@ -199,9 +220,15 @@ export async function scanTranscriptFile(
|
||||
...wider,
|
||||
...(facts.model !== undefined && { model: facts.model }),
|
||||
...(facts.tailCwd !== undefined && { tailCwd: facts.tailCwd }),
|
||||
// The wider tail holds the narrow one, so a record it has is the
|
||||
// latest; else the narrow scan's (inherited or from the head) stands.
|
||||
...((wider.agentWorktree ?? facts.agentWorktree) !== undefined && {
|
||||
agentWorktree: wider.agentWorktree ?? facts.agentWorktree,
|
||||
}),
|
||||
// A turn end the wider tail lacks may lie before it: the
|
||||
// narrow scan inherited it from an earlier scan of this file.
|
||||
lastTurnEndedAt: wider.lastTurnEndedAt ?? facts.lastTurnEndedAt,
|
||||
backgroundTasks: facts.backgroundTasks,
|
||||
hasMessages: facts.hasMessages,
|
||||
lastPromptAt,
|
||||
// Searched to the start of the file, or found.
|
||||
@@ -221,6 +248,22 @@ export async function scanTranscriptFile(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The background tasks of the tail of a file of `size` bytes. `previous`:
|
||||
* the tasks of an earlier scan, when the file only grew within the tail
|
||||
* since. The set is complete when the tail is the whole file, or when it
|
||||
* holds all records after a complete earlier set.
|
||||
*/
|
||||
function tailBackgroundTasks(
|
||||
tail: string,
|
||||
size: number,
|
||||
previous: BackgroundTasks | undefined,
|
||||
): BackgroundTasks {
|
||||
if (size <= CHUNK_SIZE) return backgroundTasksOf(tail, { complete: true });
|
||||
if (previous?.complete) return backgroundTasksOf(tail, { complete: true, after: previous });
|
||||
return backgroundTasksOf(tail, { complete: false });
|
||||
}
|
||||
|
||||
const RELOCATED_MARKER = '"relocated"';
|
||||
|
||||
/** The cwd of the last `relocated` record of the tail: where the session
|
||||
@@ -238,6 +281,43 @@ export function relocatedCwd(tail: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const WORKTREE_STATE_MARKER = '"worktree-state"';
|
||||
|
||||
/**
|
||||
* Whether the last `worktree-state` record of `text` says the session is in
|
||||
* a worktree that Claude Code made for it, as AIR's native Claude provider
|
||||
* reads it: a `worktreeSession` that is an object without
|
||||
* `enteredExisting: true`. The last line that parses as a `worktree-state`
|
||||
* record decides; any other `worktreeSession` (`null`, which the CLI writes
|
||||
* when the session leaves the worktree, missing, or no object) means false.
|
||||
* Only lines that are not JSON (such as the cut first line of a window) are
|
||||
* skipped. Undefined when `text` holds no such record.
|
||||
*
|
||||
* The CLI writes the record when it enters or leaves a worktree (for
|
||||
* `claude --worktree`, at the start of the session, so in the head), and
|
||||
* writes it again with the other session metadata at the end of the
|
||||
* transcript (on exit and as the transcript grows), so the tail usually
|
||||
* holds the latest one. Only the head and the tail are read: a session that
|
||||
* entered a worktree midway through a long transcript, with no metadata
|
||||
* written after it yet, is not seen until the CLI writes the metadata again.
|
||||
*/
|
||||
export function agentWorktreeOf(text: string): boolean | undefined {
|
||||
if (!text.includes(WORKTREE_STATE_MARKER)) return undefined;
|
||||
const lines = text.split("\n");
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const line = lines[i]!;
|
||||
if (!line.includes(WORKTREE_STATE_MARKER)) continue;
|
||||
const entry = parseLine(line);
|
||||
// Only a line that is no JSON record is skipped: the latest record that
|
||||
// parses decides, even when its `worktreeSession` is missing or no object.
|
||||
if (entry?.type !== "worktree-state") continue;
|
||||
const session = entry.worktreeSession;
|
||||
if (!session || typeof session !== "object" || Array.isArray(session)) return false;
|
||||
return (session as { enteredExisting?: unknown }).enteredExisting !== true;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** The cwd a transcript belongs to, as the SDK reads it: the last
|
||||
* `relocated` record of the tail, else the first cwd of the head. */
|
||||
export function transcriptProjectCwd({ head, tail }: HeadTail): string | undefined {
|
||||
@@ -344,6 +424,10 @@ export function scanTranscript(
|
||||
if (headCwd !== undefined) facts.headCwd = decodeJsonString(headCwd);
|
||||
const forkedFrom = FORKED_FROM_PATTERN.exec(head)?.[1];
|
||||
if (forkedFrom !== undefined) facts.forkedFrom = forkedFrom;
|
||||
// The tail only: scanTranscriptFile falls back to the head after it
|
||||
// inherits what an earlier scan found before the tail.
|
||||
const agentWorktree = agentWorktreeOf(tail);
|
||||
if (agentWorktree !== undefined) facts.agentWorktree = agentWorktree;
|
||||
|
||||
const lines = tail.split("\n");
|
||||
let turnEndFound = false;
|
||||
|
||||
@@ -115,6 +115,18 @@ export async function worktreeCounterparts(cwd: string): Promise<string[]> {
|
||||
return [...new Set(existing.filter((value): value is string => value !== undefined))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `cwd` lies in a linked worktree: its git dir is
|
||||
* `<common-dir>/worktrees/<name>`. False in the main worktree, in a
|
||||
* submodule and outside a repository.
|
||||
*/
|
||||
export async function isInLinkedWorktree(cwd: string): Promise<boolean> {
|
||||
const gitDir = await findGitDir(cwd);
|
||||
if (!gitDir) return false;
|
||||
const commonDir = await commonDirOf(gitDir);
|
||||
return path.resolve(path.dirname(gitDir)) === path.resolve(commonDir, "worktrees");
|
||||
}
|
||||
|
||||
/**
|
||||
* Every existing worktree of the repository that contains `cwd`: the main
|
||||
* worktree and the linked ones. Empty outside a repository.
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type SessionNotification,
|
||||
type SetSessionModeRequest,
|
||||
type SetSessionModeResponse,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import type {
|
||||
ModelInfo,
|
||||
PermissionMode,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { ModelInfo, Query, Settings } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY, withAirMeta } from "./air-extension.js";
|
||||
import { bedrockRegionPrefix } from "./gateway-models.js";
|
||||
|
||||
export const MODEL_CONFIG_ID = "model";
|
||||
|
||||
@@ -262,31 +263,47 @@ function resolveSettingsModel(
|
||||
|
||||
const MODEL_FAMILY_ALIASES = new Set(["opus", "sonnet", "haiku", "fable"]);
|
||||
|
||||
/** A model id without the spellings `deniedModels` ignores: provider
|
||||
* prefixes, dates, version suffixes, `-fast` and context hints. */
|
||||
function normalizeDeniedModelId(value: string): string {
|
||||
/** A model id without the spellings that name the same model: provider
|
||||
* prefixes, dates, version suffixes, `:variant` suffixes, dotted versions,
|
||||
* `-fast` and context hints. `deniedModels` and gateway listings both match on
|
||||
* it. OpenRouter lists `anthropic/claude-opus-5.5:batch[1m]`, which normalizes
|
||||
* to `claude-opus-5-5`; `anthropic/openai/gpt-5` keeps its `openai/` prefix and
|
||||
* matches no Claude id. */
|
||||
function normalizeModelId(value: string): string {
|
||||
let id = stripContextHints(value.trim().toLowerCase());
|
||||
const providerPrefix = id.lastIndexOf("anthropic.");
|
||||
if (providerPrefix !== -1) id = id.slice(providerPrefix + "anthropic.".length);
|
||||
return id
|
||||
.replace(/@.*$/, "")
|
||||
.replace(/-v\d+(?::\d+)?$/, "")
|
||||
.replace(/-fast$/, "")
|
||||
.replace(/-\d{8}$/, "");
|
||||
return (
|
||||
id
|
||||
// Only a leading `anthropic/`: OpenRouter's `anthropic/openai/gpt-5` must
|
||||
// keep `openai/`, which `lastIndexOf` would strip along with it.
|
||||
.replace(/^anthropic\//, "")
|
||||
// `:batch`, `:free`. A letter must follow the colon, so Bedrock's `-v1:0`
|
||||
// is left for the `-vN:M` step below.
|
||||
.replace(/:[a-z][a-z0-9-]*$/, "")
|
||||
// `claude-opus-5.5` -> `claude-opus-5-5`; only dots between two digits.
|
||||
.replace(/(?<=\d)\.(?=\d)/g, "-")
|
||||
.replace(/@.*$/, "")
|
||||
.replace(/-v\d+(?::\d+)?$/, "")
|
||||
.replace(/-fast$/, "")
|
||||
.replace(/-\d{8}$/, "")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether managed `deniedModels` blocks a model id, following the CLI's rules
|
||||
* closely enough for the picker: a family alias blocks the whole family, and a
|
||||
* model id blocks that version in every spelling plus the later minor versions
|
||||
* it prefixes (`claude-opus-5` also blocks `claude-opus-5-5`). The CLI stays
|
||||
* the enforcer; this only keeps the picker from offering a refused row.
|
||||
* it prefixes (`claude-opus-5` also blocks `claude-opus-5-5`). Entries share
|
||||
* `normalizeModelId`, so `claude-opus-4.5` or `anthropic/claude-opus-4.5` also
|
||||
* blocks `claude-opus-4-5`, which the CLI may still allow. The CLI stays the
|
||||
* enforcer; this only keeps the picker from offering a refused row.
|
||||
*/
|
||||
export function isDeniedModel(modelId: string, deniedModels: readonly string[]): boolean {
|
||||
const id = normalizeDeniedModelId(modelId);
|
||||
const id = normalizeModelId(modelId);
|
||||
if (!id) return false;
|
||||
return deniedModels.some((entry) => {
|
||||
const denied = normalizeDeniedModelId(entry);
|
||||
const denied = normalizeModelId(entry);
|
||||
if (!denied) return false;
|
||||
if (MODEL_FAMILY_ALIASES.has(denied)) return id === denied || id.includes(`-${denied}`);
|
||||
return id === denied || id.startsWith(`${denied}-`);
|
||||
@@ -379,6 +396,48 @@ export function applyAvailableModelsAllowlist(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the SDK models the `providers/set` gateway listed (`listGatewayModels`,
|
||||
* `listBedrockModels`). A row matches when its `resolvedModel` (or
|
||||
* its `value` when the SDK gives none) names a listed model in any spelling:
|
||||
* `claude-sonnet-5[1m]` matches a listed `claude-sonnet-5-20260101`, and
|
||||
* `eu.anthropic.claude-sonnet-5` matches a listed `eu.anthropic.claude-sonnet-5-v1:0`.
|
||||
*
|
||||
* With a Bedrock `regionPrefix`, a row resolved to another cross-region prefix
|
||||
* (`us.anthropic.claude-sonnet-5` under `eu`) is dropped even when the same
|
||||
* model is listed under `eu`: the CLI would call it in the other region.
|
||||
*
|
||||
* A row the SDK gives no `resolvedModel` names its model only by an alias, so
|
||||
* it is checked as far as the alias allows: `default` is kept, and a family
|
||||
* alias such as `opus` is kept when the gateway lists a model of that family.
|
||||
*
|
||||
* When no row matches by its model (a `default` row kept only for lacking a
|
||||
* `resolvedModel` does not count), the gateway serves none of the SDK's
|
||||
* models, and nothing is left: the session offers no model.
|
||||
*/
|
||||
export function narrowToGatewayModels(
|
||||
models: ModelInfo[],
|
||||
listedModelIds: readonly string[],
|
||||
regionPrefix?: string,
|
||||
): ModelInfo[] {
|
||||
const listed = new Set(listedModelIds.map(normalizeModelId).filter(Boolean));
|
||||
const narrowed = models.filter((model) => {
|
||||
const modelId = model.resolvedModel ?? model.value;
|
||||
const prefix = bedrockRegionPrefix(modelId);
|
||||
if (regionPrefix !== undefined && prefix !== undefined && prefix !== regionPrefix) {
|
||||
return false;
|
||||
}
|
||||
const id = normalizeModelId(modelId);
|
||||
if (listed.has(id)) return true;
|
||||
if (model.resolvedModel !== undefined) return false;
|
||||
if (model.value === "default") return true;
|
||||
return MODEL_FAMILY_ALIASES.has(id) && [...listed].some((entry) => entry.includes(`-${id}`));
|
||||
});
|
||||
return narrowed.some((model) => listed.has(normalizeModelId(model.resolvedModel ?? model.value)))
|
||||
? narrowed
|
||||
: [];
|
||||
}
|
||||
|
||||
/** Whether a rejected `setModel` was vetoed by a user-configured
|
||||
* PreModelSwitch hook. The CLI's rejection message is the stable,
|
||||
* distinguishable marker ("Model switch blocked by a PreModelSwitch hook:
|
||||
@@ -397,7 +456,18 @@ export async function getAvailableModels(
|
||||
isResumedSession: boolean,
|
||||
sessionId: string,
|
||||
resumedModelHint?: string,
|
||||
narrowedByGateway = false,
|
||||
): Promise<SessionModelState> {
|
||||
// A gateway that lists none of the SDK's models leaves none to offer
|
||||
// (`narrowToGatewayModels`). The session stays on the model the SDK runs,
|
||||
// and the gateway refuses its requests.
|
||||
if (models.length === 0) {
|
||||
return {
|
||||
availableModels: [],
|
||||
currentModelId: (isResumedSession && resumedModelHint) || sdkModels[0]?.value || "default",
|
||||
};
|
||||
}
|
||||
|
||||
const settings = settingsManager.getSettings();
|
||||
|
||||
let currentModel = models[0];
|
||||
@@ -444,9 +514,34 @@ export async function getAvailableModels(
|
||||
// be re-asserted to keep the reported model truthful.
|
||||
// Anything else (fuzzy match, allowlist-synthesized value, alias) gets a
|
||||
// setModel call so we don't drift from the user's intended pin.
|
||||
// Case (a) does not hold when `narrowToGatewayModels` left the SDK on a
|
||||
// model the gateway does not list: the dropped `default` row of a fresh or
|
||||
// transcript-less session, or a resumed transcript model outside the list.
|
||||
// Such a session switches to models[0].
|
||||
const sdkSawSameValue = sdkModels.some((m) => m.value === currentModel.value);
|
||||
const sdkDefault = sdkModels.find((m) => m.value === "default");
|
||||
const sdkDefaultDropped = sdkDefault !== undefined && !models.some((m) => m.value === "default");
|
||||
let switchToListed = false;
|
||||
if (resolvedFromInput === undefined) {
|
||||
if (!isResumedSession || !resumedModelHint) {
|
||||
switchToListed = sdkDefaultDropped;
|
||||
} else if (
|
||||
narrowedByGateway &&
|
||||
(!models.some((m) => m.value === currentModel.value) ||
|
||||
// A loose match can map the transcript's model onto a listed row of
|
||||
// another version, which the CLI does not resume on.
|
||||
(currentModel.resolvedModel !== undefined &&
|
||||
normalizeModelId(currentModel.resolvedModel) !== normalizeModelId(resumedModelHint)))
|
||||
) {
|
||||
currentModel = models[0];
|
||||
switchToListed = true;
|
||||
}
|
||||
}
|
||||
// When the switch fails, the SDK stays on the model it ran before it.
|
||||
const modelBeforeSwitch = (): ModelInfo =>
|
||||
switchToListed && sdkDefault !== undefined ? sdkDefault : models[0];
|
||||
const skipSetModel =
|
||||
resolvedFromInput === undefined ||
|
||||
(resolvedFromInput === undefined && !switchToListed) ||
|
||||
(!isResumedSession && currentModel.value === resolvedFromInput && sdkSawSameValue);
|
||||
if (!skipSetModel) {
|
||||
const setModelStartedAt = performance.now();
|
||||
@@ -478,15 +573,19 @@ export async function getAvailableModels(
|
||||
// live model back on this path: getContextUsage isn't serviced on a
|
||||
// fresh session until the first prompt turn (issues #886/#880).
|
||||
if (!isResumedSession) {
|
||||
if (!isPreModelSwitchHookBlock(error)) throw error;
|
||||
// A switch onto a listed model is advisory too: the session stays on
|
||||
// the model the SDK runs and reports it.
|
||||
if (!switchToListed && !isPreModelSwitchHookBlock(error)) throw error;
|
||||
logger.error(
|
||||
`Model pin "${currentModel.value}" was vetoed by a PreModelSwitch hook; staying on the default model:`,
|
||||
`Model switch to "${currentModel.value}" failed or was vetoed by a PreModelSwitch hook; staying on the default model:`,
|
||||
error,
|
||||
);
|
||||
currentModel = models[0];
|
||||
currentModel = modelBeforeSwitch();
|
||||
} else {
|
||||
logger.error(`Failed to re-assert model "${currentModel.value}" on resume:`, error);
|
||||
currentModel = resumedModelHint ? matchResumedModel(models, resumedModelHint) : models[0];
|
||||
currentModel = resumedModelHint
|
||||
? matchResumedModel(models, resumedModelHint)
|
||||
: modelBeforeSwitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk";
|
||||
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** Fire-and-forget advisory text for the user: live events rather than
|
||||
* conversation history. See the ACP Session Notices RFD
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
/**
|
||||
* AI-generated session titles.
|
||||
*
|
||||
* Claude Code's own auto-title generation never runs under the Agent SDK — the
|
||||
* latch that arms it is pre-set in the headless path — so `SDKSessionInfo.summary`
|
||||
* degrades to the raw first prompt. This module asks the CLI for a real title
|
||||
* instead, via the `generate_session_title` control request, and publishes it
|
||||
* over ACP as a `session_info_update`.
|
||||
* Claude Code titles a new SDK session itself: when it reads the first prompt,
|
||||
* it asks a small model for a title and stores it in the transcript as an
|
||||
* `ai-title` entry, a few seconds in. It sends no message when it does. This
|
||||
* module asks for the title with the `generate_session_title` control request,
|
||||
* which returns the stored title or waits for the one being generated, and
|
||||
* publishes it over ACP as a `session_info_update`. Where the CLI does not
|
||||
* title the session itself (a resumed session, or one with
|
||||
* `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` or
|
||||
* `CLAUDE_CODE_DISABLE_TERMINAL_TITLE` set), the same request generates it.
|
||||
*
|
||||
* {@link SessionTitles} holds the per-session state and is owned by `Session`.
|
||||
* `acp-agent.ts` drives it from four places: {@link SessionTitles.onPrompt} and
|
||||
* `acp-agent.ts` drives it from five places: {@link SessionTitles.onPrompt} and
|
||||
* {@link SessionTitles.onAssistantText} to collect text,
|
||||
* {@link SessionTitles.onPromptQueued} once a prompt is sent to the CLI,
|
||||
* {@link SessionTitles.onTurnEnd} at `session_state_changed: idle`, and
|
||||
* {@link SessionTitles.reset} on `conversation_reset`.
|
||||
*/
|
||||
|
||||
import type { ContentBlock, PromptRequest } from "@agentclientprotocol/sdk";
|
||||
import type { ContentBlock, PromptRequest } from "@agentclientprotocol/sdk/unstable";
|
||||
import { getSessionInfo, type Query, type SDKSessionInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { ClaudeAcpAgent, Session } from "./acp-agent.js";
|
||||
import { ExplicitTitle } from "./session-index/explicit-title.js";
|
||||
@@ -77,6 +82,11 @@ export function appendTitleContext(previous: string | undefined, text: string):
|
||||
return next.length > MAX_TITLE_CONTEXT_LENGTH ? next.slice(-MAX_TITLE_CONTEXT_LENGTH) : next;
|
||||
}
|
||||
|
||||
/** The text chunks of a prompt, one per line. */
|
||||
function promptText(prompt: PromptRequest["prompt"]): string {
|
||||
return prompt.flatMap((chunk) => (chunk.type === "text" ? [chunk.text] : [])).join("\n");
|
||||
}
|
||||
|
||||
/** Whether this SDK still offers on-demand title generation. */
|
||||
function supportsTitleGeneration(query: Query): boolean {
|
||||
return typeof (query as TitleCapableQuery).generateSessionTitle === "function";
|
||||
@@ -100,6 +110,11 @@ export class SessionTitles {
|
||||
* one. Released by {@link reset}, and when generation yields nothing. */
|
||||
private settled = false;
|
||||
|
||||
/** Whether {@link onPromptQueued} asked for a title from the latest prompt.
|
||||
* A turn end then does not ask again: like the CLI, a prompt that yields
|
||||
* no title gets one try, and the next prompt tries again. */
|
||||
private askedAtPrompt = false;
|
||||
|
||||
/** The title a client set, which no generated title replaces. */
|
||||
private readonly explicit = new ExplicitTitle();
|
||||
|
||||
@@ -132,11 +147,33 @@ export class SessionTitles {
|
||||
if (this.settled) {
|
||||
return;
|
||||
}
|
||||
const promptText = prompt
|
||||
.flatMap((chunk) => (chunk.type === "text" ? [chunk.text] : []))
|
||||
.join("\n");
|
||||
this.context = appendTitleContext(this.context, `\n${promptText(prompt)}\n`);
|
||||
}
|
||||
|
||||
this.context = appendTitleContext(this.context, `\n${promptText}\n`);
|
||||
/** Ask for the title as soon as a prompt is sent to the CLI, instead of
|
||||
* waiting for `idle`: `idle` comes only after the whole turn and its
|
||||
* background tasks, often minutes after the CLI stored its title. The CLI
|
||||
* answers `generate_session_title` while the turn runs, and with the title
|
||||
* it stored or is generating, so this makes no second model call. The
|
||||
* prompt alone is the description, because the CLI titles from the prompt
|
||||
* alone. When this yields no title, the stored summary is published and
|
||||
* the next prompt tries again. */
|
||||
onPromptQueued(session: Session, prompt: PromptRequest["prompt"]): void {
|
||||
this.askedAtPrompt = false;
|
||||
// `session.cancelled` is not checked: after a cancelled turn it stays set
|
||||
// until the next turn starts, which is after this prompt is queued.
|
||||
if (this.settled || this.explicit.active || session.queryClosed) {
|
||||
return;
|
||||
}
|
||||
if (!supportsTitleGeneration(session.query)) {
|
||||
return;
|
||||
}
|
||||
const description = promptText(prompt).trim();
|
||||
if (description.length < MIN_TITLE_CONTEXT_LENGTH) {
|
||||
return;
|
||||
}
|
||||
this.askedAtPrompt = true;
|
||||
this.startGeneration(() => this.titleFromPrompt(session, description));
|
||||
}
|
||||
|
||||
/** Collect the assistant's own answer for the title. Chunks are appended
|
||||
@@ -152,6 +189,7 @@ export class SessionTitles {
|
||||
* `conversation_reset`, which mounts a fresh transcript. */
|
||||
reset(): void {
|
||||
this.settled = false;
|
||||
this.askedAtPrompt = false;
|
||||
this.explicit.reset();
|
||||
this.context = undefined;
|
||||
this.lastTitle = undefined;
|
||||
@@ -165,10 +203,12 @@ export class SessionTitles {
|
||||
* title — a user `/rename` or one generated earlier; the SDK folds both into
|
||||
* that one field. Adopt it and latch, so neither is ever titled over.
|
||||
*
|
||||
* With no title yet, ask the SDK to generate one in the background: it is a
|
||||
* ~2s small-model call and turn-end must not wait on it. Only when that is
|
||||
* not possible do we fall back to `summary`, which for an SDK-driven session
|
||||
* is just the raw first prompt. */
|
||||
* With no title yet, and none asked for at the latest prompt (see
|
||||
* {@link onPromptQueued}), ask the SDK to generate one in the background
|
||||
* from the prompts and replies so far: it is a ~2s small-model call and
|
||||
* turn-end must not wait on it. That covers a prompt too short to title
|
||||
* from, such as "hi". Only when that is not possible do we fall back to
|
||||
* `summary`, which for an SDK-driven session is just the raw first prompt. */
|
||||
async onTurnEnd(session: Session): Promise<void> {
|
||||
if (this.explicit.settledByClient) {
|
||||
// No title is generated over the one the client set, but a custom title
|
||||
@@ -200,17 +240,9 @@ export class SessionTitles {
|
||||
? { title: info.summary, lastModified: info.lastModified }
|
||||
: undefined;
|
||||
|
||||
if (this.canRequest(session)) {
|
||||
this.settled = true;
|
||||
|
||||
this.explicit.track(
|
||||
this.requestGenerateTitle(session, fallback).catch((error) => {
|
||||
this.agent.logger.error(
|
||||
`Session ${this.sessionId}: session title update failed: ${error}`,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
if (!this.askedAtPrompt && this.canRequest(session)) {
|
||||
const description = this.context?.trim() ?? "";
|
||||
this.startGeneration(() => this.requestGenerateTitle(session, description, fallback));
|
||||
return;
|
||||
}
|
||||
// Only while this session has no title of its own: `summary` degrades to the
|
||||
@@ -309,17 +341,48 @@ export class SessionTitles {
|
||||
return (this.context?.trim().length ?? 0) >= MIN_TITLE_CONTEXT_LENGTH;
|
||||
}
|
||||
|
||||
/** Generate a title from the session's own text and publish it. `persist`
|
||||
* writes it to the session file, which is what carries it into
|
||||
* `session/list` and `session/load`. A null or failed generation releases the
|
||||
* latch so a later turn retries, and publishes `fallback` (the stored
|
||||
* summary) so a backend that can't generate titles is no worse off than
|
||||
* before. */
|
||||
/** Latch the title and run `generation` in the background, so neither the
|
||||
* prompt nor turn-end waits on a model call. A client title change waits
|
||||
* for it to finish (see {@link ExplicitTitle.track}). */
|
||||
private startGeneration(generation: () => Promise<void>): void {
|
||||
this.settled = true;
|
||||
this.explicit.track(
|
||||
generation().catch((error) => {
|
||||
this.agent.logger.error(`Session ${this.sessionId}: session title update failed: ${error}`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** The prompt-time title: the one the session file already holds (a resumed
|
||||
* session's title, or a `/rename`), else one generated from `description`.
|
||||
* The file is read first because the CLI checks only the titles it holds in
|
||||
* memory before generating one. */
|
||||
private async titleFromPrompt(session: Session, description: string): Promise<void> {
|
||||
const info = await this.readSessionInfo(session);
|
||||
if (this.agent.sessions[this.sessionId] !== session || this.explicit.active) {
|
||||
return;
|
||||
}
|
||||
if (info?.customTitle) {
|
||||
this.context = undefined;
|
||||
await this.publish(info.customTitle, info.lastModified);
|
||||
return;
|
||||
}
|
||||
const fallback = info?.summary
|
||||
? { title: info.summary, lastModified: info.lastModified }
|
||||
: undefined;
|
||||
await this.requestGenerateTitle(session, description, fallback);
|
||||
}
|
||||
|
||||
/** Generate a title from `description` and publish it. `persist` writes it
|
||||
* to the session file, which is what carries it into `session/list` and
|
||||
* `session/load`. A null or failed generation releases the latch so a later
|
||||
* prompt or turn end retries, and publishes `fallback` (the stored summary)
|
||||
* so a backend that can't generate titles is no worse off than before. */
|
||||
private async requestGenerateTitle(
|
||||
session: Session,
|
||||
description: string,
|
||||
fallback: TitleFallback | undefined,
|
||||
): Promise<void> {
|
||||
const description = this.context?.trim() ?? "";
|
||||
let title: string | null = null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -50,6 +50,7 @@ export class SettingsManager {
|
||||
private cwd: string;
|
||||
private effective: Settings = {};
|
||||
private managedDeniedModels: string[] = [];
|
||||
private managedApiKeyHelper?: string;
|
||||
private watchers: fs.FSWatcher[] = [];
|
||||
private onChange?: () => void;
|
||||
private logger: { log: (...args: any[]) => void; error: (...args: any[]) => void };
|
||||
@@ -111,10 +112,15 @@ export class SettingsManager {
|
||||
this.managedDeniedModels = resolved.sources
|
||||
.filter((entry) => entry.source === "managed")
|
||||
.flatMap((entry) => entry.settings.deniedModels ?? []);
|
||||
this.managedApiKeyHelper =
|
||||
resolved.provenance.apiKeyHelper?.source === "managed"
|
||||
? resolved.effective.apiKeyHelper
|
||||
: undefined;
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to resolve settings:", error);
|
||||
this.effective = {};
|
||||
this.managedDeniedModels = [];
|
||||
this.managedApiKeyHelper = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +183,16 @@ export class SettingsManager {
|
||||
return this.effective;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `apiKeyHelper` the CLI runs: managed settings outrank the `--settings`
|
||||
* override, which outranks the user/project/local files.
|
||||
*/
|
||||
getApiKeyHelper(settingsOverride?: Settings): string | undefined {
|
||||
return (
|
||||
this.managedApiKeyHelper ?? settingsOverride?.apiKeyHelper ?? this.effective.apiKeyHelper
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `deniedModels` entries from the managed tier, the only tier the CLI
|
||||
* honors them from.
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
getSessionInfo,
|
||||
getSubagentMessages,
|
||||
type SessionMessage,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { isNativeSubagentControlTool, type HistoricalSubagent } from "./native-subagents.js";
|
||||
import { findTranscript } from "./resumed-session.js";
|
||||
|
||||
/** What the metadata file of a subagent says about it. */
|
||||
export interface SubagentRecord {
|
||||
/** The SDK agent id, the name of its transcript. */
|
||||
agentId: string;
|
||||
/** The `name` that the Agent call gave it. */
|
||||
name?: string;
|
||||
/** Its role, the `subagent_type` of the Agent call. */
|
||||
agentType?: string;
|
||||
/** The short `description` of the Agent call. */
|
||||
description?: string;
|
||||
/** Whether the Agent call ran it in the background. */
|
||||
background: boolean;
|
||||
/** Whether the user stopped it. */
|
||||
stoppedByUser: boolean;
|
||||
}
|
||||
|
||||
/** Where the subagent transcripts of a session are. */
|
||||
export interface SubagentHistory {
|
||||
/** The subagent id of each Agent or Task tool use that launched a subagent. */
|
||||
ids: Map<string, string>;
|
||||
/** The subagent of each Agent or Task tool use that launched one. */
|
||||
agents: Map<string, SubagentRecord>;
|
||||
/**
|
||||
* The project directory of the session, as the SDK takes it in `dir`. With
|
||||
* it, the SDK reads a subagent transcript without a search of every project.
|
||||
@@ -23,32 +46,278 @@ export interface SubagentHistory {
|
||||
* The ids are empty for a session without a local transcript.
|
||||
*/
|
||||
export async function subagentHistory(sessionId: string): Promise<SubagentHistory> {
|
||||
const ids = new Map<string, string>();
|
||||
const agents = new Map<string, SubagentRecord>();
|
||||
const transcript = await findTranscript(sessionId);
|
||||
if (!transcript) return { ids };
|
||||
const directory = path.join(path.dirname(transcript), sessionId, "subagents");
|
||||
if (!transcript) return { agents };
|
||||
const directory = subagentsDirectory(transcript, sessionId);
|
||||
let names: string[];
|
||||
try {
|
||||
names = await readdir(directory);
|
||||
} catch {
|
||||
return { ids };
|
||||
return { agents };
|
||||
}
|
||||
await Promise.all(
|
||||
names.map(async (name) => {
|
||||
const match = /^agent-(.+)\.meta\.json$/.exec(name);
|
||||
if (!match) return;
|
||||
try {
|
||||
const meta: unknown = JSON.parse(await readFile(path.join(directory, name), "utf8"));
|
||||
const toolUseId = (meta as { toolUseId?: unknown } | null)?.toolUseId;
|
||||
if (typeof toolUseId === "string") ids.set(toolUseId, match[1]!);
|
||||
const meta = JSON.parse(await readFile(path.join(directory, name), "utf8")) as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
if (typeof meta?.toolUseId !== "string") return;
|
||||
const text = (value: unknown) => (typeof value === "string" ? value : undefined);
|
||||
agents.set(meta.toolUseId, {
|
||||
agentId: match[1]!,
|
||||
name: text(meta.name),
|
||||
agentType: text(meta.agentType),
|
||||
description: text(meta.description),
|
||||
background: meta.requestShape === "background",
|
||||
stoppedByUser: meta.stoppedByUser === true,
|
||||
});
|
||||
} catch {
|
||||
// A damaged meta file leaves its subagent without history.
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (ids.size === 0) return { ids };
|
||||
if (agents.size === 0) return { agents };
|
||||
// The folder name encodes the project directory, and the encoding loses
|
||||
// characters. The session info reads it back from the transcript.
|
||||
const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
|
||||
return dir ? { ids, dir } : { ids };
|
||||
return dir ? { agents, dir } : { agents };
|
||||
}
|
||||
|
||||
/** The folder of the subagent transcripts, next to the session transcript `transcript`. */
|
||||
function subagentsDirectory(transcript: string, sessionId: string): string {
|
||||
return path.join(path.dirname(transcript), sessionId, "subagents");
|
||||
}
|
||||
|
||||
/**
|
||||
* The agent that the Agent or Task call `launchId` launched, from the subagent
|
||||
* history of the session `sessionId`.
|
||||
*/
|
||||
export async function subagentLaunchedBy(
|
||||
sessionId: string,
|
||||
launchId: string,
|
||||
): Promise<string | undefined> {
|
||||
return (await subagentHistory(sessionId)).agents.get(launchId)?.agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The agent `agentId` and its ancestors, root first, from the subagent history
|
||||
* of the session `sessionId`. The runtime restores an agent from it when Claude
|
||||
* Code resumes one that ran before the session was loaded or resumed.
|
||||
*
|
||||
* The metadata file of an agent names the call that launched it, but not the
|
||||
* agent that made the call. So the parent of a launch is the session whose
|
||||
* transcript has the call, as in the replay: the session's own transcript, or
|
||||
* an agent's. The list is empty when the agent or one of its ancestors has no
|
||||
* metadata, or no transcript has its launch.
|
||||
*/
|
||||
export async function subagentLineage(
|
||||
sessionId: string,
|
||||
agentId: string,
|
||||
logger: { error(...args: unknown[]): void },
|
||||
): Promise<HistoricalSubagent[]> {
|
||||
const history = await subagentHistory(sessionId);
|
||||
const launchOf = new Map<string, { launchId: string; record: SubagentRecord }>();
|
||||
for (const [launchId, record] of history.agents) {
|
||||
launchOf.set(record.agentId, { launchId, record });
|
||||
}
|
||||
if (!launchOf.has(agentId)) return [];
|
||||
|
||||
// The SDK's readers return only the active chain, which starts after the
|
||||
// last compaction, so the launch calls come from the transcript files.
|
||||
const transcript = await findTranscript(sessionId);
|
||||
if (!transcript) return [];
|
||||
const rootLaunches = await readLaunchIdsFromTranscript(transcript, null, logger);
|
||||
const agentLaunches = new Map<string, Set<string>>();
|
||||
/** The agent whose transcript has the launch `launchId`, `null` for the root's. */
|
||||
const launcherOf = async (launchId: string): Promise<string | null | undefined> => {
|
||||
if (rootLaunches.has(launchId)) return null;
|
||||
for (const candidate of launchOf.keys()) {
|
||||
let launches = agentLaunches.get(candidate);
|
||||
if (!launches) {
|
||||
launches = await readLaunchIdsFromTranscript(
|
||||
path.join(subagentsDirectory(transcript, sessionId), `agent-${candidate}.jsonl`),
|
||||
candidate,
|
||||
logger,
|
||||
);
|
||||
agentLaunches.set(candidate, launches);
|
||||
}
|
||||
if (launches.has(launchId)) return candidate;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const lineage: HistoricalSubagent[] = [];
|
||||
let current: string | null = agentId;
|
||||
while (current !== null) {
|
||||
const launch = launchOf.get(current);
|
||||
// A cycle, or an ancestor without metadata.
|
||||
if (!launch || lineage.some((entry) => entry.agentId === current)) return [];
|
||||
const launcher = await launcherOf(launch.launchId);
|
||||
if (launcher === undefined) return [];
|
||||
lineage.unshift({
|
||||
agentId: current,
|
||||
launchId: launch.launchId,
|
||||
...(launcher !== null ? { parentAgentId: launcher } : {}),
|
||||
...(launch.record.name ? { name: launch.record.name } : {}),
|
||||
...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
|
||||
...(launch.record.description ? { description: launch.record.description } : {}),
|
||||
});
|
||||
current = launcher;
|
||||
}
|
||||
return lineage;
|
||||
}
|
||||
|
||||
/** The ids of the Agent and Task calls that launched subagents in `content`. */
|
||||
export function subagentLaunchIds(content: unknown): string[] {
|
||||
if (!Array.isArray(content)) return [];
|
||||
return content.flatMap((block) =>
|
||||
typeof block === "object" &&
|
||||
block !== null &&
|
||||
block.type === "tool_use" &&
|
||||
typeof block.id === "string" &&
|
||||
isNativeSubagentControlTool(block.name)
|
||||
? [block.id as string]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ids of the launch calls in the transcript `file`, every record of it,
|
||||
* including those before a compaction. `owner` is the agent the transcript
|
||||
* belongs to, null for the session's. The session's transcript can hold
|
||||
* sidechain records, which are an agent's, not the root's; an agent's
|
||||
* transcript keeps only the records of that agent. A file that cannot be read
|
||||
* has no launches, so the agent is not placed under the root.
|
||||
*/
|
||||
async function readLaunchIdsFromTranscript(
|
||||
file: string,
|
||||
owner: string | null,
|
||||
logger: { error(...args: unknown[]): void },
|
||||
): Promise<Set<string>> {
|
||||
const launches = new Set<string>();
|
||||
const stream = createReadStream(file, { encoding: "utf8" });
|
||||
try {
|
||||
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
|
||||
const forkDirective = owner !== null && line.includes(FORK_BOILERPLATE_OPEN);
|
||||
if (!line.includes('"tool_use"') && !forkDirective) continue;
|
||||
let record: {
|
||||
type?: unknown;
|
||||
isSidechain?: unknown;
|
||||
agentId?: unknown;
|
||||
message?: { content?: unknown };
|
||||
} | null;
|
||||
try {
|
||||
record = JSON.parse(line);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
// Every record of a fork child's transcript before its directive message
|
||||
// is inherited context: a copy of the launching message, whose launch
|
||||
// calls are the parent's, not the fork's.
|
||||
if (forkDirective && record !== null && isForkDirectiveMessage(record)) {
|
||||
launches.clear();
|
||||
continue;
|
||||
}
|
||||
if (record?.type !== "assistant") continue;
|
||||
if (
|
||||
owner === null
|
||||
? record.isSidechain === true
|
||||
: record.agentId !== undefined && record.agentId !== owner
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (const id of subagentLaunchIds(record.message?.content)) launches.add(id);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to read the transcript ${file}:`, error);
|
||||
return new Set();
|
||||
} finally {
|
||||
stream.destroy();
|
||||
}
|
||||
return launches;
|
||||
}
|
||||
|
||||
/**
|
||||
* The transcript of the subagent `agentId` of a session, or an empty list
|
||||
* when it cannot be read. With the project directory, the SDK skips its
|
||||
* search of every project; a session opened from another directory falls
|
||||
* back to that search.
|
||||
*/
|
||||
export async function readSubagentMessages(
|
||||
sessionId: string,
|
||||
agentId: string,
|
||||
history: SubagentHistory,
|
||||
logger: { error(...args: unknown[]): void },
|
||||
): Promise<SessionMessage[]> {
|
||||
try {
|
||||
if (history.dir) {
|
||||
const messages = await getSubagentMessages(sessionId, agentId, { dir: history.dir });
|
||||
if (messages.length > 0) return stripForkPrefix(messages);
|
||||
}
|
||||
return stripForkPrefix(await getSubagentMessages(sessionId, agentId));
|
||||
} catch (error) {
|
||||
logger.error(`Failed to read the history of subagent ${agentId}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** How Claude Code opens the directive message of a `subagent_type: "fork"` child. */
|
||||
const FORK_BOILERPLATE_OPEN = "<fork-boilerplate>";
|
||||
/** What follows the boilerplate, right before the prompt of the Agent call. */
|
||||
const FORK_DIRECTIVE_MARKER = "</fork-boilerplate>\nYour directive: ";
|
||||
|
||||
/** Whether `block` is the text block of a fork child's directive message. */
|
||||
function isForkDirectiveText(block: unknown): block is { type: "text"; text: string } {
|
||||
return (
|
||||
typeof block === "object" &&
|
||||
block !== null &&
|
||||
(block as { type?: unknown }).type === "text" &&
|
||||
typeof (block as { text?: unknown }).text === "string" &&
|
||||
(block as { text: string }).text.startsWith(FORK_BOILERPLATE_OPEN)
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the transcript record `message` is the directive message of a fork child. */
|
||||
function isForkDirectiveMessage(message: { type?: unknown; message?: unknown }): boolean {
|
||||
if (message.type !== "user") return false;
|
||||
const content = (message.message as { content?: unknown } | null | undefined)?.content;
|
||||
return Array.isArray(content) && content.some(isForkDirectiveText);
|
||||
}
|
||||
|
||||
/**
|
||||
* The transcript of a `subagent_type: "fork"` child without the context it
|
||||
* inherited. A fork transcript starts with a copy of the parent's launching
|
||||
* assistant message (its text, thinking and every tool_use, including the
|
||||
* Agent call itself), then a user message with a "Fork started — processing
|
||||
* in background" tool_result for each of those tool_uses and the text
|
||||
* `<fork-boilerplate>…</fork-boilerplate>\nYour directive: <prompt>`. A fork
|
||||
* launched from inside a subagent also gets a copy of that subagent's history
|
||||
* before them. None of it is the child's own work: replayed as is, the
|
||||
* parent's text and tool calls would show again in the child.
|
||||
*
|
||||
* This drops every record before the directive message, and reduces that
|
||||
* message to the prompt. Other transcripts are returned unchanged.
|
||||
*/
|
||||
export function stripForkPrefix(messages: SessionMessage[]): SessionMessage[] {
|
||||
const index = messages.findIndex(isForkDirectiveMessage);
|
||||
if (index === -1) return messages;
|
||||
const directive = messages[index];
|
||||
const content = (directive.message as { content: unknown[] }).content;
|
||||
const prompt = content.flatMap((block) => {
|
||||
if (!isForkDirectiveText(block)) return [];
|
||||
const start = block.text.indexOf(FORK_DIRECTIVE_MARKER);
|
||||
return start === -1
|
||||
? []
|
||||
: [{ type: "text", text: block.text.slice(start + FORK_DIRECTIVE_MARKER.length) }];
|
||||
});
|
||||
const rest = messages.slice(index + 1);
|
||||
if (prompt.length === 0) return rest;
|
||||
return [
|
||||
{ ...directive, message: { ...(directive.message as object), content: prompt } },
|
||||
...rest,
|
||||
];
|
||||
}
|
||||
|
||||
421
src/subagent-replay.ts
Normal file
421
src/subagent-replay.ts
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* The replay of subagents to a client on the subagents RFD form.
|
||||
*
|
||||
* Claude Code keeps each subagent's history in its own transcript, and the
|
||||
* subagent's metadata file names the Agent or Task call that launched it (see
|
||||
* `subagentHistory`). The replay announces each child right after that call,
|
||||
* under the session id it had live, the SDK agent id, and sends the child's
|
||||
* history to the child session. Every child's end goes out before the replay
|
||||
* ends, since no subagent from before the reload still runs.
|
||||
*
|
||||
* As live, the parent is not shown the Agent or Task call of a child that the
|
||||
* replay announces: the launch's prompt is the parent's outgoing message, and
|
||||
* a foreground child's answer is its reply to the parent. A child's
|
||||
* successful SubagentHandback call is its message to the parent instead.
|
||||
* Unlike live, the replay does not tell runs apart, so a child that handed
|
||||
* back any report gets no reply from its launch's result.
|
||||
*
|
||||
* A launch without metadata has no known id, so it is not announced: the RFD
|
||||
* forbids inventing one. Its Agent call stays an ordinary tool call of the
|
||||
* parent, and a notice says that subagent history is missing.
|
||||
*/
|
||||
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
|
||||
import {
|
||||
HANDBACK_TOOL,
|
||||
handbackMessage,
|
||||
isNativeSubagentControlTool,
|
||||
launchReply,
|
||||
linkedSessionMessages,
|
||||
nativeSubagentState,
|
||||
rfdEndState,
|
||||
rfdLabels,
|
||||
type WorkState,
|
||||
} from "./native-subagents.js";
|
||||
import { noticeUpdate } from "./session-notices.js";
|
||||
import type { SubagentHistory, SubagentRecord } from "./subagent-history.js";
|
||||
|
||||
type Publish = (notification: AcpSessionNotification) => Promise<void>;
|
||||
|
||||
/** A child that the replay shows: its launch has metadata. */
|
||||
export type ReplayChild = {
|
||||
/** The Agent or Task tool use that launched it. */
|
||||
launchId: string;
|
||||
/** Its session id: the SDK agent id, as live. */
|
||||
sessionId: string;
|
||||
record: SubagentRecord;
|
||||
/** The session that made the launch. */
|
||||
parentSessionId: string;
|
||||
/** The input of the launch call. */
|
||||
input: Record<string, unknown>;
|
||||
/** How the launch's tool result ended a foreground child's run. */
|
||||
launchEnd?: SubagentState;
|
||||
/** The state of the child's last persisted task notification. */
|
||||
notified?: SubagentState;
|
||||
announced: boolean;
|
||||
promptSent: boolean;
|
||||
/** Whether it handed a report to its parent with a SubagentHandback call. */
|
||||
handedBack?: boolean;
|
||||
};
|
||||
|
||||
type SendMessageCall = {
|
||||
toolUseId: string;
|
||||
to: string;
|
||||
message: string;
|
||||
senderSessionId: string;
|
||||
delivered: 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 }
|
||||
>();
|
||||
|
||||
constructor(
|
||||
private readonly history: SubagentHistory,
|
||||
private readonly publish: Publish,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
for (const message of messages) {
|
||||
for (const block of contentBlocks(message)) {
|
||||
if (block.type === "tool_use" && typeof block.id === "string") {
|
||||
if (isNativeSubagentControlTool(block.name)) {
|
||||
this.registerLaunch(block.id, inputOf(block), ownerSessionId);
|
||||
} else if (block.name === HANDBACK_TOOL) {
|
||||
const message = handbackMessage(block.input);
|
||||
if (message) this.handbacks.set(block.id, { message, failed: false, sent: false });
|
||||
} else if (block.name === "SendMessage") {
|
||||
const input = inputOf(block);
|
||||
if (typeof input.to === "string" && typeof input.message === "string") {
|
||||
this.sendMessages.push({
|
||||
toolUseId: block.id,
|
||||
to: input.to,
|
||||
message: input.message,
|
||||
senderSessionId: ownerSessionId,
|
||||
delivered: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
||||
const handback = this.handbacks.get(block.tool_use_id);
|
||||
if (handback && block.is_error === true) handback.failed = true;
|
||||
const child = this.children.get(block.tool_use_id);
|
||||
// A background launch answers at once; its result is not the end.
|
||||
if (child && !child.record.background) {
|
||||
child.launchEnd = replaySubagentTerminalState(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The child that the tool use `toolUseId` launched, when the replay shows it. */
|
||||
childOfLaunch(toolUseId: string): ReplayChild | undefined {
|
||||
return this.children.get(toolUseId);
|
||||
}
|
||||
|
||||
/** Announces `child` on its parent, as live: running, with the parent's labels. */
|
||||
async announce(child: ReplayChild): Promise<void> {
|
||||
if (child.announced) return;
|
||||
const labels = rfdLabels(
|
||||
child.record.name ?? child.input.name,
|
||||
child.record.agentType ?? child.input.subagent_type,
|
||||
child.record.description ?? child.input.description,
|
||||
);
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: {
|
||||
sessionUpdate: "subagent_update",
|
||||
sessionId: child.sessionId,
|
||||
...(labels.title ? { title: labels.title } : {}),
|
||||
...(labels.description ? { description: labels.description } : {}),
|
||||
state: { state: "running" },
|
||||
},
|
||||
});
|
||||
child.announced = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rest of a child's user message to replay, or `null` when nothing is
|
||||
* left, after its text went out as the child's incoming `session_message`.
|
||||
*
|
||||
* A child session has no user, so the text of its user messages is never a
|
||||
* user message. The first text is the prompt of the launch. A message that
|
||||
* a SendMessage call delivered (`origin.kind: "coordinator"`) is another
|
||||
* incoming message. Any other text is harness text, such as "[Request
|
||||
* interrupted by user for tool use]", which the live child session does not
|
||||
* show. Tool results stay.
|
||||
*/
|
||||
async childContent(
|
||||
child: ReplayChild,
|
||||
message: SessionMessage,
|
||||
content: unknown,
|
||||
): Promise<unknown> {
|
||||
const text = textOf(content);
|
||||
if (text.trim()) {
|
||||
if (isDelivery(message)) await this.sendDelivery(child, message, text);
|
||||
else if (!child.promptSent) await this.sendPrompt(child, text);
|
||||
}
|
||||
if (!Array.isArray(content)) return null;
|
||||
const rest = content.filter(
|
||||
(block) => !(typeof block === "object" && block !== null && block.type === "text"),
|
||||
);
|
||||
return rest.length > 0 ? rest : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the replay of `child`'s history. `messages` is its transcript: when
|
||||
* it is empty, the replay has only the child's association, and the prompt
|
||||
* comes from the launch call.
|
||||
*/
|
||||
async childReplayed(child: ReplayChild, messages: readonly SessionMessage[]): Promise<void> {
|
||||
if (messages.length === 0) this.withoutHistory++;
|
||||
if (!child.promptSent && typeof child.input.prompt === "string") {
|
||||
await this.sendPrompt(child, child.input.prompt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a replayed update of a tool call, from the transcript of `owner`
|
||||
* when it is a child's. Returns whether the client is not shown it: an
|
||||
* update of the launch of a child that the replay shows, or of a successful
|
||||
* SubagentHandback call of `owner`. A foreground launch's completed result
|
||||
* goes out as the child's reply, and a hand-back as the child's message to
|
||||
* its parent.
|
||||
*/
|
||||
async toolCallUpdate(
|
||||
update: AcpSessionNotification["update"],
|
||||
owner: ReplayChild | undefined,
|
||||
): Promise<boolean> {
|
||||
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
||||
return false;
|
||||
}
|
||||
const handback = owner ? this.handbacks.get(update.toolCallId) : undefined;
|
||||
if (owner && handback && !handback.failed) {
|
||||
if (!handback.sent) {
|
||||
handback.sent = true;
|
||||
owner.handedBack = true;
|
||||
for (const view of linkedSessionMessages(
|
||||
update.toolCallId,
|
||||
owner.sessionId,
|
||||
owner.parentSessionId,
|
||||
handback.message,
|
||||
)) {
|
||||
await this.publish(view);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const child = this.children.get(update.toolCallId);
|
||||
if (!child) return false;
|
||||
if (
|
||||
update.status === "completed" &&
|
||||
child.announced &&
|
||||
!child.record.background &&
|
||||
!child.handedBack
|
||||
) {
|
||||
const reply = launchReply(child.launchId, child.sessionId, child.parentSessionId, update);
|
||||
if (reply) await this.publish(reply);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a persisted task notification. Returns whether it was a child's:
|
||||
* its task id is the agent id.
|
||||
*/
|
||||
taskNotification(taskId: string, status: string): boolean {
|
||||
const child = this.byAgentId.get(taskId);
|
||||
if (!child) return false;
|
||||
child.notified = nativeSubagentState(status) ?? child.notified;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the end of every announced child, and a notice when subagent history
|
||||
* is missing. The end is the state of the child's last task notification,
|
||||
* else `cancelled` for a child the user stopped, else the result of its
|
||||
* foreground launch, else `idle` with no stop reason.
|
||||
*/
|
||||
async finish(supportsNotices: boolean, rootSessionId: string): Promise<void> {
|
||||
for (const child of [...this.children.values()].reverse()) {
|
||||
if (!child.announced) continue;
|
||||
const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
|
||||
const state: WorkState = end ? rfdEndState(end, undefined) : { state: "idle" };
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, state },
|
||||
});
|
||||
}
|
||||
const missing = this.omitted.size + this.withoutHistory;
|
||||
if (missing > 0 && supportsNotices) {
|
||||
await this.publish({
|
||||
sessionId: rootSessionId,
|
||||
update: noticeUpdate({
|
||||
severity: "info",
|
||||
title: "Some subagent history is not available",
|
||||
description:
|
||||
missing === 1
|
||||
? "The history of 1 subagent of this session could not be restored."
|
||||
: `The history of ${missing} subagents of this session could not be restored.`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
for (const view of linkedSessionMessages(
|
||||
child.launchId,
|
||||
child.parentSessionId,
|
||||
child.sessionId,
|
||||
text,
|
||||
)) {
|
||||
await this.publish(view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A message that a SendMessage call delivered. Claude Code frames it for the
|
||||
* child's model ("The coordinator sent a message while you were working:",
|
||||
* then the message, then an instruction), so the call it came from is the
|
||||
* first undelivered SendMessage to the child whose message the delivered
|
||||
* text contains. With that call, the message has the call's id and text and
|
||||
* its sender, as live; without it, its transcript id and the delivered text.
|
||||
*/
|
||||
private async sendDelivery(
|
||||
child: ReplayChild,
|
||||
message: SessionMessage,
|
||||
text: string,
|
||||
): Promise<void> {
|
||||
const names = [child.sessionId, child.record.name, child.input.name];
|
||||
const call = this.sendMessages.find(
|
||||
(candidate) =>
|
||||
!candidate.delivered &&
|
||||
names.includes(candidate.to) &&
|
||||
text.includes(candidate.message.trim()),
|
||||
);
|
||||
if (call) {
|
||||
call.delivered = true;
|
||||
await this.sendMessage(child, call.toolUseId, call.message, call.senderSessionId);
|
||||
} else {
|
||||
await this.sendMessage(child, message.uuid, text, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendMessage(
|
||||
child: ReplayChild,
|
||||
messageId: string,
|
||||
text: string,
|
||||
sender: string | undefined,
|
||||
): Promise<void> {
|
||||
await this.publish({
|
||||
sessionId: child.sessionId,
|
||||
update: {
|
||||
sessionUpdate: "session_message",
|
||||
messageId,
|
||||
...(sender ? { senderSessionId: sender } : {}),
|
||||
recipientSessionId: child.sessionId,
|
||||
content: [{ type: "text", text }],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The end of a foreground launch from its tool result: an error result that
|
||||
* names a stop is a cancellation, any other error a failure.
|
||||
*/
|
||||
export function replaySubagentTerminalState(
|
||||
block: Record<string, unknown>,
|
||||
): "completed" | "failed" | "cancelled" {
|
||||
if (block.is_error !== true) return "completed";
|
||||
const text = replayContentText(block.content).toLowerCase();
|
||||
return /\b(?:cancelled|canceled|interrupted|stopped|killed)\b/.test(text)
|
||||
? "cancelled"
|
||||
: "failed";
|
||||
}
|
||||
|
||||
function replayContentText(value: unknown, seen = new Set<unknown>()): string {
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value !== "object" || value === null || seen.has(value)) return "";
|
||||
seen.add(value);
|
||||
if (Array.isArray(value)) return value.map((item) => replayContentText(item, seen)).join(" ");
|
||||
const record = value as Record<string, unknown>;
|
||||
return [record.text, record.content, record.message]
|
||||
.map((item) => replayContentText(item, seen))
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function contentBlocks(message: SessionMessage): Record<string, unknown>[] {
|
||||
const content = (message as { message?: { content?: unknown } }).message?.content;
|
||||
return Array.isArray(content)
|
||||
? content.filter(
|
||||
(block): block is Record<string, unknown> => typeof block === "object" && block !== null,
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
function inputOf(block: Record<string, unknown>): Record<string, unknown> {
|
||||
return typeof block.input === "object" && block.input !== null
|
||||
? (block.input as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
/** The text of user message content: a string, or its text blocks. */
|
||||
function textOf(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
return content
|
||||
.flatMap((block) =>
|
||||
typeof block === "object" && block !== null && block.type === "text" ? [block.text] : [],
|
||||
)
|
||||
.filter((text): text is string => typeof text === "string")
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/** A message that a SendMessage call delivered to the child. */
|
||||
function isDelivery(message: SessionMessage): boolean {
|
||||
return (message as { origin?: { kind?: unknown } | null }).origin?.kind === "coordinator";
|
||||
}
|
||||
75
src/task-notifications.ts
Normal file
75
src/task-notifications.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* The `<task-notification>` inputs that Claude Code writes to a transcript
|
||||
* when a background task stops. The replay of a session (`acp-agent.ts`) and
|
||||
* the session index (`session-index/background-tasks.ts`) read them here, so
|
||||
* both read the same fields.
|
||||
*/
|
||||
|
||||
export const TASK_NOTIFICATION_TAG = "task-notification";
|
||||
|
||||
/** The text between the first `<tag>` and the next `</tag>` after it. */
|
||||
export function markerTagText(text: string, tag: string): string | undefined {
|
||||
const open = `<${tag}>`;
|
||||
const close = `</${tag}>`;
|
||||
const start = text.indexOf(open);
|
||||
if (start === -1) return undefined;
|
||||
const end = text.indexOf(close, start + open.length);
|
||||
if (end === -1) return undefined;
|
||||
return text.slice(start + open.length, end);
|
||||
}
|
||||
|
||||
/** The fields of a persisted `<task-notification>`, in the shape of the SDK
|
||||
* `task_notification` frame that reported the same stop live. */
|
||||
export type PersistedTaskNotification = {
|
||||
task_id: string;
|
||||
status: string;
|
||||
tool_use_id?: string;
|
||||
summary?: string;
|
||||
output_file?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns each `<task-notification>` in user-message content (a string or its
|
||||
* text blocks). The `<result>` body is model context that the live frame does
|
||||
* not carry, so the scan stops at it and a tag quoted in it cannot match.
|
||||
*
|
||||
* Only a notification with a `<task-id>` and a `<status>` is returned. A
|
||||
* notification without a status comes while its task still runs: the
|
||||
* check-in of a quiet shell ("is still running after …") or a `Monitor`
|
||||
* event.
|
||||
*/
|
||||
export function taskNotificationsOf(content: unknown): PersistedTaskNotification[] {
|
||||
const texts =
|
||||
typeof content === "string"
|
||||
? [content]
|
||||
: Array.isArray(content)
|
||||
? content.flatMap((block) =>
|
||||
block?.type === "text" && typeof block.text === "string" ? [block.text as string] : [],
|
||||
)
|
||||
: [];
|
||||
const open = `<${TASK_NOTIFICATION_TAG}>`;
|
||||
const close = `</${TASK_NOTIFICATION_TAG}>`;
|
||||
const notifications: PersistedTaskNotification[] = [];
|
||||
for (const text of texts) {
|
||||
let start = text.indexOf(open);
|
||||
while (start !== -1) {
|
||||
const end = text.indexOf(close, start + open.length);
|
||||
if (end === -1) break;
|
||||
const body = text.slice(start + open.length, end).split("<result>", 1)[0];
|
||||
const field = (tag: string) => markerTagText(body, tag)?.trim() || undefined;
|
||||
const taskId = field("task-id");
|
||||
const status = field("status");
|
||||
if (taskId && status) {
|
||||
notifications.push({
|
||||
task_id: taskId,
|
||||
status,
|
||||
tool_use_id: field("tool-use-id"),
|
||||
summary: field("summary"),
|
||||
output_file: field("output-file"),
|
||||
});
|
||||
}
|
||||
start = text.indexOf(open, end + close.length);
|
||||
}
|
||||
}
|
||||
return notifications;
|
||||
}
|
||||
@@ -515,7 +515,7 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
_meta: { disableBuiltInTools: true },
|
||||
});
|
||||
await agent.setSessionMode({ sessionId, modeId: "default" });
|
||||
const { RequestError } = await import("@agentclientprotocol/sdk");
|
||||
const { RequestError } = await import("@agentclientprotocol/sdk/unstable");
|
||||
const createSession = (agent as any).createSession.bind(agent);
|
||||
const creations: object[] = [];
|
||||
(agent as any).createSession = async (params: object, options: { resume?: string }) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolName":"mcp__docs__search"},"is_mcp_tool_call":true},"content":[],"kind":"other","name":"mcp__docs__search","sessionUpdate":"tool_call","status":"pending","title":"mcp__docs__search","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolName":"mcp__docs__search"},"is_mcp_tool_call":true,"jetbrains":{"air":{"mcp":{"server":"docs","tool":"search"},"version":1}}},"content":[],"kind":"other","name":"mcp__docs__search","sessionUpdate":"tool_call","status":"pending","title":"mcp__docs__search","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"rawInput":{"q":"acp"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"requestPermission","payload":{"_meta":{"jetbrains":{"air":{"permission":{"title":"mcp__docs__search","version":1},"version":1}}},"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","toolCall":{"_meta":{"claudeCode":{"mcpServer":{"name":"docs","source":"project"}}},"rawInput":{"q":"acp"},"title":"mcp__docs__search","toolCallId":"toolu_mcp"}}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Check the file.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"newSession","payload":{"configOptions":[{"category":"mode","currentValue":"default","description":"Session permission mode","id":"mode","name":"Mode","options":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","name":"Manual","value":"default"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","id":"model","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"modes":{"availableModes":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","id":"default","name":"Manual"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","id":"acceptEdits","name":"Accept edits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","id":"plan","name":"Plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","id":"auto","name":"Auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","id":"bypassPermissions","name":"Bypass permissions"}],"currentModeId":"default"},"sessionId":"<session>"}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolName":"Agent","toolResponse":{"isAsync":true,"status":"async_launched"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"capabilities":{},"name":"Background review","prompt":"Review the diff","sessionUpdate":"subagent_spawned","subagentSessionId":"a2","task":"Review the diff"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"content":[{"newText":"b","oldText":"a","path":"<cwd>/x.ts","type":"diff"}],"rawInput":{"file_path":"<cwd>/x.ts"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"requestPermission","payload":{"_meta":{"jetbrains":{"air":{"permission":{"title":"Edit x.ts","version":1},"version":1}}},"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"agent_1","toolCall":{"rawInput":{"file_path":"<cwd>/x.ts"},"title":"Edit x.ts","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"rawOutput":"The file has been updated.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/x.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"locations":[{"line":1,"path":"<cwd>/x.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Foun","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"d it.","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolName":"Task","toolResponse":{"status":"completed"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_task"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"sessionUpdate":"subagent_state_update","state":"completed","subagentSessionId":"agent_1"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"promptResponse","payload":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"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":"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"}}}
|
||||
@@ -16,4 +16,6 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Report.","type":"text"},"type":"content"}],"rawOutput":"Report.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_r_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r2","sessionUpdate":"agent_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Fixed.","type":"text"},"messageId":"msg_r2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"description":"The history of 1 subagent of this session could not be restored.","sessionUpdate":"notice","severity":"info","title":"Some subagent history is not available"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"idle"}}}
|
||||
{"kind":"response","method":"session/resume","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}]}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review"},"sessionUpdate":"tool_call_update","title":"Background review","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review","prompt":"Review the diff"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Background review","prompt":"Review the diff","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Review the diff","type":"text"},"type":"content"}],"rawInput":{"description":"Background review","prompt":"Review the diff","run_in_background":true,"subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Async agent launched successfully.","type":"text"},"type":"content"}],"rawOutput":"Async agent launched successfully.\nagentId: a2 (use SendMessage to continue)","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"isAsync":true,"status":"async_launched"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_agent"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Background review","sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"general-purpose"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientMessageId":"toolu_agent","recipientSessionId":"a2","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientSessionId":"a2","senderMessageId":"toolu_agent","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_late"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Check the logs"},"sessionUpdate":"tool_call_update","title":"Check the logs","toolCallId":"toolu_late"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Check the logs","prompt":"Check logs"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Check logs","type":"text"},"type":"content"}],"rawInput":{"description":"Check the logs","prompt":"Check logs","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_late"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"The logs are clean.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The logs are clean.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolResponse":{"elapsedTimeSeconds":4}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n1\tok\n```","type":"text"},"type":"content"}],"rawOutput":"1\tok","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Check the logs","sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"general-purpose"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientMessageId":"toolu_late","recipientSessionId":"agent_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientSessionId":"agent_late","senderMessageId":"toolu_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"The logs are clean.","type":"text"}],"messageId":"toolu_late:reply","recipientSessionId":"<session>","senderSessionId":"agent_late","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolResponse":{"elapsedTimeSeconds":4}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"content":{"text":"```\n1\tok\n```","type":"text"},"type":"content"}],"rawOutput":"1\tok","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_native"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Fix the build"},"sessionUpdate":"tool_call_update","title":"Fix the build","toolCallId":"toolu_native"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Fix the build","prompt":"Make the build pass"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_native"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Make the build pass","type":"text"},"type":"content"}],"rawInput":{"description":"Fix the build","prompt":"Make the build pass","subagent_type":"general-purpose"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_native"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"Running t","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"he build.","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[],"kind":"search","name":"Grep","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"grep","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"TODO"},"sessionUpdate":"tool_call_update","title":"grep \"TODO\"","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"path":"/src","pattern":"TODO"},"sessionUpdate":"tool_call_update","title":"grep \"TODO\" /src","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"src/a.ts","type":"text"},"type":"content"}],"rawOutput":"src/a.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[{"terminalId":"toolu_native_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"command":"make","sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"make"},"sessionUpdate":"tool_call_update","title":"make","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"make","description":"Build"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Fix the build","sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"general-purpose"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Make the build pass","type":"text"}],"messageId":"toolu_native","recipientMessageId":"toolu_native","recipientSessionId":"agent_n","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"content":[{"text":"Make the build pass","type":"text"}],"messageId":"toolu_native","recipientSessionId":"agent_n","senderMessageId":"toolu_native","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"Running t","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":{"text":"he build.","type":"text"},"messageId":"msg_native_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[],"kind":"search","name":"Grep","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"grep","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"rawInput":{"pattern":"TODO"},"sessionUpdate":"tool_call_update","title":"grep \"TODO\"","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"rawInput":{"path":"/src","pattern":"TODO"},"sessionUpdate":"tool_call_update","title":"grep \"TODO\" /src","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"content":[{"content":{"text":"src/a.ts","type":"text"},"type":"content"}],"rawOutput":"src/a.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_native_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[{"terminalId":"toolu_native_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"command":"make","sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"rawInput":{"command":"make"},"sessionUpdate":"tool_call_update","title":"make","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"rawInput":{"command":"make","description":"Build"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[{"terminalId":"toolu_native_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"make","description":"Build"},"status":"pending","title":"make","toolCallId":"toolu_native_bash"},"type":"tool_call"},"title":"make"}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"agent_n","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_native"}},"content":[{"terminalId":"toolu_native_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"make","description":"Build"},"status":"pending","title":"make","toolCallId":"toolu_native_bash"},"type":"tool_call"},"title":"make"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"output":{"data":"bWFrZTogKioqIEVycm9yIDI="},"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\nThe build still fails.\n```","type":"text"},"type":"content"}],"rawOutput":[{"text":"The build still fails.","type":"text"}],"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_native"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"output":{"data":"bWFrZTogKioqIEVycm9yIDI="},"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_native_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"error"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"error"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_outer"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Plan the work"},"sessionUpdate":"tool_call_update","title":"Plan the work","toolCallId":"toolu_outer"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Plan the work","prompt":"Plan it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_outer"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Plan it","type":"text"},"type":"content"}],"rawInput":{"description":"Plan the work","prompt":"Plan it","subagent_type":"Plan"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_outer"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":[],"kind":"think","name":"Agent","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_inner"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Read the spec"},"sessionUpdate":"tool_call_update","title":"Read the spec","toolCallId":"toolu_inner"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Read the spec","prompt":"Read spec.md"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_inner"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Read spec.md","type":"text"},"type":"content"}],"rawInput":{"description":"Read the spec","prompt":"Read spec.md","subagent_type":"Explore"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_inner"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"/spec.md"}],"rawInput":{"file_path":"/spec.md"},"sessionUpdate":"tool_call_update","title":"Read /spec.md","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n1\tspec\n```","type":"text"},"type":"content"}],"rawOutput":"1\tspec","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"The spec says X.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The spec says X.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_inner"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":"Plan:","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":" do X.","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Plan: do X.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Plan: do X.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_outer"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Plan the work","sessionId":"agent_outer","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Plan it","type":"text"}],"messageId":"toolu_outer","recipientMessageId":"toolu_outer","recipientSessionId":"agent_outer","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"content":[{"text":"Plan it","type":"text"}],"messageId":"toolu_outer","recipientSessionId":"agent_outer","senderMessageId":"toolu_outer","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"capabilities":{"cancel":{}},"description":"Read the spec","sessionId":"agent_inner","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Explore"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"content":[{"text":"Read spec.md","type":"text"}],"messageId":"toolu_inner","recipientMessageId":"toolu_inner","recipientSessionId":"agent_inner","senderSessionId":"agent_outer","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"content":[{"text":"Read spec.md","type":"text"}],"messageId":"toolu_inner","recipientSessionId":"agent_inner","senderMessageId":"toolu_inner","senderSessionId":"agent_outer","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_inner"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"locations":[{"line":1,"path":"/spec.md"}],"rawInput":{"file_path":"/spec.md"},"sessionUpdate":"tool_call_update","title":"Read /spec.md","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"content":[{"content":{"text":"```\n1\tspec\n```","type":"text"},"type":"content"}],"rawOutput":"1\tspec","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_grandchild_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"content":[{"text":"The spec says X.","type":"text"}],"messageId":"toolu_inner:reply","recipientSessionId":"agent_outer","senderSessionId":"agent_inner","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"sessionId":"agent_inner","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":"Plan:","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"content":{"text":" do X.","type":"text"},"messageId":"msg_outer_text","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Plan: do X.","type":"text"}],"messageId":"toolu_outer:reply","recipientSessionId":"<session>","senderSessionId":"agent_outer","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_outer","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Task","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Explore code"},"sessionUpdate":"tool_call_update","title":"Explore code","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Explore code","prompt":"Find the parser"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Find the parser","type":"text"},"type":"content"}],"rawInput":{"description":"Explore code","prompt":"Find the parser","subagent_type":"Explore"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Looking for","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":" the parser.","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"/x.ts"}],"rawInput":{"file_path":"/x.ts"},"sessionUpdate":"tool_call_update","title":"Read /x.ts","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"elapsedTimeSeconds":3,"subagentType":"Explore"}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n1\tparser\n```","type":"text"},"type":"content"}],"rawOutput":"1\tparser","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[{"terminalId":"toolu_sub_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"command":"rg parser","sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"rg parser"},"sessionUpdate":"tool_call_update","title":"rg parser","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"rg parser","description":"Search"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Explore code","sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Explore"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Find the parser","type":"text"}],"messageId":"toolu_task","recipientMessageId":"toolu_task","recipientSessionId":"agent_1","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"content":[{"text":"Find the parser","type":"text"}],"messageId":"toolu_task","recipientSessionId":"agent_1","senderMessageId":"toolu_task","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Looking for","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":" the parser.","type":"text"},"messageId":"msg_sub_1","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"locations":[{"line":1,"path":"/x.ts"}],"rawInput":{"file_path":"/x.ts"},"sessionUpdate":"tool_call_update","title":"Read /x.ts","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"content":[{"content":{"text":"```\n1\tparser\n```","type":"text"},"type":"content"}],"rawOutput":"1\tparser","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[{"terminalId":"toolu_sub_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"command":"rg parser","sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"rawInput":{"command":"rg parser"},"sessionUpdate":"tool_call_update","title":"rg parser","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"rawInput":{"command":"rg parser","description":"Search"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[{"terminalId":"toolu_sub_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"rg parser","description":"Search"},"status":"pending","title":"rg parser","toolCallId":"toolu_sub_bash"},"type":"tool_call"},"title":"rg parser"}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"agent_1","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[{"terminalId":"toolu_sub_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"rg parser","description":"Search"},"status":"pending","title":"rg parser","toolCallId":"toolu_sub_bash"},"type":"tool_call"},"title":"rg parser"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"output":{"data":"eC50cw=="},"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/x.ts"}],"rawInput":{"file_path":"<cwd>/x.ts"},"sessionUpdate":"tool_call_update","title":"Edit x.ts","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/x.ts","old_string":"a"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/x.ts","new_string":"b","old_string":"a"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"output":{"data":"eC50cw=="},"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"locations":[{"path":"<cwd>/x.ts"}],"rawInput":{"file_path":"<cwd>/x.ts"},"sessionUpdate":"tool_call_update","title":"Edit x.ts","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"rawInput":{"file_path":"<cwd>/x.ts","old_string":"a"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"rawInput":{"file_path":"<cwd>/x.ts","new_string":"b","old_string":"a"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"edit","locations":[{"path":"<cwd>/x.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/x.ts","new_string":"b","old_string":"a"},"status":"pending","title":"Edit x.ts","toolCallId":"toolu_sub_edit"},"type":"tool_call"},"title":"Edit x.ts"}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"agent_1","subject":{"toolCall":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":[],"kind":"edit","locations":[{"path":"<cwd>/x.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/x.ts","new_string":"b","old_string":"a"},"status":"pending","title":"Edit x.ts","toolCallId":"toolu_sub_edit"},"type":"tool_call"},"title":"Edit x.ts"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"The file has been updated.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/x.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/x.ts <cwd>/x.ts\n--- <cwd>/x.ts\n+++ <cwd>/x.ts\n@@ -1 +1 @@\n-a\n+b\n"},"type":"diff"}],"locations":[{"line":1,"path":"<cwd>/x.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Foun","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"d it.","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"The parser is in x.ts.","type":"text"},"type":"content"}],"rawOutput":[{"text":"The parser is in x.ts.\nagentId: a1 (use SendMessage to continue)","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"status":"completed"}}},"sessionUpdate":"tool_call_update","toolCallId":"toolu_task"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"rawOutput":"The file has been updated.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/x.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/x.ts <cwd>/x.ts\n--- <cwd>/x.ts\n+++ <cwd>/x.ts\n@@ -1 +1 @@\n-a\n+b\n"},"type":"diff"}],"locations":[{"line":1,"path":"<cwd>/x.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_sub_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"Foun","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_task"}},"content":{"text":"d it.","type":"text"},"messageId":"msg_sub_2","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"The parser is in x.ts.","type":"text"}],"messageId":"toolu_task:reply","recipientSessionId":"<session>","senderSessionId":"agent_1","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"think","name":"Task","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Task","toolCallId":"toolu_transcript"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Summarize"},"sessionUpdate":"tool_call_update","title":"Summarize","toolCallId":"toolu_transcript"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"description":"Summarize","prompt":"Summarize the repo"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_transcript"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Summarize the repo","type":"text"},"type":"content"}],"rawInput":{"description":"Summarize","prompt":"Summarize the repo","subagent_type":"Explore"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_transcript"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"Look at t","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"he layout.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"The repo has ","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"two packages.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":[],"kind":"search","locations":[],"name":"Glob","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Find","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"*"},"sessionUpdate":"tool_call_update","title":"Find `*`","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"a\nb","type":"text"},"type":"content"}],"rawOutput":"a\nb","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Two packages.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Two packages.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_transcript"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Summarize","sessionId":"agent_t","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Explore"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Summarize the repo","type":"text"}],"messageId":"toolu_transcript","recipientMessageId":"toolu_transcript","recipientSessionId":"agent_t","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"content":[{"text":"Summarize the repo","type":"text"}],"messageId":"toolu_transcript","recipientSessionId":"agent_t","senderMessageId":"toolu_transcript","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"Look at t","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"he layout.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"The repo has ","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":{"text":"two packages.","type":"text"},"messageId":"msg_transcript_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_transcript"}},"content":[],"kind":"search","locations":[],"name":"Glob","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Find","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"rawInput":{"pattern":"*"},"sessionUpdate":"tool_call_update","title":"Find `*`","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"content":[{"content":{"text":"a\nb","type":"text"},"type":"content"}],"rawOutput":"a\nb","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_transcript_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Two packages.","type":"text"}],"messageId":"toolu_transcript:reply","recipientSessionId":"<session>","senderSessionId":"agent_t","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_t","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
@@ -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":{}}}},"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}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"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>"}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user