mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-04 08:22:03 +00:00
Compare commits
15 Commits
v0.99.2-pr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bbdea6cc6 | ||
|
|
1e986b526d | ||
|
|
53e3eec013 | ||
|
|
981bf9a28a | ||
|
|
4d79b9ffbc | ||
|
|
5565259771 | ||
|
|
720bbb3e4c | ||
|
|
38cf656fc9 | ||
|
|
1d0306468b | ||
|
|
c6de68797b | ||
|
|
eb78afa855 | ||
|
|
d2ef490a31 | ||
|
|
f50598a39c | ||
|
|
3bc671193c | ||
|
|
c3428de1bf |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.99.1"
|
||||
".": "0.100.0"
|
||||
}
|
||||
|
||||
84
AGENTS.md
84
AGENTS.md
@@ -16,13 +16,85 @@ npm run format # prettier --write
|
||||
CI runs `format:check`, `lint`, `build` and `test:run`. Run `npm run check` before
|
||||
opening a PR.
|
||||
|
||||
## Code comments
|
||||
## Coding guidelines
|
||||
|
||||
Keep comments straightforward, concise, and concrete. Say what the code does and
|
||||
why, in plain terms: name the actual state, call, or failure involved instead of
|
||||
alluding to it. Prefer a short concrete example over an abstract description.
|
||||
Don't use vague phrases like "keeps it honest" or "the residual case" that make
|
||||
the reader reconstruct the meaning.
|
||||
- Keep edits consistent with the surrounding code's formatting, naming and idioms.
|
||||
- Keep comments concise and concrete. Name the actual state, call or failure
|
||||
instead of alluding to it, prefer a short example over an abstract
|
||||
description, and avoid vague phrases like "keeps it honest". A comment or doc
|
||||
must be true on every code path it describes, and a doc that lists what a
|
||||
result omits or rejects must name the actual cases.
|
||||
- When a request can't be served, throw a `RequestError` whose message says what
|
||||
happened and what to do (e.g. "Try again when the turn ends"). Don't drop the
|
||||
request silently.
|
||||
- Don't report success for work you start without awaiting it. If the detached
|
||||
call can still refuse and its `.catch` only logs, check the refusal condition
|
||||
first or report the failure to the client.
|
||||
- A result must not depend on what an earlier call happened to cache. A request
|
||||
right after a restart, with cold caches, must return the same thing as one
|
||||
after a warm-up.
|
||||
- If code relies on an ordering or call sequence that nothing enforces (e.g. an
|
||||
echo arriving before the result), say so in a short comment.
|
||||
|
||||
## Architecture and design fit
|
||||
|
||||
- Before coding, read the code the change plugs into and find the module that
|
||||
owns the state or concept. Build on that module, not beside it.
|
||||
- Don't add new features or components to `acp-agent.ts`. If no existing module fits the new
|
||||
code cleanly, create a dedicated one that owns the feature's state and logic,
|
||||
and keep the change in `acp-agent.ts` to the calls that wire it in.
|
||||
- Prefer extending an existing mechanism over adding a new one, even if it takes
|
||||
a small refactor (moving a helper, generalizing a function). Add a parallel
|
||||
path, special case or flag only when the existing structure can't support the
|
||||
change.
|
||||
- Before writing new logic, check whether it already exists in a sibling handler
|
||||
of the same flow or in a helper that works with the same data, and reuse it.
|
||||
Paths that handle the same input must apply the same rules through the same
|
||||
code: the live stream and the `session/load` replay, the command list and
|
||||
command dispatch, `prompt` and `steer`. Two copies of one check drift apart.
|
||||
- Put guards, filters and cleanup in the module that owns the state, so every
|
||||
caller gets them. If guards, flags or fallbacks pile up in one place,
|
||||
restructure instead of adding another.
|
||||
- If a result is meant to match another request's output, reuse that request's
|
||||
logic.
|
||||
- The adapter serves ACP v1 and v2. A change that is not meant for one protocol
|
||||
version only must work correctly in both. `src/v2/` converts v2 requests to v1
|
||||
and reuses the v1 logic; keep v2-only differences in the conversion.
|
||||
- A session runs one turn at a time. "Claude is working" means `startTurn`'s
|
||||
check (an unsettled turn in `turnQueue`, or `foregroundWork` for the session);
|
||||
don't write a narrower one. Anything that sends `session/update` must go
|
||||
through the turn queue or be refused while the session is busy, or its output
|
||||
lands inside another turn. Keep the session marked busy across awaited network
|
||||
calls.
|
||||
- Don't branch on a local copy of state that Claude Code or a remote service
|
||||
owns: it goes stale when the other side changes. Have a command set the state
|
||||
the user asks for instead of toggling it.
|
||||
- A command or name the adapter adds must not shadow one that Claude Code or the
|
||||
user's own commands and skills already provide.
|
||||
- New optional request fields and `_meta` keys must not reject requests that
|
||||
were valid before.
|
||||
- If a workaround or new path is unavoidable (e.g. a Claude Code or SDK
|
||||
limitation), say why in a short code comment and in the PR description.
|
||||
|
||||
## Testing guidelines
|
||||
|
||||
- Before adding a test, search `src/tests/` for existing tests of the same API
|
||||
and scenario, and add to their file and `describe` block. Use the helpers in
|
||||
`helpers.ts` and `session-doubles.ts` instead of copying setup.
|
||||
- Don't add cases that take the same code path. Tests that differ only in input
|
||||
and expected output belong in one `it.each`. Don't repeat another test's
|
||||
assertions; assert only what this test is about.
|
||||
- Reach a state the shortest way (e.g. set `session.tornDown = true`) instead of
|
||||
driving a full flow with extra mocks, unless the flow is what you test.
|
||||
- Build test doubles from what the bundled Claude Code CLI really sends,
|
||||
including fields like `origin` it echoes back and fields it omits. A double
|
||||
simpler than the real message hides bugs. If unsure, check against the real
|
||||
CLI (`npm run test:integration`).
|
||||
- Cover both ACP v1 and v2 when a change affects what a client receives
|
||||
(updates, responses, replay). A change to internal logic only does not need
|
||||
its tests duplicated per version.
|
||||
- For each behavior the docs or comments promise, including what is omitted or
|
||||
rejected, have a test that would fail if it broke.
|
||||
|
||||
## Pull requests
|
||||
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [0.100.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.1...v0.100.0) (2026-11-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* list v2 background tool calls ([#1511](https://github.com/agentclientprotocol/claude-agent-acp/issues/1511)) ([4d79b9f](https://github.com/agentclientprotocol/claude-agent-acp/commit/4d79b9ffbce95c23928a5dd771d6c87476d37160))
|
||||
* report subagent working directories ([#1496](https://github.com/agentclientprotocol/claude-agent-acp/issues/1496)) ([eb78afa](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb78afa8556b8fc4082495e48e3fd1762fb6a50f))
|
||||
* report v2 background work ([#1510](https://github.com/agentclientprotocol/claude-agent-acp/issues/1510)) ([5565259](https://github.com/agentclientprotocol/claude-agent-acp/commit/5565259771cb74e3a9bdbacfcbce43e061d81422))
|
||||
* report why Claude Code could not start a session ([#1508](https://github.com/agentclientprotocol/claude-agent-acp/issues/1508)) ([38cf656](https://github.com/agentclientprotocol/claude-agent-acp/commit/38cf656fc9d80fec2d1a16c472779803e2c802d2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* align session/cancel, session/close, and v2 setup with the ACP v2 spec ([#1494](https://github.com/agentclientprotocol/claude-agent-acp/issues/1494)) ([f50598a](https://github.com/agentclientprotocol/claude-agent-acp/commit/f50598a39c5dc3908d4b98a7213e01d63ea867d5))
|
||||
* forward v2 elicitation values and read dismissed v2 permission prompts as a refusal ([#1493](https://github.com/agentclientprotocol/claude-agent-acp/issues/1493)) ([3bc6711](https://github.com/agentclientprotocol/claude-agent-acp/commit/3bc671193c6456516fe05a0eaa53c17bdfb6a190))
|
||||
* keep native Windows paths in v2 git patches ([#1492](https://github.com/agentclientprotocol/claude-agent-acp/issues/1492)) ([c3428de](https://github.com/agentclientprotocol/claude-agent-acp/commit/c3428de1bf411c513b0703ddd56bb6c5fdff583c))
|
||||
* preserve live session settings across provider refreshes ([#1474](https://github.com/agentclientprotocol/claude-agent-acp/issues/1474)) ([e01a288](https://github.com/agentclientprotocol/claude-agent-acp/commit/e01a28838499dd6dd7c31e35708090b938e852c6))
|
||||
* reject unreadable skill manifests ([#1482](https://github.com/agentclientprotocol/claude-agent-acp/issues/1482)) ([99a6e76](https://github.com/agentclientprotocol/claude-agent-acp/commit/99a6e76158b9164e51bd62e3b89113beadea5ce2))
|
||||
* report the fresh context's usage when ExitPlanMode clears context ([#1484](https://github.com/agentclientprotocol/claude-agent-acp/issues/1484)) ([2fd46c8](https://github.com/agentclientprotocol/claude-agent-acp/commit/2fd46c866d4ff2939fe1b1245b15a536d6de7c0b))
|
||||
* update to @agentclientprotocol/sdk 2.2.0 ([#1491](https://github.com/agentclientprotocol/claude-agent-acp/issues/1491)) ([1d2b92f](https://github.com/agentclientprotocol/claude-agent-acp/commit/1d2b92feae5eeccde3ac738edca1f1d71250762e))
|
||||
* update to @agentclientprotocol/sdk 2.4.0 ([#1509](https://github.com/agentclientprotocol/claude-agent-acp/issues/1509)) ([720bbb3](https://github.com/agentclientprotocol/claude-agent-acp/commit/720bbb3e4c65874775a0bb9418799baf4b6e3680))
|
||||
* update to Claude Agent SDK 0.3.316 (Claude Code 2.1.316) ([#1507](https://github.com/agentclientprotocol/claude-agent-acp/issues/1507)) ([1d03064](https://github.com/agentclientprotocol/claude-agent-acp/commit/1d0306468b632c6a56b4fb076ad7a19f23d9e7b5))
|
||||
* validate session, prompt, config, and auth requests per the latest ACP spec ([#1495](https://github.com/agentclientprotocol/claude-agent-acp/issues/1495)) ([d2ef490](https://github.com/agentclientprotocol/claude-agent-acp/commit/d2ef490a318033049af00cb9164a39ac2dd1eb95))
|
||||
|
||||
## [0.99.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.99.0...v0.99.1) (2026-10-29)
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,14 @@ Task subagent as its own child session, following the [subagents RFD](https://gi
|
||||
`end_turn` or `cancelled` when the SDK gives the reason. A failure is `idle` with the `error`
|
||||
stop reason and the SDK's error text as its `error`. The child keeps its session for every
|
||||
delegation to it.
|
||||
- A child that runs outside its parent's working directory, such as an Agent call with
|
||||
`isolation: "worktree"`, gets its `cwd` on `subagent_update` before its first tool call. The
|
||||
adapter takes it from the `cwd` of the child's own hooks: its SubagentStart hook runs before its
|
||||
first model request. It sends `cwd: null` when the child is back in its parent's directory. A
|
||||
child in its parent's directory gets no `cwd`, and no child gets `additionalDirectories`: Claude
|
||||
Code gives a subagent the session's additional directories, so it inherits them. A replay
|
||||
announces a child with the `cwd` that its transcript recorded, and clears it after the response
|
||||
when that directory no longer exists.
|
||||
- Session messages replace the Agent or Task call that launches the child, which its parent is
|
||||
not shown:
|
||||
- The launch's prompt is a `session_message` in both transcripts: the parent's outgoing view and
|
||||
|
||||
@@ -1569,7 +1569,7 @@ It is idempotent, and an unknown id also returns `{}`.
|
||||
`session/delete`:
|
||||
|
||||
- a `sessionIndex` client: deletes every transcript of the session (empty ones too) and every `<sessionId>/` directory, also one left without a transcript by an earlier failed delete. Anything left behind fails the request. A session that runs here is closed first. As with the CLI, the session is deleted also when another Claude Code process has it open. An unknown session is `-32002`.
|
||||
- an AIR client without `sessionIndex`: archives the session instead of deleting it (the title records of [Archive](#archive)), because AIR uses delete for "Done" and can reopen the session. A session that runs here is closed first. The list hides archived sessions from such a client too: those whose custom title, as the SDK list reports it, is archived. A session without a transcript fails with the error of the SDK delete, as before.
|
||||
- an AIR client without `sessionIndex`: archives the session instead of deleting it (the title records of [Archive](#archive)), because AIR uses delete for "Done" and can reopen the session. A session that runs here is closed first. The list hides archived sessions from such a client too: those whose custom title, as the SDK list reports it, is archived. A session without a transcript has nothing to archive, so the delete succeeds, as ACP asks of a session that does not exist.
|
||||
A `sessionIndex` client sees such a session as archived (`archived: true`), as AIR's native Claude path does.
|
||||
- any other client: the SDK delete. A session without a transcript (unknown, deleted before, or
|
||||
never prompted) succeeds, as ACP asks.
|
||||
|
||||
298
package-lock.json
generated
298
package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.99.1",
|
||||
"version": "0.100.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.99.1",
|
||||
"version": "0.100.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
||||
"@agentclientprotocol/sdk": "2.4.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.316",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
@@ -21,14 +21,14 @@
|
||||
"@anthropic-ai/sdk": "1.3.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.5",
|
||||
"@types/node": "26.6.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.72.1",
|
||||
"@typescript-eslint/parser": "8.72.1",
|
||||
"ajv": "8.20.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"eslint": "10.13.0",
|
||||
"eslint": "10.14.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.13.0",
|
||||
"globals": "17.14.0",
|
||||
"prettier": "3.9.10",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "5.0.5"
|
||||
@@ -38,9 +38,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.2.0.tgz",
|
||||
"integrity": "sha512-o1+eDQVb05yzPY+5v54awHyONU1oeLsbLVavkwrMQaZDuJDh/38pQiXJS4ix4d6QA3Y4bU0lHFRjwg99KKKSaQ==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.4.0.tgz",
|
||||
"integrity": "sha512-7qJ8nnyj7WesC5ve185ioTpB2/svQoeGV4HA4nLEhgDUrcmBe54/08VcXc/oL4p14tDbxxECZzdYmUYQ+4l+Wg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/server": "^2.4.0",
|
||||
@@ -53,22 +53,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.312.tgz",
|
||||
"integrity": "sha512-Ilj5Agx3ak/Z0UPZNLS7+CTnYMa+Ijf8+FKelh9MKGRwHVYyfvmJgL0fsmooI78SdkZwqjI5a4CwOAsJurdjJA==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.316.tgz",
|
||||
"integrity": "sha512-mCMr0que5kIy80JslJub6WFDB2+GYRGWNeVgSZaBpkBwXrr4wnRMnnGHitkA2BEJ5QP5xWyc6YH/6do/edxCWg==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.14.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.312",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.312"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.316",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.316"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -77,9 +77,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.312.tgz",
|
||||
"integrity": "sha512-AVmdMOuoeE3Y9f4wAIPcEAAS0OFaZw1L6l11YuyqfS0B/+DYxkWxS/ZYZ1NbUy57eaIW/vdEgz9Q5M99Pl6ZJw==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.316.tgz",
|
||||
"integrity": "sha512-1I1oJ/YGqq4pFQvJqycD2e8pbsUQLLR+DwEzG9jQAGCoSKkxP1TBfHpjSik/0byCixyzpYObrYXPC0PKBIR91g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -90,9 +90,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.312.tgz",
|
||||
"integrity": "sha512-Dmxyil+GPzF1WENoFOi1Cx0roSdJA6HewGAfAFvsH5vYrzb6G0REGuoQQ0n3u8i8qFgtl1m/WSl4u5t+MdiHWA==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.316.tgz",
|
||||
"integrity": "sha512-gzNncKrID/6yJIKCAuF/pY1kfygQZ4dU0J+MNFl0bU+iQW65ERoIyoghYVfrAQ/gH2fuvnQtWxa8LQNScHgNMA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -103,9 +103,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.312.tgz",
|
||||
"integrity": "sha512-jebsXksKh7g7u1x+RxjYQXM50TCN0e7GthebXa+n5IebF3pPqtmyWGVIiEEjvpajn3P6Zh8BWUVuufUpXn8ccA==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.316.tgz",
|
||||
"integrity": "sha512-+lm9XSw/em14yMZ41fbo80kfblbylfBQc9nYS6GgLoZVvgT3wSeEbv88v+mEK/jFw9g3VCH4ZI01sjNM34o7uw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -119,9 +119,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.312.tgz",
|
||||
"integrity": "sha512-9GT0JzbfhODwsCO3MCg79wPH47qARCuKECcryeAJnVczplAlXSIiBw1+6UcqL5+0wSYFu6D/tvw/AiVy+PkHsg==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.316.tgz",
|
||||
"integrity": "sha512-1qqaEdsAs6eipEQQ4bMAWiwDjIouyA/YN5KQWS0/+kuPb+GKS8rUVW2dty2elV703hsMovb4IhMP4cv5K7R9YA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -135,9 +135,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.312.tgz",
|
||||
"integrity": "sha512-UOsqza2uo5gqlX2OU72LhkQvIaNJKUQMJIi1poX2NtYqaWACqNo/TZPWfOBzxhyWmX8BLWaOlcuZGPLgcCJOTg==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.316.tgz",
|
||||
"integrity": "sha512-+csYyGkHziNp4It+ZykqTmXRZBuNrieTiBnji+zwAxNmLLCWz8xEWjNmwhaLPIMpUiaYREkDhZXt/AS6xXta5w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -151,9 +151,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.312.tgz",
|
||||
"integrity": "sha512-zM+PB6qVybdwtlqElXQ4+wCwk2v/jjT3Xau4foydzy/g5pGRTuGT3PTcfDLeG0j9zF8IxLaCQ/+Bl9MABPQD8g==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.316.tgz",
|
||||
"integrity": "sha512-pMG9MRkjxMMbIxpwg/cKz6JorNjZtJOA9l/fifRQ/A01zGTKrRIaSCWoGg+Asmc64QBOa3aA4vzE/od9B1R0Tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -167,9 +167,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.312.tgz",
|
||||
"integrity": "sha512-Li9CyzsC2mzGmF1wtt/R59ChBLww6nc2dLn6tb+bgTDcd6kqvW44x5rIH+lRmBcpTNHR/8VO/k+GlmnkxqeaQQ==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.316.tgz",
|
||||
"integrity": "sha512-Xj4iTS8NsB6isGhnZEnaIXIpUcSspv/lyIVnfPZjVGwuttaVHjq4IxfDopsPaVsPuTKd+L7DLAaBram1k85U2g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -180,9 +180,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.312",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.312.tgz",
|
||||
"integrity": "sha512-9GgGHHFEQnXx6EPhkPplOw1ndqmys8+Z+uOR68EIQlXGZm+wE+BmfypyHAoZhjs4151iWdQgNzjDT7wet3crdw==",
|
||||
"version": "0.3.316",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.316.tgz",
|
||||
"integrity": "sha512-FzQA1zHbYJTtLR9nn1McwkGYeiEmroTmYjIg7xZqo5eweJ8U/DESZKR/QdfF04MXe2DF23oLL8WHjEa+WxbGQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -363,9 +363,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.5.tgz",
|
||||
"integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==",
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.6.tgz",
|
||||
"integrity": "sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -544,9 +544,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.155.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
|
||||
"integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==",
|
||||
"version": "0.156.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz",
|
||||
"integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -555,9 +555,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz",
|
||||
"integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
|
||||
"integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -573,9 +573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -591,9 +591,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -609,9 +609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz",
|
||||
"integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -627,9 +627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz",
|
||||
"integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -645,9 +645,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz",
|
||||
"integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
|
||||
"integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -663,9 +663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -684,9 +684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz",
|
||||
"integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -705,9 +705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -726,9 +726,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -747,9 +747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -768,9 +768,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz",
|
||||
"integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -789,9 +789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -807,9 +807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz",
|
||||
"integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -825,9 +825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz",
|
||||
"integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -889,9 +889,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz",
|
||||
"integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -903,9 +903,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz",
|
||||
"integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==",
|
||||
"version": "26.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz",
|
||||
"integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1543,13 +1543,17 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz",
|
||||
"integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
@@ -1613,9 +1617,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz",
|
||||
"integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==",
|
||||
"version": "10.14.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.14.0.tgz",
|
||||
"integrity": "sha512-T0pNsxkb9IKmtQjhpK3d7nWOpFv+YHuMbTJnSVMoQblF0onvXfErrtp4V1jHtsQPan7dKaGVD0q1WCXnzMF31A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -1628,7 +1632,7 @@
|
||||
"@eslint/config-helpers": "^0.7.0",
|
||||
"@eslint/core": "^1.2.1",
|
||||
"@eslint/plugin-kit": "^0.7.3",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanfs/node": "^0.16.8",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
@@ -1649,7 +1653,7 @@
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"minimatch": "^10.2.5",
|
||||
"minimatch": "^10.2.6",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.3"
|
||||
},
|
||||
@@ -1941,9 +1945,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.7.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.1.tgz",
|
||||
"integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==",
|
||||
"version": "8.7.2",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.2.tgz",
|
||||
"integrity": "sha512-U1ScocxDD9WI09LaBVypvNkOtNR5yWAmZLkuKJdAzIchA4YKFYF6GT5IKER7epG58oXtjvGlV0/6ii0F83EyZg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2201,9 +2205,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.13.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz",
|
||||
"integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==",
|
||||
"version": "17.14.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.14.0.tgz",
|
||||
"integrity": "sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2266,9 +2270,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.13.14",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.14.tgz",
|
||||
"integrity": "sha512-yhRLQ21U6qa9Hsu2N7Qcbh8tE+WKEcoErjIo56LeSGiGXGp9sA7UpE86Ui9uexb61KakiGwuHMn2QdV7w6i6kQ==",
|
||||
"version": "4.13.16",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.16.tgz",
|
||||
"integrity": "sha512-AOLOEhvrV+iH92xXCnFr/5+3KjPKcI3xP63XJtX5spAnkznPdHijlIsUTpi7/9EhWqrTCekw5c5zh1K+YsVw7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2404,9 +2408,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.13",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.13.tgz",
|
||||
"integrity": "sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==",
|
||||
"version": "6.2.15",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.15.tgz",
|
||||
"integrity": "sha512-qdF6ewcZARtef63fJ9AKKZPgxw+t7USH6HLXYcD4CrdmSvDp79rpJSPFEKc90QBeGS63u1KQtN2MZQHC0yDIzA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
@@ -3215,9 +3219,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.17.0.tgz",
|
||||
"integrity": "sha512-5LK5BZ2XDHMvcUJaf0CQ5HL3aVgKbpklvByC7qLSxYkfE8COEEbJg5xCHWCTqzqi0Gs4Jn5Mv5v3R38W6edscA==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz",
|
||||
"integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3271,14 +3275,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz",
|
||||
"integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==",
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
|
||||
"integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.155.0",
|
||||
"@oxc-project/types": "=0.156.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3288,21 +3292,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.15",
|
||||
"@rolldown/binding-android-arm64": "1.2.15",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.15",
|
||||
"@rolldown/binding-darwin-x64": "1.2.15",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.15",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.15",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.15",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.15",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.15",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.15",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.15"
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.16",
|
||||
"@rolldown/binding-android-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-x64": "1.2.16",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.16",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.16",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.16",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.16",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.16",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.16"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3532,9 +3536,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz",
|
||||
"integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.3.tgz",
|
||||
"integrity": "sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
12
package.json
12
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.99.1",
|
||||
"version": "0.100.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -65,8 +65,8 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
||||
"@agentclientprotocol/sdk": "2.4.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.316",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
@@ -74,14 +74,14 @@
|
||||
"@anthropic-ai/sdk": "1.3.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.5",
|
||||
"@types/node": "26.6.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.72.1",
|
||||
"@typescript-eslint/parser": "8.72.1",
|
||||
"ajv": "8.20.0",
|
||||
"ajv-formats": "3.0.1",
|
||||
"eslint": "10.13.0",
|
||||
"eslint": "10.14.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.13.0",
|
||||
"globals": "17.14.0",
|
||||
"prettier": "3.9.10",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "5.0.5"
|
||||
|
||||
428
src/acp-agent.ts
428
src/acp-agent.ts
@@ -164,6 +164,13 @@ import {
|
||||
backgroundedBashToolCallIds,
|
||||
clientSupportsAsyncTasks,
|
||||
} from "./async-tasks.js";
|
||||
import {
|
||||
type BackgroundSink,
|
||||
type BackgroundTaskEnd,
|
||||
BackgroundWorkReporter,
|
||||
cancelBackgroundWork,
|
||||
SessionBackgroundWork,
|
||||
} from "./background-work.js";
|
||||
import {
|
||||
AUTH_STATUS_PROBE_TIMEOUT_MS,
|
||||
AUTH_STATUS_UPDATE_METHOD,
|
||||
@@ -302,7 +309,11 @@ import {
|
||||
unregisterHookCallback,
|
||||
} from "./tools.js";
|
||||
import { previewPatchContent } from "./diff.js";
|
||||
import { backgroundedBashToolCall, withRunningTerminal } from "./tool-calls/background.js";
|
||||
import {
|
||||
backgroundedBashToolCall,
|
||||
withRunningTerminal,
|
||||
withRunningToolCall,
|
||||
} from "./tool-calls/background.js";
|
||||
import { ChangedMetaFilter } from "./tool-calls/changed-meta-filter.js";
|
||||
import { ToolCallFieldTracker } from "./tool-calls/field-tracker.js";
|
||||
import { ClientCapabilities as ToolCallClientCapabilities } from "./tool-calls/client-capabilities.js";
|
||||
@@ -381,6 +392,7 @@ import {
|
||||
ultracodeWorkflowsEnabled,
|
||||
type UltracodeOptionState,
|
||||
} from "./session-ultracode.js";
|
||||
import { STARTUP_FAILURE_RESULTS_ENV, startupFailureError } from "./startup-failure.js";
|
||||
|
||||
export { EFFORT_CONFIG_ID, settingsEffortForModel } from "./session-effort.js";
|
||||
export {
|
||||
@@ -790,6 +802,9 @@ function parseSteerRequest(params: unknown): SteerRequest {
|
||||
* queued user messages back in submission order, so `turnQueue[0]` is the turn
|
||||
* currently running. */
|
||||
type Turn = {
|
||||
/** The `ClaudeAcpAgent.promptsReceived` count when `startTurn()` received
|
||||
* this prompt. `cancelTurns` cancels only turns received before the cancel. */
|
||||
received: number;
|
||||
/** uuid stamped on the pushed `SDKUserMessage`; the SDK echoes it back so the
|
||||
* consumer can match the replayed user message to this turn. It is also the
|
||||
* id of the user message the turn reports as inserted. */
|
||||
@@ -1451,17 +1466,18 @@ export type Session = {
|
||||
* `perTaskStopAffordance`: then it spares both, and the client is expected
|
||||
* to stop each one itself. A client that can cancel one subagent gets the
|
||||
* declaration, but it has no control for a workflow unless it is AIR with
|
||||
* `asyncTasks` (`_session/async_task/stop`). So for such a client the
|
||||
* `asyncTasks` (`_session/async_task/stop`), or gets the session's
|
||||
* background work (`background.reported`, with `tool_call/cancel`), which
|
||||
* `session/cancel` must leave running. So for any other such client the
|
||||
* adapter stops the workflows after the interrupt, as the interrupt did
|
||||
* before. */
|
||||
stopsWorkflowsOnCancel?: boolean;
|
||||
/** The display terminals of the commands that went to the background and
|
||||
* still run, by the id of the command's task: the Bash tool call whose
|
||||
* terminal it is, and the subagent tool call that ran it, if any. The
|
||||
* terminal exits when the task ends, not at the tool result (see
|
||||
* `withRunningTerminal`). Only for a client that renders terminals and is
|
||||
* not AIR, which shows the command through its async tasks. */
|
||||
backgroundTerminals?: Map<string, { toolCallId: string; parentToolUseId?: string }>;
|
||||
/** The tasks that Claude Code runs in the background for the session: the
|
||||
* terminals of commands, kept for a client that renders terminals and is
|
||||
* not AIR (AIR shows a command through its async tasks), and, when the
|
||||
* client gets the session's background work, the tool calls of tasks and
|
||||
* the `background` lists. Per CLI process, as is a session. */
|
||||
background: SessionBackgroundWork;
|
||||
/** The parent tool call of each subagent that a settle bookend removed from
|
||||
* `liveBackgroundTasks`. The SDK can resume such a subagent under the same
|
||||
* agent id without a new `task_started` (a running `task_updated` patch or
|
||||
@@ -2439,8 +2455,8 @@ export function isReplayHiddenMetaMessage(message: unknown): boolean {
|
||||
export interface AcpClient {
|
||||
sessionUpdate(params: AcpSessionNotification): Promise<void>;
|
||||
/** `signal`, when aborted, sends `$/cancel_request` for the in-flight
|
||||
* permission request so the client can dismiss its prompt (and settle our
|
||||
* await) instead of leaving the dialog open after the turn was cancelled. */
|
||||
* permission request so the client can dismiss its prompt, and rejects the
|
||||
* call at once: a client that ignores the cancel never answers. */
|
||||
requestPermission(
|
||||
params: AcpPermissionRequest,
|
||||
signal?: AbortSignal,
|
||||
@@ -2448,7 +2464,8 @@ export interface AcpClient {
|
||||
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
|
||||
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
||||
/** `signal`, when aborted, sends `$/cancel_request` for the in-flight
|
||||
* elicitation so the client can dismiss its prompt and settle our await. */
|
||||
* elicitation so the client can dismiss its prompt, and rejects the call
|
||||
* at once, as {@link requestPermission} does. */
|
||||
createElicitation(
|
||||
params: CreateElicitationRequest,
|
||||
signal?: AbortSignal,
|
||||
@@ -2477,6 +2494,7 @@ class ClientConnection implements AcpClient {
|
||||
): Promise<RequestPermissionResponse> {
|
||||
return this.ctx.request(methods.client.session.requestPermission, params, {
|
||||
cancellationSignal: signal,
|
||||
stopWaitingOnCancel: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2494,6 +2512,7 @@ class ClientConnection implements AcpClient {
|
||||
): Promise<CreateElicitationResponse> {
|
||||
return this.ctx.request(methods.client.elicitation.create, params, {
|
||||
cancellationSignal: signal,
|
||||
stopWaitingOnCancel: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2704,7 +2723,10 @@ async function authenticateMcpServer(
|
||||
elicitation.then((response) => ({ type: "elicitation" as const, response })),
|
||||
]);
|
||||
|
||||
if (first.type === "elicitation" && !CreateElicitationResponse.isAccept(first.response)) {
|
||||
// A url-mode accept carries no content to check. The guard
|
||||
// `CreateElicitationResponse.isAccept` would also reject an accept whose
|
||||
// `content` holds a v2-only value, such as an object.
|
||||
if (first.type === "elicitation" && first.response.action !== "accept") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2818,6 +2840,14 @@ export class ClaudeAcpAgent {
|
||||
>();
|
||||
/** See the `foreground` constructor option. */
|
||||
private readonly foregroundEvents?: (sessionId: string) => ForegroundEvents;
|
||||
/** See the `background` constructor option. */
|
||||
private readonly backgroundWork: BackgroundWorkReporter;
|
||||
/** Counts the prompts `startTurn()` has received, across sessions. */
|
||||
private promptsReceived = 0;
|
||||
/** Per session, the `promptsReceived` count when its last cancel started.
|
||||
* A prompt received before that cancel that `startTurn()` had not queued
|
||||
* yet ends as cancelled instead of being queued. */
|
||||
private readonly cancelledThrough = new Map<string, number>();
|
||||
/**
|
||||
* The foreground work of each session while it runs, by the session id that
|
||||
* the client knows, on every connection: only the `foreground` option
|
||||
@@ -2863,10 +2893,17 @@ export class ClaudeAcpAgent {
|
||||
* ended from its prompt response.
|
||||
*/
|
||||
foreground?: (sessionId: string) => ForegroundEvents;
|
||||
/**
|
||||
* Where the agent reports the background work of each session (see
|
||||
* `background-work.ts`): once when the session starts, and each time
|
||||
* the work changes. Without it, nothing reports the work.
|
||||
*/
|
||||
background?: BackgroundSink;
|
||||
} = {},
|
||||
) {
|
||||
this.v2 = options.v2 ?? false;
|
||||
this.foregroundEvents = options.foreground;
|
||||
this.backgroundWork = new BackgroundWorkReporter(options.background);
|
||||
this.sessions = {};
|
||||
this.client = new ChangedMetaClient(client, () => this.toolCallCapabilities.air.client);
|
||||
this.logger = logger ?? console;
|
||||
@@ -3154,7 +3191,7 @@ export class ClaudeAcpAgent {
|
||||
params: LoadSessionRequest,
|
||||
timing: SessionTiming,
|
||||
): Promise<NewSessionResponse> {
|
||||
await this.validateCwd(params.cwd);
|
||||
await this.validateCwd(params.cwd, params.additionalDirectories);
|
||||
clientSystemPrompt(params.systemPrompt);
|
||||
const pending: PendingReplay = {
|
||||
state: {
|
||||
@@ -3217,7 +3254,8 @@ export class ClaudeAcpAgent {
|
||||
*/
|
||||
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
||||
while (this.providerUpdate) await this.providerUpdate;
|
||||
// Reject a prompt the agent cannot apply before copying the transcript.
|
||||
// Reject a bad `cwd` or prompt before copying the transcript.
|
||||
await this.validateCwd(params.cwd, params.additionalDirectories);
|
||||
clientSystemPrompt(params.systemPrompt);
|
||||
const { sessionId } = await forkSession(params, {
|
||||
liveMessageIdToUuid: this.sessions[params.sessionId]?.messageIdToUuid,
|
||||
@@ -3280,12 +3318,21 @@ export class ClaudeAcpAgent {
|
||||
* reads the start and the end only of the transcripts of the page.
|
||||
*/
|
||||
async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
|
||||
if (params.cwd != null && !path.isAbsolute(params.cwd)) {
|
||||
throw RequestError.invalidParams(
|
||||
{ cwd: params.cwd },
|
||||
`\`cwd\` must be an absolute path, but received: ${params.cwd}`,
|
||||
);
|
||||
}
|
||||
const indexed = this.sessionIndex.list(params);
|
||||
if (indexed) return indexed;
|
||||
const offset = sessionListOffset(params.cursor);
|
||||
// One more session than the page tells whether a next page exists.
|
||||
// `includeWorktrees: false`: a `cwd` filter matches sessions of exactly
|
||||
// that directory, not of the other git worktrees of its repository.
|
||||
const sdkSessions = await listSessions({
|
||||
dir: params.cwd ?? undefined,
|
||||
includeWorktrees: false,
|
||||
limit: SESSION_LIST_PAGE_SIZE + 1,
|
||||
offset,
|
||||
});
|
||||
@@ -3371,7 +3418,10 @@ export class ClaudeAcpAgent {
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error("Method not implemented.");
|
||||
throw RequestError.invalidParams(
|
||||
{ methodId: _params.methodId },
|
||||
`Unsupported authentication method: ${_params.methodId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3871,8 +3921,21 @@ export class ClaudeAcpAgent {
|
||||
/**
|
||||
* Serves an ACP v1 `session/prompt`, which answers when the turn ends. The
|
||||
* outcome of a turn is field for field a v1 prompt response.
|
||||
*
|
||||
* A content block that the agent does not advertise, such as `audio`, fails
|
||||
* the request with invalid params (-32602) before anything is queued:
|
||||
* `promptToClaude` would drop it. v2 prompts reach `startTurn` through
|
||||
* `v1PromptRequest`, which rejects these blocks itself.
|
||||
*/
|
||||
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
||||
for (const block of params.prompt) {
|
||||
if (!SUPPORTED_PROMPT_CONTENT_TYPES.includes(block.type)) {
|
||||
throw RequestError.invalidParams(
|
||||
{ type: block.type },
|
||||
`Content blocks of type ${block.type} are not supported`,
|
||||
);
|
||||
}
|
||||
}
|
||||
let events!: TurnEvents;
|
||||
const outcome = new Promise<TurnOutcome>((resolve, reject) => {
|
||||
events = { inserted() {}, ended: resolve, failed: reject };
|
||||
@@ -3888,6 +3951,7 @@ export class ClaudeAcpAgent {
|
||||
* before this resolves.
|
||||
*/
|
||||
async startTurn(params: PromptRequest, events: TurnEvents): Promise<void> {
|
||||
const received = ++this.promptsReceived;
|
||||
while (this.providerUpdate) await this.providerUpdate;
|
||||
let session = this.sessions[params.sessionId];
|
||||
if (!session) {
|
||||
@@ -4029,7 +4093,9 @@ export class ClaudeAcpAgent {
|
||||
// its consumer is gone, so the prompt would never be inserted. End it as
|
||||
// the close ends a queued turn: `cancelled` on v1, -32800 on v2. A stream
|
||||
// that ended meanwhile fails the prompt as the check above does.
|
||||
if (session.tornDown) {
|
||||
// A `session/cancel` that arrived after this prompt, during the awaits
|
||||
// above, ends it the same way.
|
||||
if (session.tornDown || received <= (this.cancelledThrough.get(params.sessionId) ?? 0)) {
|
||||
session.fileChangeReporter?.finish(fileChangeReport, "cancelled");
|
||||
events.ended({ stopReason: "cancelled" });
|
||||
return;
|
||||
@@ -4044,6 +4110,7 @@ export class ClaudeAcpAgent {
|
||||
// the consumer is running.
|
||||
let completeTurn!: () => void;
|
||||
const turn: Turn = {
|
||||
received,
|
||||
promptUuid,
|
||||
events,
|
||||
isLocalOnlyCommand,
|
||||
@@ -5328,28 +5395,10 @@ export class ClaudeAcpAgent {
|
||||
this.syncAwaitingUser(params.sessionId, session);
|
||||
};
|
||||
|
||||
/** Ends the display terminal of a command that went to the background, once
|
||||
* its task ends (see `Session.backgroundTerminals`). Claude Code reports no
|
||||
* exit code for it, and `completed` does not mean 0: Claude Code accepts
|
||||
* some non-zero codes as a success, such as grep's 1. */
|
||||
const endBackgroundTerminal = async (taskId: string) => {
|
||||
const terminal = session.backgroundTerminals?.get(taskId);
|
||||
if (!terminal) return;
|
||||
session.backgroundTerminals!.delete(taskId);
|
||||
await sendUpdate({
|
||||
sessionId: params.sessionId,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: terminal.toolCallId,
|
||||
_meta: {
|
||||
...(terminal.parentToolUseId
|
||||
? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
|
||||
: {}),
|
||||
terminal_exit: { terminal_id: terminal.toolCallId, exit_code: null, signal: null },
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
/** Ends the terminal and the tool call of a task that ran in the
|
||||
* background, once the task ends (see `Session.background`). */
|
||||
const endBackgroundTask = (taskId: string, end: BackgroundTaskEnd) =>
|
||||
session.background.taskEnded(taskId, end, sendUpdate);
|
||||
|
||||
/** Settles a task. Its request may still be open, and an agent without a
|
||||
* record no longer blocks the foreground work, so that is checked again. */
|
||||
@@ -5879,6 +5928,9 @@ export class ClaudeAcpAgent {
|
||||
// pulled only after the pause.
|
||||
const pause = messageLoopYielder.maybeYield();
|
||||
if (pause) await pause;
|
||||
// The last message may have changed the background work, and its
|
||||
// updates are sent, so the list follows them.
|
||||
this.backgroundWork.sync(params.sessionId, session, this.foregroundState(params.sessionId));
|
||||
pendingNext ??= session.query
|
||||
.next()
|
||||
.then((result) => ({ kind: "message" as const, result }));
|
||||
@@ -6797,7 +6849,7 @@ export class ClaudeAcpAgent {
|
||||
streamedBlocksByParent.delete(message.tool_use_id);
|
||||
droppedStreams.delete(message.tool_use_id);
|
||||
}
|
||||
await endBackgroundTerminal(message.task_id);
|
||||
await endBackgroundTask(message.task_id, message.status);
|
||||
if (message.status === "stopped") {
|
||||
await closeStoppedSubagentToolCalls(message.tool_use_id);
|
||||
}
|
||||
@@ -6842,15 +6894,23 @@ export class ClaudeAcpAgent {
|
||||
streamedBlocksByParent.delete(parentToolUseId);
|
||||
droppedStreams.delete(parentToolUseId);
|
||||
}
|
||||
await endBackgroundTerminal(message.task_id);
|
||||
await endBackgroundTask(
|
||||
message.task_id,
|
||||
message.patch.status === "killed" ? "stopped" : message.patch.status,
|
||||
);
|
||||
if (message.patch.status === "killed") {
|
||||
await closeStoppedSubagentToolCalls(parentToolUseId);
|
||||
}
|
||||
settleLiveTask(message.task_id);
|
||||
} else if (message.patch.status === "paused") {
|
||||
const pause = session.background.taskPaused(message.task_id, true);
|
||||
if (pause) await sendUpdate(pause);
|
||||
} else if (
|
||||
message.patch.status === "running" ||
|
||||
message.patch.status === "pending"
|
||||
) {
|
||||
const resume = session.background.taskPaused(message.task_id, false);
|
||||
if (resume) await sendUpdate(resume);
|
||||
// The SDK can resume a finished subagent under the same
|
||||
// agent id without a new task_started.
|
||||
resumeLiveTask(message.task_id);
|
||||
@@ -7018,8 +7078,9 @@ export class ClaudeAcpAgent {
|
||||
// control requests, which this adapter never issues.
|
||||
case "control_request_progress":
|
||||
break;
|
||||
case "background_tasks_changed":
|
||||
case "background_tasks_changed": {
|
||||
await asyncTasks.backgroundTasksChanged(message.tasks);
|
||||
await session.background.levelChanged(message.tasks, sendUpdate);
|
||||
// A level signal: the full live background-task set on every
|
||||
// membership change, with REPLACE semantics. Used only to
|
||||
// reconcile `liveBackgroundTasks` — dropping (or, for
|
||||
@@ -7063,6 +7124,7 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable(message, this.logger);
|
||||
break;
|
||||
@@ -8421,6 +8483,8 @@ export class ClaudeAcpAgent {
|
||||
const acceptedPlanToolUseId = observeExitPlanToolResults(message, content, session);
|
||||
let backgroundedToolCalls: ReadonlySet<string> = new Set();
|
||||
let runningTerminal: string | undefined;
|
||||
let runningTerminalTask: string | undefined;
|
||||
let runningToolCalls: ReadonlySet<string> = new Set();
|
||||
if (message.type === "user") {
|
||||
rememberResolvedToolNames(session, content);
|
||||
recordSubagentFileEdits(
|
||||
@@ -8446,13 +8510,24 @@ export class ClaudeAcpAgent {
|
||||
session.liveBackgroundTasks.has(backgroundBashTask.taskId)
|
||||
) {
|
||||
runningTerminal = terminalId;
|
||||
(session.backgroundTerminals ??= new Map()).set(backgroundBashTask.taskId, {
|
||||
session.background.terminalStarted(backgroundBashTask.taskId, {
|
||||
toolCallId: terminalId,
|
||||
...(message.parent_tool_use_id
|
||||
? { parentToolUseId: message.parent_tool_use_id }
|
||||
: {}),
|
||||
...(typeof backgroundBashTask.outputFilePath === "string"
|
||||
? { outputFilePath: backgroundBashTask.outputFilePath }
|
||||
: {}),
|
||||
});
|
||||
runningTerminalTask = backgroundBashTask.taskId;
|
||||
}
|
||||
runningToolCalls = session.background.toolResults(
|
||||
session,
|
||||
content,
|
||||
toolUseResult,
|
||||
session.toolUseCache,
|
||||
message.parent_tool_use_id,
|
||||
);
|
||||
await asyncTasks.toolResults(content);
|
||||
backgroundedToolCalls = backgroundedBashToolCallIds(
|
||||
content,
|
||||
@@ -8515,16 +8590,31 @@ export class ClaudeAcpAgent {
|
||||
// (e.g. a subagent image) carry the stamped parentToolUseId
|
||||
// meta and are excluded there.
|
||||
await sendUpdate(
|
||||
withRunningTerminal(
|
||||
backgroundedBashToolCall(
|
||||
acceptedPlanToolResult(notification, acceptedPlanToolUseId),
|
||||
backgroundedToolCalls,
|
||||
asyncTasks.enabled,
|
||||
withRunningToolCall(
|
||||
withRunningTerminal(
|
||||
backgroundedBashToolCall(
|
||||
acceptedPlanToolResult(notification, acceptedPlanToolUseId),
|
||||
backgroundedToolCalls,
|
||||
asyncTasks.enabled,
|
||||
),
|
||||
runningTerminal,
|
||||
),
|
||||
runningTerminal,
|
||||
runningToolCalls,
|
||||
),
|
||||
);
|
||||
}
|
||||
// After the result's own updates: the command's output replaces
|
||||
// the placeholder that they gave its terminal.
|
||||
if (runningTerminalTask) {
|
||||
session.background.followOutput(runningTerminalTask, (notification) =>
|
||||
sendUpdate(notification).catch((error) => {
|
||||
this.logger.error(
|
||||
`Session ${params.sessionId}: failed to send background command output:`,
|
||||
error,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "tool_progress": {
|
||||
@@ -8850,6 +8940,12 @@ export class ClaudeAcpAgent {
|
||||
return;
|
||||
}
|
||||
session.cancelled = true;
|
||||
// Only prompts received before this cancel are cancelled. A prompt that
|
||||
// arrives while the awaits below run is queued and runs after the cancel
|
||||
// (ACP v2: a `session/prompt` received after `session/cancel` is not
|
||||
// cancelled by it).
|
||||
const cancelledThrough = this.promptsReceived;
|
||||
this.cancelledThrough.set(params.sessionId, cancelledThrough);
|
||||
// The interrupt below makes Claude Code withdraw the requests that are open
|
||||
// now (see `withPendingUserInput`).
|
||||
for (const input of session.openUserInputs ?? []) input.cancelled = true;
|
||||
@@ -8857,7 +8953,7 @@ export class ClaudeAcpAgent {
|
||||
// the meantime is already judged against these prompts.
|
||||
session.cancelledPrompts ??= new Set();
|
||||
for (const turn of session.turnQueue ?? []) {
|
||||
if (turn.settled) continue;
|
||||
if (turn.settled || turn.received > cancelledThrough) continue;
|
||||
session.cancelledPrompts.add(turn.promptUuid);
|
||||
for (const uuid of turn.steeredUuids ?? []) session.cancelledPrompts.add(uuid);
|
||||
}
|
||||
@@ -8933,7 +9029,7 @@ export class ClaudeAcpAgent {
|
||||
const orphanedTurns: Turn[] = [];
|
||||
if (session.turnQueue) {
|
||||
for (const turn of session.turnQueue) {
|
||||
if (turn !== session.activeTurn && !turn.settled) {
|
||||
if (turn !== session.activeTurn && !turn.settled && turn.received <= cancelledThrough) {
|
||||
session.fileChangeReporter?.finish(turn.fileChangeReport, "cancelled");
|
||||
turn.settled = true;
|
||||
// Deliberately no `usage`: a queued turn never ran, so the session
|
||||
@@ -8993,9 +9089,7 @@ export class ClaudeAcpAgent {
|
||||
turn.commandStarted ? "started" : "pending",
|
||||
);
|
||||
}
|
||||
session.turnQueue = session.turnQueue.filter(
|
||||
(turn) => turn === session.activeTurn && !turn.settled,
|
||||
);
|
||||
session.turnQueue = session.turnQueue.filter((turn) => !turn.settled);
|
||||
}
|
||||
const diagnosticTurns = orphanedTurns.filter((turn) => {
|
||||
if (
|
||||
@@ -9129,8 +9223,8 @@ export class ClaudeAcpAgent {
|
||||
|
||||
// Before the interrupt: once it stops the running turn, Claude Code starts
|
||||
// the next queued message right away.
|
||||
// A prompt or steer that arrived while this cancel awaited is abandoned
|
||||
// too, after the record above was taken.
|
||||
// A steer that arrived while this cancel awaited is abandoned too, after
|
||||
// the record above was taken.
|
||||
for (const uuid of abandoned) (session.cancelledPrompts ??= new Set()).add(uuid);
|
||||
this.dropQueuedMessages(params.sessionId, session.query, abandoned);
|
||||
const interrupt = session.query.interrupt();
|
||||
@@ -9232,9 +9326,17 @@ export class ClaudeAcpAgent {
|
||||
return;
|
||||
}
|
||||
session.queryClosed = true;
|
||||
// Its list row now shows what the registry and the transcript tell.
|
||||
for (const [sessionId, candidate] of Object.entries(this.sessions)) {
|
||||
if (candidate === session) this.sessionIndex.onOwnSessionChanged(sessionId);
|
||||
if (candidate !== session) continue;
|
||||
// Its list row now shows what the registry and the transcript tell.
|
||||
this.sessionIndex.onOwnSessionChanged(sessionId);
|
||||
// Its background tasks end with the query's process.
|
||||
for (const exit of session.background.queryClosed()) {
|
||||
this.client.sessionUpdate(exit).catch((error) => {
|
||||
this.logger.error(`Session ${sessionId}: failed to end a background terminal:`, error);
|
||||
});
|
||||
}
|
||||
this.backgroundWork.sync(sessionId, session, this.foregroundState(sessionId));
|
||||
}
|
||||
session.consumer = undefined;
|
||||
session.contextCompaction = undefined;
|
||||
@@ -9289,6 +9391,8 @@ export class ClaudeAcpAgent {
|
||||
session.nativeSubagentRuntime?.clear();
|
||||
session.asyncTaskRuntime?.clear();
|
||||
this.replayedChildModels.delete(sessionId);
|
||||
this.cancelledThrough.delete(sessionId);
|
||||
this.backgroundWork.forget(sessionId);
|
||||
delete this.sessions[sessionId];
|
||||
this.sessionIndex.onTeardown(sessionId);
|
||||
const ended = await raceTimeoutAndAbort(
|
||||
@@ -9335,6 +9439,57 @@ export class ClaudeAcpAgent {
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Stops a command running in the background (v2 `terminal/cancel`, see
|
||||
* {@link cancelBackgroundWork}). */
|
||||
cancelBackgroundTerminal(params: { sessionId: string; terminalId: string }): Promise<void> {
|
||||
return cancelBackgroundWork(Object.values(this.sessions), params.sessionId, {
|
||||
terminalId: params.terminalId,
|
||||
});
|
||||
}
|
||||
|
||||
/** Stops the task of a tool call running in the background (v2
|
||||
* `tool_call/cancel`, see {@link cancelBackgroundWork}). */
|
||||
cancelBackgroundToolCall(params: { sessionId: string; toolCallId: string }): Promise<void> {
|
||||
return cancelBackgroundWork(Object.values(this.sessions), params.sessionId, {
|
||||
toolCallId: params.toolCallId,
|
||||
});
|
||||
}
|
||||
|
||||
/** Sends the current state of each tool call in `toolCallIds` that the root
|
||||
* session `sessionId` runs in the background: the call as its tool use
|
||||
* shows it, with its status now. v2 requires it for each tool call that a
|
||||
* `session/resume` snapshot lists. A failure is logged, as for
|
||||
* {@link publishLiveSubagents}. */
|
||||
async publishBackgroundToolCalls(sessionId: string, toolCallIds: string[]): Promise<void> {
|
||||
const session = this.sessions[sessionId];
|
||||
if (!session) return;
|
||||
const renderer = new AcpToolCallRenderer(this.toolCallCapabilities);
|
||||
try {
|
||||
for (const update of session.background.toolCallStates(toolCallIds, renderer, session.cwd)) {
|
||||
await this.client.sessionUpdate(update);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Session ${sessionId}: failed to send background tool calls:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends the current `subagent_update` of each live subagent session in
|
||||
* `children`, of the session `sessionId` (see
|
||||
* {@link NativeSubagentRuntime.currentUpdate}). v2 requires it for each
|
||||
* child that a `session/resume` snapshot lists. A failure is logged, as
|
||||
* for {@link publishCurrentSubagentStates}. */
|
||||
async publishLiveSubagents(sessionId: string, children: string[]): Promise<void> {
|
||||
const runtime = this.sessions[sessionId]?.nativeSubagentRuntime;
|
||||
try {
|
||||
for (const child of children) {
|
||||
const update = runtime?.currentUpdate(child);
|
||||
if (update) await this.client.sessionUpdate(asSdkSessionNotification(update));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Session ${sessionId}: failed to send current subagent states:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Per client: see {@link sessionIndex.SessionIndexConnection.deleteSession}. */
|
||||
async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
|
||||
return this.sessionIndex.deleteSession(params);
|
||||
@@ -9373,6 +9528,16 @@ export class ClaudeAcpAgent {
|
||||
if (!option) {
|
||||
throw RequestError.invalidParams(undefined, `Unknown config option: ${params.configId}`);
|
||||
}
|
||||
// The value must match the type the option was advertised with: a
|
||||
// `type: "boolean"` option takes `true`/`false`, not "on"/"off", and a
|
||||
// `type: "select"` option takes a string.
|
||||
const expectedType = option.type === "boolean" ? "boolean" : "string";
|
||||
if (typeof params.value !== expectedType) {
|
||||
throw RequestError.invalidParams(
|
||||
{ configId: params.configId, value: params.value },
|
||||
`Config option ${params.configId} takes a ${expectedType} value, but received: ${JSON.stringify(params.value)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Fast mode and ultracode carry a boolean value (for Clients that opted
|
||||
// into boolean config options) or the "on"/"off" select fallback, so they
|
||||
@@ -9658,6 +9823,7 @@ export class ClaudeAcpAgent {
|
||||
(notification) => this.client.sessionUpdate(asSdkSessionNotification(notification)),
|
||||
this.subagentModels(sessionId),
|
||||
!pending,
|
||||
session?.cwd,
|
||||
)
|
||||
: undefined;
|
||||
rfdReplay?.scan(messages, sessionId);
|
||||
@@ -10272,19 +10438,20 @@ export class ClaudeAcpAgent {
|
||||
/** Forward a permission request to the client, wiring the tool call's
|
||||
* `signal` through as a `cancellationSignal`. When the turn is cancelled
|
||||
* while the client's prompt is still open the signal aborts, the SDK sends
|
||||
* `$/cancel_request`, and our local abort race settles even if the client
|
||||
* ignores it. A `cancelled` outcome, request rejection, and local abort all
|
||||
* surface the same "Tool use aborted" the callers already expect. */
|
||||
* `$/cancel_request` and stops waiting for the client's answer. The
|
||||
* rejection surfaces as the "Tool use aborted" that the callers expect.
|
||||
* `canUseTool` decides what a `cancelled` outcome means. */
|
||||
private async requestPermissionFromClient(
|
||||
params: AcpPermissionRequest,
|
||||
toolName: string,
|
||||
signal: AbortSignal,
|
||||
toolSignal: AbortSignal,
|
||||
parentToolUseId: string | undefined,
|
||||
ownerSessionId: string,
|
||||
agentID: string | undefined,
|
||||
toolInput: unknown,
|
||||
previewContent?: ToolCallContent[],
|
||||
): Promise<RequestPermissionResponse> {
|
||||
const signal = this.untilSessionClosed(ownerSessionId, toolSignal);
|
||||
if (signal.aborted) throw new Error("Tool use aborted");
|
||||
// The SDK may invoke `canUseTool` (and therefore this permission request)
|
||||
// before the assistant message's tool_use block streams to us. Some ACP clients
|
||||
@@ -10304,9 +10471,6 @@ export class ClaudeAcpAgent {
|
||||
);
|
||||
if (signal.aborted) throw new Error("Tool use aborted");
|
||||
|
||||
// Do not rely on every ACP client settling requestPermission after the
|
||||
// cancellation signal. The local race guarantees that Claude's tool call
|
||||
// is released even when an older or broken client ignores $/cancel_request.
|
||||
// The request goes to a subagent's child session for a tool call of that
|
||||
// subagent, but it blocks the turn of the owner session.
|
||||
try {
|
||||
@@ -10314,7 +10478,7 @@ export class ClaudeAcpAgent {
|
||||
this.withPendingUserInput(
|
||||
ownerSessionId,
|
||||
signal,
|
||||
() => raceWithAbort(this.client.requestPermission(params, signal), signal),
|
||||
() => this.client.requestPermission(params, signal),
|
||||
agentID,
|
||||
),
|
||||
);
|
||||
@@ -10327,18 +10491,27 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
|
||||
/** Send the client an elicitation for a callback of Claude Code. When the
|
||||
* callback's `signal` aborts, the SDK sends `$/cancel_request` and keeps
|
||||
* waiting for the client's reply, which a client that ignores the cancel
|
||||
* never sends. So the call also rejects at once on the abort, as
|
||||
* {@link requestPermissionFromClient} does for a permission request. Left
|
||||
* open, the request would stay in `openUserInputs` (see
|
||||
* {@link withPendingUserInput}), which keeps steering non-interrupting, and
|
||||
* a child's `openRequests` would not drop. */
|
||||
* callback's `signal` aborts, the SDK sends `$/cancel_request` and the call
|
||||
* rejects at once, without waiting for a reply that a client ignoring the
|
||||
* cancel never sends. Left open, the request would stay in
|
||||
* `openUserInputs` (see {@link withPendingUserInput}), which keeps steering
|
||||
* non-interrupting, and a child's `openRequests` would not drop. */
|
||||
private elicitFromClient(
|
||||
request: CreateElicitationRequest,
|
||||
signal: AbortSignal,
|
||||
): Promise<CreateElicitationResponse> {
|
||||
return raceWithAbort(this.client.createElicitation(request, signal), signal);
|
||||
return this.client.createElicitation(request, signal);
|
||||
}
|
||||
|
||||
/** `signal`, also aborted when `teardownSession` aborts the
|
||||
* `abortController` of session `sessionId`. Claude Code aborts a callback's
|
||||
* own signal when its query closes, but not necessarily before
|
||||
* `session/close` responds, and v2 requires every pending permission
|
||||
* request and elicitation of the session to be cancelled with
|
||||
* `$/cancel_request` before that response. */
|
||||
private untilSessionClosed(sessionId: string, signal: AbortSignal): AbortSignal {
|
||||
const sessionSignal = this.sessions[sessionId]?.abortController?.signal;
|
||||
return sessionSignal ? AbortSignal.any([signal, sessionSignal]) : signal;
|
||||
}
|
||||
|
||||
/** Settle the tool call `ensureToolCallEmitted` emitted for a
|
||||
@@ -10495,6 +10668,13 @@ export class ClaudeAcpAgent {
|
||||
return async (input) => {
|
||||
const session = this.sessions[sessionId];
|
||||
if (!session || !input.agent_id) return { continue: true };
|
||||
// A hook's `cwd` is the child's working directory, e.g. its worktree.
|
||||
// Reported before the hook returns, so before the work that follows it.
|
||||
await session.nativeSubagentRuntime
|
||||
?.reportCwd(input.agent_id, input.cwd)
|
||||
.catch((error) =>
|
||||
this.logger.error(`Session ${sessionId}: failed to report subagent cwd: ${error}`),
|
||||
);
|
||||
const options: SessionConfigOption[] = [];
|
||||
if (input.permission_mode) {
|
||||
const mode = session.modes.availableModes.find((mode) => mode.id === input.permission_mode);
|
||||
@@ -10813,10 +10993,14 @@ export class ClaudeAcpAgent {
|
||||
// cancel does not stop. Answer such a request with a denial so the
|
||||
// subagent reads "the user refused" and goes on. Throwing instead makes
|
||||
// Claude Code deny it as "Tool permission request failed".
|
||||
// On ACP v2, `cancelled` also answers a prompt that the user dismissed
|
||||
// or that the client declined by policy. Without a `session/cancel`
|
||||
// (`session.cancelled` unset), that is the user refusing the tool, so it
|
||||
// gets the same denial and the turn goes on.
|
||||
if (
|
||||
response.outcome?.outcome === "cancelled" &&
|
||||
this.sessions[sessionId] === session &&
|
||||
!blocksForeground(session, agentID)
|
||||
(!blocksForeground(session, agentID) || (this.v2 && !session.cancelled))
|
||||
) {
|
||||
return {
|
||||
behavior: "deny",
|
||||
@@ -10896,7 +11080,8 @@ export class ClaudeAcpAgent {
|
||||
* an error, is a `cancel`.
|
||||
*/
|
||||
private handleMcpElicitation(sessionId: string, support: ElicitationSupport): OnElicitation {
|
||||
return async (request, { signal }) => {
|
||||
return async (request, { signal: callbackSignal }) => {
|
||||
const signal = this.untilSessionClosed(sessionId, callbackSignal);
|
||||
const isUrl = request.mode === "url";
|
||||
if ((isUrl && !support.url) || (!isUrl && !support.form)) {
|
||||
return { action: "cancel" };
|
||||
@@ -10942,9 +11127,10 @@ export class ClaudeAcpAgent {
|
||||
requestSessionId: string,
|
||||
toolInput: Record<string, unknown>,
|
||||
toolUseID: string,
|
||||
signal: AbortSignal,
|
||||
toolSignal: AbortSignal,
|
||||
agentID: string | undefined,
|
||||
): Promise<PermissionResult> {
|
||||
const signal = this.untilSessionClosed(sessionId, toolSignal);
|
||||
const questions = extractAskUserQuestions(toolInput);
|
||||
if (!questions) {
|
||||
return { behavior: "deny", message: "AskUserQuestion called with no valid questions." };
|
||||
@@ -10997,7 +11183,8 @@ export class ClaudeAcpAgent {
|
||||
* refusal-fallback consent prompt, rendered as an ACP form elicitation.
|
||||
*/
|
||||
private handleUserDialog(sessionId: string): OnUserDialog {
|
||||
return async (request, { signal }) => {
|
||||
return async (request, { signal: callbackSignal }) => {
|
||||
const signal = this.untilSessionClosed(sessionId, callbackSignal);
|
||||
if (request.dialogKind !== REFUSAL_FALLBACK_DIALOG_KIND) {
|
||||
return { behavior: "cancelled" };
|
||||
}
|
||||
@@ -11093,7 +11280,7 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
}
|
||||
|
||||
private async sendAvailableCommandsUpdate(sessionId: string): Promise<void> {
|
||||
async sendAvailableCommandsUpdate(sessionId: string): Promise<void> {
|
||||
if (!this.sessions[sessionId]) return;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId,
|
||||
@@ -11107,20 +11294,28 @@ export class ClaudeAcpAgent {
|
||||
/**
|
||||
* The work that follows a session setup response: the session's commands,
|
||||
* the current states of the subagents a load replayed, and MCP server
|
||||
* authentication. A v2 client gets the commands in the setup response itself,
|
||||
* and the subagent states from the v2 surface once it has sent its own
|
||||
* response (`src/v2/agent.ts`), so it gets neither here.
|
||||
* authentication. On v2 the setup handlers of `src/v2/agent.ts` do this
|
||||
* work once they have sent their response, so it does nothing here.
|
||||
*/
|
||||
private afterSetupResponse(sessionId: string, mcpServers: McpServer[]): void {
|
||||
if (this.v2) return;
|
||||
setTimeout(() => {
|
||||
if (!this.v2) {
|
||||
this.sendAvailableCommandsUpdate(sessionId);
|
||||
void this.publishCurrentSubagentStates(sessionId);
|
||||
}
|
||||
this.sendAvailableCommandsUpdate(sessionId);
|
||||
void this.publishCurrentSubagentStates(sessionId);
|
||||
startMcpAuthentication(this, sessionId, mcpServers);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts OAuth in the background for the servers of `mcpServers` that
|
||||
* Claude Code reports as needing it, which sends the client a URL
|
||||
* elicitation. The v2 surface calls it after the setup response, since v2
|
||||
* asks for no elicitation before the `session/new` response.
|
||||
*/
|
||||
authenticateMcpServers(sessionId: string, mcpServers: McpServer[]): void {
|
||||
startMcpAuthentication(this, sessionId, mcpServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the current state of each subagent that the last load of
|
||||
* `sessionId` replayed (see {@link RfdSubagentReplay.currentStates}). Run it
|
||||
@@ -11617,7 +11812,8 @@ export class ClaudeAcpAgent {
|
||||
},
|
||||
resumedSession?: ResumedSessionSnapshot,
|
||||
): Promise<NewSessionResponse> {
|
||||
// Reject a prompt the agent cannot apply before reusing a live session.
|
||||
// Reject a bad `cwd` or prompt before reusing or tearing down a live session.
|
||||
await this.validateCwd(params.cwd, params.additionalDirectories);
|
||||
clientSystemPrompt(params.systemPrompt);
|
||||
const existingSession = this.sessions[params.sessionId];
|
||||
// A recreated live session keeps its mode, not the mode of the transcript.
|
||||
@@ -11669,8 +11865,22 @@ export class ClaudeAcpAgent {
|
||||
* directory before we create a session. Throws an `invalidParams` error with
|
||||
* an actionable message so clients (e.g. Zed) can surface it to the user
|
||||
* instead of failing later with an opaque SDK error.
|
||||
*
|
||||
* Each `additionalDirectories` entry must be absolute too. Only the lexical
|
||||
* rule is checked there: a missing extra root does not fail a session.
|
||||
*/
|
||||
private async validateCwd(cwd: string): Promise<void> {
|
||||
private async validateCwd(
|
||||
cwd: string,
|
||||
additionalDirectories?: NewSessionRequest["additionalDirectories"],
|
||||
): Promise<void> {
|
||||
for (const dir of additionalDirectories ?? []) {
|
||||
if (!path.isAbsolute(dir)) {
|
||||
throw RequestError.invalidParams(
|
||||
{ additionalDirectories },
|
||||
`\`additionalDirectories\` entries must be absolute paths, but received: ${dir}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!path.isAbsolute(cwd)) {
|
||||
throw RequestError.invalidParams(
|
||||
{ cwd },
|
||||
@@ -11730,7 +11940,7 @@ export class ClaudeAcpAgent {
|
||||
// this check a session is created against a missing directory and the
|
||||
// failure only surfaces later as a confusing "native binary failed to
|
||||
// launch" error from the SDK (see issue #749).
|
||||
await this.validateCwd(params.cwd);
|
||||
await this.validateCwd(params.cwd, params.additionalDirectories);
|
||||
const clientPrompt = clientSystemPrompt(params.systemPrompt);
|
||||
|
||||
// We want to create a new session id unless it is resume,
|
||||
@@ -11957,6 +12167,7 @@ export class ClaudeAcpAgent {
|
||||
...providerEnv,
|
||||
// Opt-in to session state events like when the agent is idle
|
||||
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
|
||||
...STARTUP_FAILURE_RESULTS_ENV,
|
||||
};
|
||||
// Scopes the context-window cache to this query's backend (see
|
||||
// `contextWindowCache`). Derived from the same `env` object handed to the
|
||||
@@ -12211,7 +12422,7 @@ export class ClaudeAcpAgent {
|
||||
) {
|
||||
throw RequestError.sessionNotFound(sessionId);
|
||||
}
|
||||
throw error;
|
||||
throw startupFailureError(error) ?? error;
|
||||
}
|
||||
timing.phase("sdk-initialize");
|
||||
|
||||
@@ -12488,8 +12699,13 @@ export class ClaudeAcpAgent {
|
||||
emittedToolCalls: new Set(),
|
||||
eagerToolCallSessions: new Map(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(
|
||||
sessionId,
|
||||
this.backgroundWork.enabled && options.perTaskStopAffordance === true,
|
||||
),
|
||||
stopsWorkflowsOnCancel:
|
||||
options.perTaskStopAffordance === true &&
|
||||
!this.backgroundWork.enabled &&
|
||||
!clientSupportsAsyncTasks(this.clientCapabilities),
|
||||
nativeSubagentsByTaskId: new Map(),
|
||||
nativeSubagentTaskIdByToolUseId: new Map(),
|
||||
@@ -12507,6 +12723,11 @@ export class ClaudeAcpAgent {
|
||||
fileChangeReporter,
|
||||
};
|
||||
timing.phase("register");
|
||||
this.backgroundWork.sync(
|
||||
sessionId,
|
||||
this.sessions[sessionId],
|
||||
this.foregroundState(sessionId),
|
||||
);
|
||||
this.refreshContextWindowInBackground(sessionId, this.sessions[sessionId]);
|
||||
|
||||
return {
|
||||
@@ -13581,13 +13802,21 @@ function getAvailableSlashCommands(
|
||||
})
|
||||
.filter((command: AvailableCommand) => !UNSUPPORTED_COMMANDS.includes(command.name));
|
||||
|
||||
// Command names must not contain whitespace and must be unique. A name
|
||||
// like "my command" is dropped, and of two commands named "review" only the
|
||||
// first is kept.
|
||||
const names = new Set<string>();
|
||||
return [
|
||||
...advertised,
|
||||
MCP_AVAILABLE_COMMAND,
|
||||
...remoteControlCommands.filter(
|
||||
(command) => !claudeCodeOffersCommand(commands, terminalCommands, command.name),
|
||||
),
|
||||
];
|
||||
].filter((command) => {
|
||||
if (/\s/.test(command.name) || names.has(command.name)) return false;
|
||||
names.add(command.name);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function formatUriAsLink(uri: string): string {
|
||||
@@ -13607,16 +13836,29 @@ function formatUriAsLink(uri: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
/** The prompt content block types that `promptToClaude` converts. The agent
|
||||
* advertises no `audio` prompt capability. */
|
||||
const SUPPORTED_PROMPT_CONTENT_TYPES: readonly string[] = [
|
||||
"text",
|
||||
"image",
|
||||
"resource_link",
|
||||
"resource",
|
||||
];
|
||||
|
||||
export function promptToClaude(prompt: PromptRequest): SDKUserMessage {
|
||||
const content: any[] = [];
|
||||
const context: any[] = [];
|
||||
|
||||
const firstText = prompt.prompt.find((chunk) => chunk.type === "text");
|
||||
for (const chunk of prompt.prompt) {
|
||||
switch (chunk.type) {
|
||||
case "text": {
|
||||
let text = chunk.text;
|
||||
// change /mcp:server:command args -> /server:command (MCP) args
|
||||
const mcpMatch = text.match(/^\/mcp:([^:\s]+):(\S+)(?:\s(.*))?$/);
|
||||
// A slash command is the first text block. Change
|
||||
// `/mcp:server:command args` to `/server:command (MCP) args`; the
|
||||
// arguments can span lines.
|
||||
const mcpMatch =
|
||||
chunk === firstText ? text.match(/^\/mcp:([^:\s]+):(\S+)(?:\s+([\s\S]*))?$/) : null;
|
||||
if (mcpMatch) {
|
||||
const [, server, command, args] = mcpMatch;
|
||||
text = `/${server}:${command} (MCP)${args ? ` ${args}` : ""}`;
|
||||
|
||||
131
src/background-output.ts
Normal file
131
src/background-output.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* The output of a command that went to the background, read from the file
|
||||
* that Claude Code writes it to. Claude Code streams no output of such a
|
||||
* command, so its display terminal would show only the placeholder of the
|
||||
* Bash result ("Command running in background with ID …") until the command
|
||||
* exits. ACP v2 asks an Agent to keep a listed terminal's output current
|
||||
* whenever it can read it (the v2 prompt lifecycle, "Background work").
|
||||
*
|
||||
* The file is polled: watching a file is not reliable on every platform, and
|
||||
* a missing file only means that nothing was written yet.
|
||||
*/
|
||||
import { open } from "node:fs/promises";
|
||||
import { StringDecoder } from "node:string_decoder";
|
||||
|
||||
/** How often a running command's output file is read. */
|
||||
export const BACKGROUND_OUTPUT_POLL_MS = 500;
|
||||
|
||||
/** The most bytes one read takes, so a burst of output does not block the loop. */
|
||||
export const BACKGROUND_OUTPUT_READ_BYTES = 256 * 1024;
|
||||
|
||||
/**
|
||||
* The most unread bytes that are still sent. A command that writes faster
|
||||
* than the polls read falls behind. Past this, the reads skip to the file's
|
||||
* last bytes, as a snapshot: a terminal shows the latest output, and a
|
||||
* client keeps a bounded amount of it anyway.
|
||||
*/
|
||||
export const BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES = 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Text that the file adds. A snapshot replaces the terminal's output so far:
|
||||
* the first one replaces the placeholder of the Bash result, and a later one
|
||||
* follows a skip or a file that shrank. Other text appends to it.
|
||||
*/
|
||||
export type BackgroundOutput = { text: string; replaces: boolean };
|
||||
|
||||
/**
|
||||
* Follows the output file `path` of one background command and hands each
|
||||
* piece of new text to `publish`, in order. Reads never overlap. A read that
|
||||
* fails, for example because the file does not exist yet, is tried again at
|
||||
* the next poll. `publish` must not reject: the next read waits for it.
|
||||
*/
|
||||
export class BackgroundOutputTail {
|
||||
private offset = 0;
|
||||
private decoder = new StringDecoder("utf8");
|
||||
/** Whether the next text replaces the terminal's output. */
|
||||
private replaces = true;
|
||||
private stopped = false;
|
||||
private reading: Promise<void> = Promise.resolve();
|
||||
private readonly timer: ReturnType<typeof setInterval>;
|
||||
|
||||
constructor(
|
||||
private readonly path: string,
|
||||
private readonly publish: (output: BackgroundOutput) => Promise<void>,
|
||||
) {
|
||||
this.timer = setInterval(() => void this.read(false), BACKGROUND_OUTPUT_POLL_MS);
|
||||
// A command that runs on must not keep the agent process alive.
|
||||
this.timer.unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the rest of the file and stops. The command ended, so what the file
|
||||
* holds is its whole output, and it goes out before the terminal's exit.
|
||||
*/
|
||||
async finish(): Promise<void> {
|
||||
clearInterval(this.timer);
|
||||
await this.read(true);
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
/** Stops without reading more: the session ended. */
|
||||
stop(): void {
|
||||
clearInterval(this.timer);
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads what the file adds, one read of at most
|
||||
* {@link BACKGROUND_OUTPUT_READ_BYTES} at a time, and publishes each read's
|
||||
* text before the next. `toEnd` reads until the end of the file, and ends a
|
||||
* character that the command cut off with a replacement character.
|
||||
*/
|
||||
private read(toEnd: boolean): Promise<void> {
|
||||
this.reading = this.reading.then(async () => {
|
||||
if (this.stopped) return;
|
||||
let file;
|
||||
try {
|
||||
file = await open(this.path, "r");
|
||||
} catch {
|
||||
// Nothing written yet, or the file is gone: try again at the next
|
||||
// poll. At the end, the terminal keeps the output it has.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { size } = await file.stat();
|
||||
// A file that shrank is a new one: its output starts over. Past the
|
||||
// backlog, the reads skip to the file's last bytes.
|
||||
const start = size < this.offset ? 0 : this.offset;
|
||||
const skipTo = Math.max(start, size - BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES);
|
||||
if (skipTo !== this.offset) {
|
||||
this.offset = skipTo;
|
||||
this.decoder = new StringDecoder("utf8");
|
||||
this.replaces = true;
|
||||
}
|
||||
const buffer = Buffer.alloc(BACKGROUND_OUTPUT_READ_BYTES);
|
||||
let bytesRead: number;
|
||||
do {
|
||||
({ bytesRead } = await file.read(buffer, 0, buffer.length, this.offset));
|
||||
this.offset += bytesRead;
|
||||
const text = this.decoder.write(buffer.subarray(0, bytesRead));
|
||||
if (text !== "") await this.send(text);
|
||||
} while (toEnd && bytesRead > 0);
|
||||
if (toEnd) {
|
||||
const rest = this.decoder.end();
|
||||
// A command that wrote nothing still replaces the placeholder.
|
||||
if (rest !== "" || this.replaces) await this.send(rest);
|
||||
}
|
||||
} catch {
|
||||
// The read failed: try again at the next poll.
|
||||
} finally {
|
||||
await file.close();
|
||||
}
|
||||
});
|
||||
return this.reading;
|
||||
}
|
||||
|
||||
private async send(text: string): Promise<void> {
|
||||
const replaces = this.replaces;
|
||||
this.replaces = false;
|
||||
await this.publish({ text, replaces });
|
||||
}
|
||||
}
|
||||
627
src/background-work.ts
Normal file
627
src/background-work.ts
Normal file
@@ -0,0 +1,627 @@
|
||||
/**
|
||||
* The background work of a session: the work that Claude Code keeps running
|
||||
* independently of the session's foreground work, which ACP v2 lists on every
|
||||
* `state_update` of the session as `background` (the v2 prompt lifecycle,
|
||||
* "Background work").
|
||||
*
|
||||
* The lists are built from Claude Code's live background task set
|
||||
* (`background_tasks_changed`), without the tasks it marks `ambient`, which
|
||||
* are housekeeping that it started on its own. The root session and each
|
||||
* announced subagent session (the subagents RFD form) has its own list. Each
|
||||
* task is listed once, in the list of the session that runs it:
|
||||
*
|
||||
* - A command that went to the background: its display terminal, from the
|
||||
* Bash tool result until its task ends (`Session.backgroundTerminals`).
|
||||
* - A detached subagent: its child session, in its parent's list, while it
|
||||
* works, and while it is idle with background work of its own.
|
||||
* - A task that a tool call started, such as a `Monitor`, a `Workflow` or a
|
||||
* backgrounded MCP call: that tool call, kept `in_progress` (or `paused`)
|
||||
* from its tool result until its task ends (`Session.backgroundToolCalls`).
|
||||
* - Any other task, once its `task_started` came and the tool call that
|
||||
* started it, if any, returned: a `_claude/task` item with the task's id
|
||||
* and description. For example, a task whose tool call ended before the
|
||||
* task joined the level.
|
||||
*
|
||||
* Only a session whose interrupt spares background tasks reports its lists
|
||||
* (`Session.reportsBackground`): `session/cancel` must leave listed work
|
||||
* running.
|
||||
*/
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AcpSessionNotification } from "./acp-subagents.js";
|
||||
import type { NativeSubagent } from "./native-subagents.js";
|
||||
import { type AcpToolCallRenderer, V2_CANCELLED, V2_PAUSED } from "./tool-calls/renderer.js";
|
||||
import { type BackgroundOutput, BackgroundOutputTail } from "./background-output.js";
|
||||
|
||||
/** An item of a `background` list. */
|
||||
export type BackgroundItem =
|
||||
| { type: "terminal"; terminalId: string }
|
||||
| { type: "tool_call"; toolCallId: string }
|
||||
| { type: "subagent"; sessionId: string }
|
||||
| { type: "_claude/task"; taskId: string; description: string };
|
||||
|
||||
/** The display terminal of a command that went to the background (see `Session.backgroundTerminals`). */
|
||||
export interface BackgroundTerminal {
|
||||
/** The Bash tool call, whose id names the terminal. */
|
||||
toolCallId: string;
|
||||
/** The subagent tool call that ran the command, if any. */
|
||||
parentToolUseId?: string;
|
||||
/** The file that Claude Code writes the command's output to, from the Bash result. */
|
||||
outputFilePath?: string;
|
||||
/** Reads that file into the terminal while the command runs. */
|
||||
output?: BackgroundOutputTail;
|
||||
}
|
||||
|
||||
/** A tool call whose task runs on in the background (see `Session.backgroundToolCalls`). */
|
||||
export interface BackgroundToolCall {
|
||||
toolCallId: string;
|
||||
/** The tool use of the call, which Claude Code no longer caches after its result. */
|
||||
toolUse: { name: string; input: unknown };
|
||||
/** The subagent tool call that made the call, if any. */
|
||||
parentToolUseId?: string;
|
||||
paused?: boolean;
|
||||
}
|
||||
|
||||
/** How a background task ended, as Claude Code reports it. Undefined when it did not say. */
|
||||
export type BackgroundTaskEnd = "completed" | "failed" | "stopped" | undefined;
|
||||
|
||||
/** The live background task set of `background_tasks_changed`, by task id. */
|
||||
export type BackgroundLevel = Map<
|
||||
string,
|
||||
{ ambient: boolean; taskType: string; description: string; parentTaskId?: string }
|
||||
>;
|
||||
|
||||
/** The work state of a session, as its `state_update` names it. */
|
||||
export type WorkStateName = "idle" | "running" | "requires_action" | "unknown";
|
||||
|
||||
/** A new list of the session `sessionId`, with what its repeated state needs. */
|
||||
export interface BackgroundReport {
|
||||
sessionId: string;
|
||||
items: BackgroundItem[];
|
||||
/** The session's work state now, or undefined when it has none yet. */
|
||||
state: WorkStateName | undefined;
|
||||
/** The parent of a subagent session, which mirrors the child's state. */
|
||||
parentSessionId?: string;
|
||||
}
|
||||
|
||||
/** Where the agent reports the lists (see the agent's `background` option). */
|
||||
export interface BackgroundSink {
|
||||
/** The list of a session changed, or is reported for the first time. */
|
||||
changed(report: BackgroundReport): void;
|
||||
/** The session ended: its list is no longer reported. */
|
||||
forget(sessionId: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The state of a session that other modules own, and that the background
|
||||
* work reads: Claude Code's tasks as the consumer tracks them, the subagent
|
||||
* sessions, and the query that stops a task.
|
||||
*/
|
||||
export interface BackgroundOwner {
|
||||
liveBackgroundTasks: Map<string, { parentToolUseId?: string; isSubagent: boolean }>;
|
||||
nativeSubagentsByTaskId?: Map<string, NativeSubagent>;
|
||||
query: { stopTask(taskId: string): Promise<void> };
|
||||
}
|
||||
|
||||
/** A task of a `background_tasks_changed` payload. */
|
||||
export type LevelTask = {
|
||||
task_id: string;
|
||||
task_type: string;
|
||||
description: string;
|
||||
parent_task_id?: string;
|
||||
ambient?: boolean;
|
||||
};
|
||||
|
||||
/** The most returned tool calls that {@link SessionBackgroundWork} remembers. */
|
||||
const MAX_RETURNED_CALLS = 1024;
|
||||
|
||||
/** The tools whose structured result names the task that the call started. */
|
||||
const TASK_TOOLS = new Set(["Monitor", "Workflow"]);
|
||||
|
||||
/**
|
||||
* The background work of one root session, and of its subagent sessions: the
|
||||
* terminals and tool calls of the tasks that run in the background, and what
|
||||
* the client is told of them.
|
||||
*
|
||||
* Terminals are kept for every client that renders them, as before the
|
||||
* background lists. The rest is kept only when `reported`.
|
||||
*/
|
||||
export class SessionBackgroundWork {
|
||||
/** Claude Code's live background task set, from its last `background_tasks_changed`. */
|
||||
private level: BackgroundLevel = new Map();
|
||||
/** The terminals of the commands that went to the background, by task id. */
|
||||
private readonly terminals = new Map<string, BackgroundTerminal>();
|
||||
/** The tool calls whose tasks went on in the background, by task id. */
|
||||
private readonly toolCalls = new Map<string, BackgroundToolCall>();
|
||||
/**
|
||||
* The tool calls that returned, oldest first. A task that a call started
|
||||
* is part of that call until it returns, and the foreground work waits on
|
||||
* it. The task's `task_started` can come after the call returned. Bounded
|
||||
* by {@link MAX_RETURNED_CALLS}: a task joins the level around the time its
|
||||
* call returns.
|
||||
*/
|
||||
private readonly returned = new Set<string>();
|
||||
|
||||
/**
|
||||
* `reported`: whether the client gets the lists. Only when Claude Code's
|
||||
* interrupt spares background tasks, as `perTaskStopAffordance` makes it.
|
||||
*/
|
||||
constructor(
|
||||
private readonly sessionId: string,
|
||||
readonly reported: boolean,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Keeps the display terminal of the command `taskId` running after its Bash
|
||||
* result, until its task ends (see `withRunningTerminal`).
|
||||
*/
|
||||
terminalStarted(taskId: string, terminal: BackgroundTerminal): void {
|
||||
this.terminals.set(taskId, terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads what a tool result message tells of the background tasks:
|
||||
*
|
||||
* - The returned calls no longer hold the tasks that they started.
|
||||
* - Each call whose task goes on in the background is registered: a
|
||||
* `Monitor` or `Workflow` call, whose structured result names its task,
|
||||
* or a call that a live task names as its tool use, such as a
|
||||
* backgrounded MCP call. The task must be in the level: one that ended
|
||||
* already, or that Claude Code marks `ambient`, leaves its call as it is.
|
||||
*
|
||||
* Returns the ids of the registered calls, whose result then reports
|
||||
* `in_progress` (see `withRunningToolCall`).
|
||||
*/
|
||||
toolResults(
|
||||
owner: BackgroundOwner,
|
||||
content: unknown,
|
||||
toolUseResult: unknown,
|
||||
toolUses: Record<string, { name: string; input: unknown }>,
|
||||
parentToolUseId: string | null,
|
||||
): Set<string> {
|
||||
const registered = new Set<string>();
|
||||
if (!this.reported || !Array.isArray(content)) return registered;
|
||||
const results = content.filter(
|
||||
(block): block is { type: "tool_result"; tool_use_id: string; is_error?: boolean } =>
|
||||
typeof block === "object" &&
|
||||
block !== null &&
|
||||
block.type === "tool_result" &&
|
||||
typeof block.tool_use_id === "string",
|
||||
);
|
||||
for (const { tool_use_id: toolCallId } of results) {
|
||||
this.returned.add(toolCallId);
|
||||
if (this.returned.size > MAX_RETURNED_CALLS) {
|
||||
this.returned.delete(this.returned.values().next().value!);
|
||||
}
|
||||
}
|
||||
const succeeded = results.filter((block) => block.is_error !== true);
|
||||
const structuredTaskId =
|
||||
succeeded.length === 1 &&
|
||||
typeof toolUseResult === "object" &&
|
||||
toolUseResult !== null &&
|
||||
typeof (toolUseResult as { taskId?: unknown }).taskId === "string"
|
||||
? (toolUseResult as { taskId: string }).taskId
|
||||
: undefined;
|
||||
for (const { tool_use_id: toolCallId } of succeeded) {
|
||||
const toolUse = toolUses[toolCallId];
|
||||
if (!toolUse) continue;
|
||||
const taskId =
|
||||
(TASK_TOOLS.has(toolUse.name) ? structuredTaskId : undefined) ??
|
||||
[...owner.liveBackgroundTasks].find(
|
||||
([, task]) => task.parentToolUseId === toolCallId && !task.isSubagent,
|
||||
)?.[0];
|
||||
const task = taskId === undefined ? undefined : this.level.get(taskId);
|
||||
if (
|
||||
!task ||
|
||||
task.ambient ||
|
||||
task.taskType === "local_bash" ||
|
||||
task.taskType === "local_agent"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
this.toolCalls.set(taskId!, {
|
||||
toolCallId,
|
||||
toolUse,
|
||||
...(parentToolUseId ? { parentToolUseId } : {}),
|
||||
});
|
||||
registered.add(toolCallId);
|
||||
}
|
||||
return registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts reading the output of the background command `taskId` into its
|
||||
* terminal, when the client gets the lists and the Bash result named the
|
||||
* command's output file. `send` sends an update of the terminal's tool
|
||||
* call, routed as the call's other updates. Called once the Bash result's
|
||||
* own updates are sent: the first output replaces the placeholder that they
|
||||
* gave the terminal.
|
||||
*/
|
||||
followOutput(taskId: string, send: Send): void {
|
||||
const terminal = this.terminals.get(taskId);
|
||||
if (!this.reported || !terminal?.outputFilePath || terminal.output) return;
|
||||
terminal.output = new BackgroundOutputTail(terminal.outputFilePath, (output) =>
|
||||
send(terminalOutputUpdate(this.sessionId, terminal, output)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The task `taskId` ended. Reads the rest of its command's output, then
|
||||
* exits its terminal, and gives a tool call whose task it was its final
|
||||
* status: `cancelled` for a task that Claude Code stopped, and `completed`
|
||||
* for one that ended without saying how, such as one that the level
|
||||
* dropped: it no longer runs, and reported no failure. Claude Code reports
|
||||
* no exit code for a command, and `completed` does not mean 0: Claude Code
|
||||
* accepts some non-zero codes as a success, such as grep's 1.
|
||||
*/
|
||||
async taskEnded(taskId: string, end: BackgroundTaskEnd, send: Send): Promise<void> {
|
||||
const terminal = this.terminals.get(taskId);
|
||||
if (terminal) {
|
||||
await terminal.output?.finish();
|
||||
this.terminals.delete(taskId);
|
||||
await send(terminalExit(this.sessionId, terminal));
|
||||
}
|
||||
const toolCall = this.toolCalls.get(taskId);
|
||||
if (toolCall) {
|
||||
this.toolCalls.delete(taskId);
|
||||
await send(
|
||||
toolCallStatusUpdate(
|
||||
this.sessionId,
|
||||
toolCall,
|
||||
end === "stopped" ? V2_CANCELLED : end === "failed" ? "failed" : "completed",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The update that reports the background tool call of the task `taskId` as
|
||||
* `paused`, or as `in_progress` again, when `paused` changes it. Claude Code
|
||||
* pauses a workflow this way.
|
||||
*/
|
||||
taskPaused(taskId: string, paused: boolean): AcpSessionNotification | undefined {
|
||||
const toolCall = this.toolCalls.get(taskId);
|
||||
if (!toolCall || (toolCall.paused ?? false) === paused) return undefined;
|
||||
toolCall.paused = paused;
|
||||
return toolCallStatusUpdate(this.sessionId, toolCall, paused ? V2_PAUSED : "in_progress");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes Claude Code's new live background task set. The terminals and tool
|
||||
* calls whose tasks it no longer holds end first, before a list drops them,
|
||||
* as v2 requires. A terminal or tool call is registered at its tool result,
|
||||
* after its task joined the level, so a later level without the task means
|
||||
* that it ended. Claude Code usually sends a task's end before that level,
|
||||
* which ends them first, but the SDK does not promise that order.
|
||||
*/
|
||||
async levelChanged(tasks: LevelTask[], send: Send): Promise<void> {
|
||||
if (!this.reported) return;
|
||||
const level: BackgroundLevel = new Map(
|
||||
tasks.map((task) => [
|
||||
task.task_id,
|
||||
{
|
||||
ambient: task.ambient === true,
|
||||
taskType: task.task_type,
|
||||
description: task.description,
|
||||
...(task.parent_task_id !== undefined ? { parentTaskId: task.parent_task_id } : {}),
|
||||
},
|
||||
]),
|
||||
);
|
||||
for (const taskId of [...this.terminals.keys(), ...this.toolCalls.keys()]) {
|
||||
if (!level.has(taskId)) await this.taskEnded(taskId, undefined, send);
|
||||
}
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* The query closed: Claude Code's background tasks end with its process.
|
||||
* Stops reading output, and returns the updates that exit the root
|
||||
* session's terminals and cancel its tool calls. The level empties, so the
|
||||
* next lists are empty. A subagent's command or tool call is left as it
|
||||
* was: it is on the child's stream, which the closed query's routing owned.
|
||||
*/
|
||||
queryClosed(): AcpSessionNotification[] {
|
||||
for (const terminal of this.terminals.values()) terminal.output?.stop();
|
||||
if (!this.reported) return [];
|
||||
const updates: AcpSessionNotification[] = [];
|
||||
for (const [taskId, terminal] of this.terminals) {
|
||||
if (terminal.parentToolUseId) continue;
|
||||
this.terminals.delete(taskId);
|
||||
updates.push(terminalExit(this.sessionId, terminal));
|
||||
}
|
||||
for (const [taskId, toolCall] of this.toolCalls) {
|
||||
if (toolCall.parentToolUseId) continue;
|
||||
this.toolCalls.delete(taskId);
|
||||
updates.push(toolCallStatusUpdate(this.sessionId, toolCall, V2_CANCELLED));
|
||||
}
|
||||
this.level = new Map();
|
||||
this.returned.clear();
|
||||
return updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `background` list of the root session and of each of its announced
|
||||
* subagent sessions, with what their repeated states need, deeper sessions
|
||||
* first and the root last: a child's own list goes out before its parent's
|
||||
* list drops it. Each list is empty while the level has not been reported:
|
||||
* Claude Code sends none before the first background task starts.
|
||||
*/
|
||||
reports(owner: BackgroundOwner, rootState: WorkStateName): BackgroundReport[] {
|
||||
const children = [...(owner.nativeSubagentsByTaskId ?? [])].filter(
|
||||
([, child]) => child.announced,
|
||||
);
|
||||
const childByTask = new Map(children);
|
||||
const childByToolUse = new Map(
|
||||
children.flatMap(([, child]) =>
|
||||
child.parentToolUseId ? [[child.parentToolUseId, child] as const] : [],
|
||||
),
|
||||
);
|
||||
const lists = new Map<string, BackgroundItem[]>([
|
||||
[this.sessionId, []],
|
||||
...children.map(([, child]): [string, BackgroundItem[]] => [child.sessionId, []]),
|
||||
]);
|
||||
// A subagent's command or tool call runs on the child's stream.
|
||||
const ownerOf = (parentToolUseId: string | undefined) =>
|
||||
parentToolUseId ? childByToolUse.get(parentToolUseId)?.sessionId : this.sessionId;
|
||||
for (const [taskId, task] of this.level) {
|
||||
if (task.ambient) continue;
|
||||
const terminal = this.terminals.get(taskId);
|
||||
if (terminal) {
|
||||
const session = ownerOf(terminal.parentToolUseId);
|
||||
if (session)
|
||||
lists.get(session)!.push({ type: "terminal", terminalId: terminal.toolCallId });
|
||||
continue;
|
||||
}
|
||||
const toolCall = this.toolCalls.get(taskId);
|
||||
if (toolCall) {
|
||||
const session = ownerOf(toolCall.parentToolUseId);
|
||||
if (session)
|
||||
lists.get(session)!.push({ type: "tool_call", toolCallId: toolCall.toolCallId });
|
||||
continue;
|
||||
}
|
||||
// A command without a terminal yet is one that foreground work waits on.
|
||||
// A subagent is listed below, as its session.
|
||||
if (task.taskType === "local_bash" || task.taskType === "local_agent") continue;
|
||||
// A task joins the level before its `task_started`, and before the tool
|
||||
// call that starts it returns. Until both, the task is unknown or part
|
||||
// of that tool call.
|
||||
const live = owner.liveBackgroundTasks.get(taskId);
|
||||
if (!live || (live.parentToolUseId && !this.returned.has(live.parentToolUseId))) continue;
|
||||
const parent = task.parentTaskId ? childByTask.get(task.parentTaskId) : undefined;
|
||||
lists
|
||||
.get(parent?.sessionId ?? this.sessionId)!
|
||||
.push({ type: "_claude/task", taskId, description: task.description });
|
||||
}
|
||||
// A child is listed in its parent's list while it works in the background,
|
||||
// and while it is idle with background work of its own. Deeper children
|
||||
// first, so a child's list is whole before its parent's reads it.
|
||||
const sessions = new Map(children.map(([, child]) => [child.sessionId, child]));
|
||||
const depth = (child: NativeSubagent): number => {
|
||||
const parent = sessions.get(child.parentSessionId);
|
||||
return parent ? depth(parent) + 1 : 0;
|
||||
};
|
||||
const ordered = children.sort(([, a], [, b]) => depth(b) - depth(a));
|
||||
for (const [taskId, child] of ordered) {
|
||||
const task = this.level.get(taskId);
|
||||
const working =
|
||||
child.workState !== undefined &&
|
||||
child.workState !== "idle" &&
|
||||
task !== undefined &&
|
||||
!task.ambient;
|
||||
if (working || lists.get(child.sessionId)!.length > 0) {
|
||||
lists.get(child.parentSessionId)?.push({ type: "subagent", sessionId: child.sessionId });
|
||||
}
|
||||
}
|
||||
return [
|
||||
...ordered.map(([, child]) => ({
|
||||
sessionId: child.sessionId,
|
||||
items: lists.get(child.sessionId)!,
|
||||
state: workStateName(child.workState),
|
||||
parentSessionId: child.parentSessionId,
|
||||
})),
|
||||
{ sessionId: this.sessionId, items: lists.get(this.sessionId)!, state: rootState },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The current state of each background tool call in `toolCallIds`: the
|
||||
* call as `renderer` shows its tool use, with its status now (see
|
||||
* `ClaudeAcpAgent.publishBackgroundToolCalls`).
|
||||
*/
|
||||
toolCallStates(
|
||||
toolCallIds: string[],
|
||||
renderer: AcpToolCallRenderer,
|
||||
cwd: string,
|
||||
): AcpSessionNotification[] {
|
||||
return [...this.toolCalls.values()]
|
||||
.filter((toolCall) => toolCallIds.includes(toolCall.toolCallId))
|
||||
.map((toolCall) => {
|
||||
const update = renderer.toolCall({ id: toolCall.toolCallId, ...toolCall.toolUse }, { cwd });
|
||||
const status = toolCall.paused ? V2_PAUSED : "in_progress";
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
update: { ...update, status } as AcpSessionNotification["update"],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The task of the terminal or tool call `target` that the session
|
||||
* `sessionId` lists, or undefined when it lists no such item.
|
||||
*/
|
||||
listedTask(
|
||||
owner: BackgroundOwner,
|
||||
sessionId: string,
|
||||
target: BackgroundTarget,
|
||||
): string | undefined {
|
||||
if (!this.reported) return undefined;
|
||||
const listed = this.reports(owner, "idle")
|
||||
.find((report) => report.sessionId === sessionId)
|
||||
?.items.some((item) =>
|
||||
"terminalId" in target
|
||||
? item.type === "terminal" && item.terminalId === target.terminalId
|
||||
: item.type === "tool_call" && item.toolCallId === target.toolCallId,
|
||||
);
|
||||
if (!listed) return undefined;
|
||||
const id = "terminalId" in target ? target.terminalId : target.toolCallId;
|
||||
const entries = "terminalId" in target ? this.terminals : this.toolCalls;
|
||||
return [...entries].find(([, entry]) => entry.toolCallId === id)?.[0];
|
||||
}
|
||||
}
|
||||
|
||||
type Send = (notification: AcpSessionNotification) => Promise<void>;
|
||||
|
||||
/** A terminal or tool call that `terminal/cancel` or `tool_call/cancel` names. */
|
||||
export type BackgroundTarget = { terminalId: string } | { toolCallId: string };
|
||||
|
||||
/**
|
||||
* Stops the task of a terminal or tool call that the session `sessionId`
|
||||
* lists (v2 `terminal/cancel` and `tool_call/cancel`). `sessions` are the
|
||||
* root sessions: a subagent session's list is built with its root's. The
|
||||
* terminal exits, or the tool call gets its final status, when Claude Code
|
||||
* reports the task stopped.
|
||||
*/
|
||||
export async function cancelBackgroundWork(
|
||||
sessions: Array<BackgroundOwner & { background?: SessionBackgroundWork }>,
|
||||
sessionId: string,
|
||||
target: BackgroundTarget,
|
||||
): Promise<void> {
|
||||
const notCancellable = () =>
|
||||
"terminalId" in target
|
||||
? RequestError.invalidParams(
|
||||
{ sessionId, terminalId: target.terminalId },
|
||||
`Terminal ${target.terminalId} is not a command running in the background, so it cannot be cancelled.`,
|
||||
)
|
||||
: RequestError.invalidParams(
|
||||
{ sessionId, toolCallId: target.toolCallId },
|
||||
`Tool call ${target.toolCallId} is not running in the background, so it cannot be cancelled.`,
|
||||
);
|
||||
for (const session of sessions) {
|
||||
const taskId = session.background?.listedTask(session, sessionId, target);
|
||||
if (taskId === undefined) continue;
|
||||
try {
|
||||
await session.query.stopTask(taskId);
|
||||
} catch (error) {
|
||||
// The task ended meanwhile: it has ended on the client too, or is about to.
|
||||
if (session.background?.listedTask(session, sessionId, target) === undefined) {
|
||||
throw notCancellable();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw notCancellable();
|
||||
}
|
||||
|
||||
/** The update that gives the terminal of `terminal` the text `output`. */
|
||||
function terminalOutputUpdate(
|
||||
sessionId: string,
|
||||
terminal: BackgroundTerminal,
|
||||
output: BackgroundOutput,
|
||||
): AcpSessionNotification {
|
||||
const data = { terminal_id: terminal.toolCallId, data: output.text };
|
||||
return {
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: terminal.toolCallId,
|
||||
_meta: {
|
||||
...(terminal.parentToolUseId
|
||||
? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
|
||||
: {}),
|
||||
...(output.replaces ? { terminal_output: data } : { terminal_output_delta: data }),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** The update that exits the terminal of `terminal`, with no exit code. */
|
||||
function terminalExit(sessionId: string, terminal: BackgroundTerminal): AcpSessionNotification {
|
||||
return {
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: terminal.toolCallId,
|
||||
_meta: {
|
||||
...(terminal.parentToolUseId
|
||||
? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
|
||||
: {}),
|
||||
terminal_exit: { terminal_id: terminal.toolCallId, exit_code: null, signal: null },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function toolCallStatusUpdate(
|
||||
sessionId: string,
|
||||
toolCall: BackgroundToolCall,
|
||||
status: NonNullable<
|
||||
Extract<AcpSessionNotification["update"], { sessionUpdate: "tool_call_update" }>["status"]
|
||||
>,
|
||||
): AcpSessionNotification {
|
||||
return {
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: toolCall.toolCallId,
|
||||
status,
|
||||
...(toolCall.parentToolUseId
|
||||
? { _meta: { claudeCode: { parentToolUseId: toolCall.parentToolUseId } } }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the lists of each session that reports them, each time one
|
||||
* changes. The sink sends them to the client synchronously, so a list follows
|
||||
* the updates sent before it.
|
||||
*/
|
||||
export class BackgroundWorkReporter {
|
||||
/** The list last published for each session, as JSON, and the root session it is built with. */
|
||||
private readonly published = new Map<string, { json: string; root: string }>();
|
||||
|
||||
constructor(private readonly sink?: BackgroundSink) {}
|
||||
|
||||
/** Whether the lists are reported at all. */
|
||||
get enabled(): boolean {
|
||||
return this.sink !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes each list of the root session `rootSessionId` that changed. The
|
||||
* first call for a session publishes its list, also when it is empty.
|
||||
* `rootState` is the root session's state now.
|
||||
*/
|
||||
sync(
|
||||
rootSessionId: string,
|
||||
session: BackgroundOwner & { background?: SessionBackgroundWork },
|
||||
rootState: WorkStateName,
|
||||
): void {
|
||||
if (!this.sink || !session.background?.reported) return;
|
||||
for (const report of session.background.reports(session, rootState)) {
|
||||
const json = JSON.stringify(report.items);
|
||||
if (this.published.get(report.sessionId)?.json === json) continue;
|
||||
this.published.set(report.sessionId, { json, root: rootSessionId });
|
||||
this.sink.changed(report);
|
||||
}
|
||||
}
|
||||
|
||||
/** The root session `rootSessionId` ended, and its subagent sessions with it. */
|
||||
forget(rootSessionId: string): void {
|
||||
for (const [sessionId, { root }] of this.published) {
|
||||
if (root !== rootSessionId) continue;
|
||||
this.published.delete(sessionId);
|
||||
this.sink?.forget(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A child's work state, when it is one that v2 names. */
|
||||
function workStateName(state: string | undefined): WorkStateName | undefined {
|
||||
return state === "idle" ||
|
||||
state === "running" ||
|
||||
state === "requires_action" ||
|
||||
state === "unknown"
|
||||
? state
|
||||
: undefined;
|
||||
}
|
||||
12
src/diff.ts
12
src/diff.ts
@@ -89,7 +89,7 @@ export type PatchForm = "air" | "v2";
|
||||
/**
|
||||
* How a git patch names its file. `git` is git's own form: the leading slash
|
||||
* dropped and the `a/` and `b/` prefixes added. `absolute` is the form of an
|
||||
* ACP v2 `git_patch`, whose paths must be absolute: the path itself.
|
||||
* ACP v2 `git_patch`, whose paths must be absolute: the native path itself.
|
||||
*/
|
||||
type GitPatchPaths = "git" | "absolute";
|
||||
|
||||
@@ -438,11 +438,11 @@ export function gitPatchText(
|
||||
}
|
||||
|
||||
function gitPatchHeader(filePath: string, change: FileChange, paths: GitPatchPaths): string[] {
|
||||
// A Windows path gets forward slashes so the header names the same file on
|
||||
// every platform.
|
||||
const slashed = filePath.replaceAll("\\", "/");
|
||||
// git drops one leading slash of an absolute path.
|
||||
const name = paths === "git" ? slashed.replace(/^\/+/u, "") : slashed;
|
||||
// git's form uses forward slashes, so the header names the same file on
|
||||
// every platform, and drops one leading slash of an absolute path. An ACP v2
|
||||
// patch keeps the native path so that, once unquoted, it equals the `changes`
|
||||
// entry: `C:\work\App.ts` is written `"C:\\work\\App.ts"`.
|
||||
const name = paths === "git" ? filePath.replaceAll("\\", "/").replace(/^\/+/u, "") : filePath;
|
||||
const oldName = quoteGitPath(paths === "git" ? "a/" : "", name);
|
||||
const newName = quoteGitPath(paths === "git" ? "b/" : "", name);
|
||||
// git ends a ---/+++ name that contains a space with a tab, for GNU patch.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { CreateElicitationResponse } from "@agentclientprotocol/sdk/unstable";
|
||||
import type {
|
||||
CreateElicitationRequest,
|
||||
ElicitationAcceptAction,
|
||||
CreateElicitationResponse,
|
||||
ElicitationPropertySchema,
|
||||
ElicitationSchema,
|
||||
EnumOption,
|
||||
@@ -75,17 +74,22 @@ export function mcpElicitationToCreateRequest(
|
||||
}
|
||||
|
||||
/**
|
||||
* Content of an accepted elicitation response.
|
||||
* Content of an accepted elicitation response, or `{}` when the response is
|
||||
* no accept or its `content` is no JSON object.
|
||||
*
|
||||
* Uses the SDK's validating guard rather than an `action === "accept"` check:
|
||||
* the guard both narrows past the union's custom/future variant and validates
|
||||
* the payload, so a malformed accept (right tag, ill-typed content) yields
|
||||
* empty content — the same classification the SDK's wire validators apply.
|
||||
* The values are not checked. v1 allows a string, number, boolean, or string
|
||||
* array; v2 also allows any other JSON value, such as an object. An MCP
|
||||
* server checks the content against its own `requestedSchema`, so it gets the
|
||||
* content as the client sent it: before, an accept whose content held an
|
||||
* object reached the server as `{ action: "accept", content: {} }`. Callers
|
||||
* that read a field must check its type.
|
||||
*/
|
||||
function acceptedElicitationContent(
|
||||
response: CreateElicitationResponse,
|
||||
): NonNullable<ElicitationAcceptAction["content"]> {
|
||||
return CreateElicitationResponse.isAccept(response) ? (response.content ?? {}) : {};
|
||||
function acceptedElicitationContent(response: CreateElicitationResponse): Record<string, unknown> {
|
||||
if (response.action !== "accept") return {};
|
||||
const content: unknown = (response as { content?: unknown }).content;
|
||||
return content !== null && typeof content === "object" && !Array.isArray(content)
|
||||
? (content as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +101,11 @@ export function createElicitationResponseToElicitResult(
|
||||
): ElicitationResult {
|
||||
switch (response.action) {
|
||||
case "accept":
|
||||
return { action: "accept", content: acceptedElicitationContent(response) };
|
||||
// `ElicitResult` types the values as v1 does. The MCP server checks them.
|
||||
return {
|
||||
action: "accept",
|
||||
content: acceptedElicitationContent(response) as ElicitationResult["content"],
|
||||
};
|
||||
case "decline":
|
||||
return { action: "decline" };
|
||||
case "cancel":
|
||||
@@ -295,7 +303,7 @@ export function applyAskElicitationResponse(
|
||||
questions: AskUserQuestion[],
|
||||
options: { reportUnanswered?: boolean } = {},
|
||||
): AskUserQuestionOutcome {
|
||||
// Typed against the output schema: as of SDK 0.3.312 only it lists
|
||||
// Typed against the output schema: as of SDK 0.3.316 only it lists
|
||||
// `unanswered`, though the tool reads the same annotations from its input.
|
||||
const annotations: NonNullable<AskUserQuestionOutput["annotations"]> = {};
|
||||
const withAnnotations = (answers: AskUserQuestionOutput["answers"]) => ({
|
||||
@@ -325,13 +333,13 @@ export function applyAskElicitationResponse(
|
||||
const custom = content[questionCustomFieldKey(index)];
|
||||
const customText = typeof custom === "string" ? custom.trim() : "";
|
||||
|
||||
// A pick is a string, number, or boolean. Other values, such as an object
|
||||
// that a v2 client may send, are dropped instead of read as "[object Object]".
|
||||
const value = content[questionFieldKey(index)];
|
||||
const picks: string[] =
|
||||
value === undefined || value === null
|
||||
? []
|
||||
: Array.isArray(value)
|
||||
? value.filter((item) => item !== undefined && item !== null && item !== "").map(String)
|
||||
: [String(value)];
|
||||
const picks: string[] = (Array.isArray(value) ? value : [value])
|
||||
.filter(isElicitationScalar)
|
||||
.map(String)
|
||||
.filter((item) => item !== "");
|
||||
|
||||
// A multi-select is additive, and the form offers the selection and the
|
||||
// custom box as independent fields — a user who fills both means both, so
|
||||
@@ -374,6 +382,10 @@ export function applyAskElicitationResponse(
|
||||
return { action: "answered", updatedInput: withAnnotations(answers) };
|
||||
}
|
||||
|
||||
function isElicitationScalar(value: unknown): value is string | number | boolean {
|
||||
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce an arbitrary MCP `requestedSchema` into an ACP `ElicitationSchema`.
|
||||
* The two are structurally compatible JSON Schemas; we just guarantee the
|
||||
|
||||
@@ -176,9 +176,37 @@ function resolveFromFullHistory(
|
||||
return occurrenceMatches.length === 1 ? occurrenceMatches[0]?.uuid : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `error` is the Claude SDK's report that `sessionId` has no
|
||||
* transcript: `forkSession` and `importSessionToStore` throw
|
||||
* `Error("Session <id> not found")`, and both throw
|
||||
* `"Invalid sessionId: <id>"` for an id that is not a UUID.
|
||||
*/
|
||||
function isUnknownSessionError(error: unknown, sessionId: string): boolean {
|
||||
if (!(error instanceof Error)) return false;
|
||||
return (
|
||||
error.message === `Session ${sessionId} not found` ||
|
||||
error.message === `Invalid sessionId: ${sessionId}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function forkSession(
|
||||
params: ForkSessionRequest,
|
||||
dependencies: ForkSessionDependencies,
|
||||
): Promise<ForkSessionResponse> {
|
||||
try {
|
||||
return await forkTranscript(params, dependencies);
|
||||
} catch (error) {
|
||||
if (isUnknownSessionError(error, params.sessionId)) {
|
||||
throw RequestError.sessionNotFound(params.sessionId);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function forkTranscript(
|
||||
params: ForkSessionRequest,
|
||||
dependencies: ForkSessionDependencies,
|
||||
): Promise<ForkSessionResponse> {
|
||||
const timing = new SessionTiming(dependencies.logger, "fork", params.sessionId);
|
||||
const point = forkPoint(params._meta);
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
type SessionConfigOption,
|
||||
type SessionNotification,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import { realpath } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { AcpSessionNotification, SubagentForm, SubagentState } from "./acp-subagents.js";
|
||||
import { AIR_SUBAGENT_KEY, airExtensionMeta } from "./air-extension.js";
|
||||
import { MODEL_CONFIG_ID } from "./session-model.js";
|
||||
@@ -56,14 +58,23 @@ export type NativeSubagent = {
|
||||
/** The RFD form: the calls that delegated to the child, whose runs handed back a report. */
|
||||
handedBack?: Set<string>;
|
||||
announced?: boolean;
|
||||
/**
|
||||
* The RFD form: the `cwd` that the client holds for the child. Undefined or
|
||||
* null while the child inherits its parent's working directory.
|
||||
*/
|
||||
reportedCwd?: string | null;
|
||||
terminalState?: SubagentState;
|
||||
/** Connection-local single-flight state; never serialized on the wire. */
|
||||
announcePromise?: Promise<void>;
|
||||
/** Connection-local: serializes the child's `cwd` reports; never serialized on the wire. */
|
||||
cwdPublishing?: Promise<void>;
|
||||
/** Connection-local single-flight state; never serialized on the wire. */
|
||||
terminalPromise?: Promise<void>;
|
||||
};
|
||||
|
||||
export type NativeSubagentSession = {
|
||||
/** The working directory of the root session, which a child inherits unless it reports its own. */
|
||||
cwd?: string;
|
||||
nativeSubagentsByTaskId?: Map<string, NativeSubagent>;
|
||||
nativeSubagentTaskIdByToolUseId?: Map<string, string>;
|
||||
nativeSubagentParentByToolUseId?: Map<string, string>;
|
||||
@@ -133,6 +144,8 @@ export type HistoricalSubagent = {
|
||||
launchId: string;
|
||||
/** The agent that made that call. None means the root session. */
|
||||
parentAgentId?: string;
|
||||
/** Its working directory, when Claude Code placed it elsewhere, e.g. in a worktree. */
|
||||
cwd?: string;
|
||||
name?: string;
|
||||
agentType?: string;
|
||||
description?: string;
|
||||
@@ -172,6 +185,11 @@ export class NativeSubagentRuntime {
|
||||
private readonly identityByToolUse = new Map<string, SubagentIdentity>();
|
||||
private readonly controlByToolUse = new Map<string, AcpSessionNotification>();
|
||||
private readonly configBySession = new Map<string, ChildConfiguration>();
|
||||
/**
|
||||
* The RFD form: the working directory of each child, by session id, as its
|
||||
* hooks last reported it. A hook can come before the child is announced.
|
||||
*/
|
||||
private readonly cwdBySession = new Map<string, string>();
|
||||
private readonly childByParentToolUse = new Map<string, NativeSubagent>();
|
||||
/**
|
||||
* The child session of each tool call that went to a child session. A later
|
||||
@@ -274,6 +292,30 @@ export class NativeSubagentRuntime {
|
||||
return child?.announced ? child.workState : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: the `subagent_update` that tells a client that holds nothing
|
||||
* of the announced child `sessionId` of this live session what it is now:
|
||||
* its labels, its directory, its `cancel` capability, and its work state.
|
||||
* Undefined when no such child runs here.
|
||||
*/
|
||||
currentUpdate(sessionId: string): AcpSessionNotification | undefined {
|
||||
if (this.form !== "rfd") return undefined;
|
||||
const child = this.childOfSession(sessionId);
|
||||
if (!child?.announced || !child.workState) return undefined;
|
||||
return {
|
||||
sessionId: child.parentSessionId,
|
||||
update: {
|
||||
sessionUpdate: "subagent_update",
|
||||
sessionId: child.sessionId,
|
||||
...(child.title ? { title: child.title } : {}),
|
||||
...(child.description ? { description: child.description } : {}),
|
||||
...(child.reportedCwd ? { cwd: child.reportedCwd } : {}),
|
||||
capabilities: { cancel: {} },
|
||||
state: { state: child.workState },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the model of a child response, an exact model id, as the entry of
|
||||
* the root session's `model` option that matches it, else as the id (see
|
||||
@@ -400,6 +442,83 @@ export class NativeSubagentRuntime {
|
||||
await publishing;
|
||||
}
|
||||
|
||||
/**
|
||||
* The RFD form: takes the working directory of the child `agentId`, the
|
||||
* `cwd` of one of its hooks. A child that Claude Code runs in its own
|
||||
* worktree (Agent `isolation: "worktree"`) reports the worktree. The client
|
||||
* gets the `cwd` only when it differs from the parent's, in the
|
||||
* announcement or in a `subagent_update` right after it. Claude Code runs a
|
||||
* child's SubagentStart hook before its first model request, so the `cwd`
|
||||
* reaches the client before the child's first tool call.
|
||||
*/
|
||||
async reportCwd(agentId: string, cwd: string): Promise<void> {
|
||||
if (this.form !== "rfd" || agentId === this.rootSessionId || !path.isAbsolute(cwd)) return;
|
||||
const known = this.children.get(agentId) ?? this.childOfSession(agentId);
|
||||
const sessionId = known?.sessionId ?? agentId;
|
||||
if (!known && !this.cwdBySession.has(sessionId)) {
|
||||
const pending = [...this.cwdBySession.keys()].filter((id) => !this.childOfSession(id)).length;
|
||||
if (pending >= MAX_PENDING_PARENTS) {
|
||||
this.logger.log(
|
||||
`Session ${this.rootSessionId}: dropping cwd for unannounced subagent ${agentId}; pending buffer limit reached`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.cwdBySession.set(sessionId, cwd);
|
||||
if (!known) return;
|
||||
// An announcement in flight may have been built without this `cwd`.
|
||||
if (known.announcePromise) await known.announcePromise.catch(() => {});
|
||||
if (known.announced) await this.publishCwd(known);
|
||||
}
|
||||
|
||||
/**
|
||||
* The `cwd` of `child` that the client does not hold yet: its working
|
||||
* directory when that differs from its parent's, `null` when it is back in
|
||||
* the parent's after it reported another, else undefined.
|
||||
*/
|
||||
private async unreportedCwd(child: NativeSubagent): Promise<string | null | undefined> {
|
||||
const cwd = this.cwdBySession.get(child.sessionId);
|
||||
if (cwd === undefined) return undefined;
|
||||
const inherited = this.effectiveCwd(child.parentSessionId);
|
||||
if (inherited !== undefined && (await sameDirectory(cwd, inherited))) {
|
||||
return child.reportedCwd ? null : undefined;
|
||||
}
|
||||
return cwd === child.reportedCwd ? undefined : cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* The working directory of the session `sessionId` as the client holds it:
|
||||
* the root's from session setup, a child's reported `cwd`, else its
|
||||
* parent's.
|
||||
*/
|
||||
private effectiveCwd(sessionId: string): string | undefined {
|
||||
const seen = new Set<string>();
|
||||
let child = this.childOfSession(sessionId);
|
||||
while (child && !seen.has(child.sessionId)) {
|
||||
if (child.reportedCwd) return child.reportedCwd;
|
||||
seen.add(child.sessionId);
|
||||
child = this.childOfSession(child.parentSessionId);
|
||||
}
|
||||
return this.session.cwd;
|
||||
}
|
||||
|
||||
/** The RFD form: sends the `cwd` of the announced `child` that the client does not hold. */
|
||||
private async publishCwd(child: NativeSubagent): Promise<void> {
|
||||
if (this.form !== "rfd") return;
|
||||
// Concurrent hooks of one child report the same `cwd` once.
|
||||
const publishing = (child.cwdPublishing ?? Promise.resolve()).then(async () => {
|
||||
const cwd = await this.unreportedCwd(child);
|
||||
if (cwd === undefined) return;
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, cwd },
|
||||
});
|
||||
child.reportedCwd = cwd;
|
||||
});
|
||||
child.cwdPublishing = publishing.catch(() => {});
|
||||
await publishing;
|
||||
}
|
||||
|
||||
private async routeUpdate(
|
||||
notification: AcpSessionNotification,
|
||||
deliver: Publish,
|
||||
@@ -923,9 +1042,14 @@ export class NativeSubagentRuntime {
|
||||
this.childByParentToolUse.set(entry.launchId, child);
|
||||
}
|
||||
if (child.announced) continue;
|
||||
// A hook of the resumed agent may have reported a newer `cwd`.
|
||||
if (entry.cwd && !this.cwdBySession.has(child.sessionId)) {
|
||||
this.cwdBySession.set(child.sessionId, entry.cwd);
|
||||
}
|
||||
if (entry.agentId === agentId) {
|
||||
await this.announce(child);
|
||||
} else {
|
||||
const cwd = await this.unreportedCwd(child);
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: {
|
||||
@@ -933,8 +1057,10 @@ export class NativeSubagentRuntime {
|
||||
sessionId: child.sessionId,
|
||||
...(child.title ? { title: child.title } : {}),
|
||||
...(child.description ? { description: child.description } : {}),
|
||||
...(cwd ? { cwd } : {}),
|
||||
},
|
||||
});
|
||||
if (cwd) child.reportedCwd = cwd;
|
||||
child.announced = true;
|
||||
}
|
||||
for (const pending of this.takePending(entry.launchId)) await deliver(pending);
|
||||
@@ -1103,6 +1229,7 @@ export class NativeSubagentRuntime {
|
||||
private withdraw(taskId: string, child: NativeSubagent): void {
|
||||
this.children.delete(taskId);
|
||||
this.configBySession.delete(child.sessionId);
|
||||
this.cwdBySession.delete(child.sessionId);
|
||||
if (child.parentToolUseId) {
|
||||
this.takePending(child.parentToolUseId);
|
||||
this.cleanupControl(child.parentToolUseId);
|
||||
@@ -1125,12 +1252,16 @@ export class NativeSubagentRuntime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces `child` in the client's form, then its configuration. A runtime
|
||||
* without a form never does.
|
||||
* Announces `child` in the client's form, with its `cwd` in the RFD form,
|
||||
* then its configuration. A runtime without a form never does.
|
||||
*/
|
||||
private async announce(child: NativeSubagent): Promise<void> {
|
||||
if (!this.form) return;
|
||||
await announceNativeSubagent(child, this.publish, this.form);
|
||||
const cwd =
|
||||
this.form === "rfd" && !child.announced ? await this.unreportedCwd(child) : undefined;
|
||||
await announceNativeSubagent(child, this.publish, this.form, cwd ?? undefined);
|
||||
// A hook that reported a `cwd` while the announcement was being built.
|
||||
await this.publishCwd(child);
|
||||
await this.publishConfiguration(child);
|
||||
}
|
||||
|
||||
@@ -1396,6 +1527,7 @@ export class NativeSubagentRuntime {
|
||||
this.identityByToolUse.clear();
|
||||
this.controlByToolUse.clear();
|
||||
this.configBySession.clear();
|
||||
this.cwdBySession.clear();
|
||||
this.childByParentToolUse.clear();
|
||||
this.taskFinishPromises.clear();
|
||||
this.senderByToolUse.clear();
|
||||
@@ -1601,6 +1733,7 @@ export async function announceNativeSubagent(
|
||||
child: NativeSubagent,
|
||||
publish: Publish,
|
||||
form: SubagentForm,
|
||||
cwd?: string,
|
||||
): Promise<void> {
|
||||
if (child.announced) return;
|
||||
if (child.announcePromise) return child.announcePromise;
|
||||
@@ -1614,10 +1747,12 @@ export async function announceNativeSubagent(
|
||||
sessionId: child.sessionId,
|
||||
...(child.title ? { title: child.title } : {}),
|
||||
...(child.description ? { description: child.description } : {}),
|
||||
...(cwd ? { cwd } : {}),
|
||||
capabilities: { cancel: {} },
|
||||
state: { state: "running" },
|
||||
},
|
||||
});
|
||||
if (cwd) child.reportedCwd = cwd;
|
||||
child.announced = true;
|
||||
// The immediate parent made the Agent/Task call that launched the child.
|
||||
if (child.prompt) {
|
||||
@@ -2155,3 +2290,15 @@ function promptField(prompt: string | undefined): { prompt?: string } {
|
||||
function nonBlankString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `left` and `right` are the same directory, also when one is
|
||||
* spelled through a symlink, e.g. `/tmp/project` and `/private/tmp/project`
|
||||
* on macOS. A path that does not exist is compared as written.
|
||||
*/
|
||||
export async function sameDirectory(left: string, right: string): Promise<boolean> {
|
||||
if (path.resolve(left) === path.resolve(right)) return true;
|
||||
const real = (dir: string) => realpath(dir).catch(() => path.resolve(dir));
|
||||
const [realLeft, realRight] = await Promise.all([real(left), real(right)]);
|
||||
return realLeft === realRight;
|
||||
}
|
||||
|
||||
@@ -387,16 +387,6 @@ function isSdkNotFound(error: unknown): boolean {
|
||||
return error instanceof Error && /^Session \S+ not found in /.test(error.message);
|
||||
}
|
||||
|
||||
/** The error the SDK `deleteSession` gives for a session id it rejects or
|
||||
* does not find, which a client without `sessionIndex` got before. */
|
||||
function sdkDeleteError(sessionId: string): Error {
|
||||
return new Error(
|
||||
isSessionId(sessionId)
|
||||
? `Session ${sessionId} not found in any project directory`
|
||||
: `Invalid sessionId: ${sessionId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `session/delete` of a client that is neither AIR nor a `sessionIndex` client:
|
||||
* the SDK delete of the session's transcript. A session without one succeeds,
|
||||
@@ -422,16 +412,18 @@ export async function deleteSessionIfStored(
|
||||
/**
|
||||
* `session/delete` of an AIR client without `sessionIndex`, which uses delete
|
||||
* to mark a session done: archives it instead, so the transcript survives. A
|
||||
* session without a transcript fails as the SDK delete did.
|
||||
* session without a transcript has nothing to archive and succeeds, as ACP
|
||||
* asks of a delete of a session that never existed (see
|
||||
* {@link deleteSessionIfStored}).
|
||||
*/
|
||||
export async function archiveInsteadOfDelete(
|
||||
sessionId: string,
|
||||
service: SessionIndexService,
|
||||
): Promise<void> {
|
||||
if (!isSessionId(sessionId)) throw sdkDeleteError(sessionId);
|
||||
if (!isSessionId(sessionId)) return;
|
||||
// The SDK delete skips empty transcripts, so they do not count.
|
||||
const found = await service.index.findTranscripts(sessionId);
|
||||
if (found.length === 0) throw sdkDeleteError(sessionId);
|
||||
if (found.length === 0) return;
|
||||
await service.retitle(sessionId, archiveTo(true), { sidecar: "existing" });
|
||||
}
|
||||
|
||||
|
||||
@@ -79,8 +79,9 @@ type InitializeSessionModeParams = {
|
||||
* The permission mode a new Claude query starts in.
|
||||
*
|
||||
* An explicit mode wins. A resume or a load continues in the mode of the last
|
||||
* user record of the transcript. If that mode is unknown or not available now,
|
||||
* for example bypass, the session starts in `defaultMode` of the settings.
|
||||
* user record of the transcript. If that mode is unknown or absent from the
|
||||
* session's mode catalog, use an available `defaultMode` from settings, or
|
||||
* `default` when that setting is unavailable too.
|
||||
*/
|
||||
export function resolveInitialPermissionMode(
|
||||
sources: { explicit?: PermissionMode; resumed?: string; defaultMode?: unknown },
|
||||
@@ -97,13 +98,23 @@ export function resolveInitialPermissionMode(
|
||||
{ error: () => (rejected = true) },
|
||||
allowBypass,
|
||||
);
|
||||
if (!rejected) return mode;
|
||||
if (!rejected && buildAvailableModes(allowBypass).some((available) => available.id === mode)) {
|
||||
return mode;
|
||||
}
|
||||
logger.error(
|
||||
`Ignoring permission mode "${sources.resumed}" of the resumed transcript: ` +
|
||||
`not available in this session.`,
|
||||
);
|
||||
}
|
||||
return resolvePermissionMode(sources.defaultMode, logger, allowBypass);
|
||||
const configuredMode = resolvePermissionMode(sources.defaultMode, logger, allowBypass);
|
||||
// A rejected transcript mode falls back only to a mode this session can offer.
|
||||
if (
|
||||
sources.resumed !== undefined &&
|
||||
!buildAvailableModes(allowBypass).some((available) => available.id === configuredMode)
|
||||
) {
|
||||
return "default";
|
||||
}
|
||||
return configuredMode;
|
||||
}
|
||||
|
||||
/** Owns session-mode policy and the ACP/SDK synchronization it requires. */
|
||||
@@ -120,7 +131,7 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
modes: SessionModeState;
|
||||
autoModeFallbackWarningPending: boolean;
|
||||
}> {
|
||||
const availableModes = this.buildAvailableModes(allowBypass);
|
||||
const availableModes = buildAvailableModes(allowBypass, this.options.airClient?.() ?? false);
|
||||
let effectiveMode = requestedMode;
|
||||
let autoModeFallbackWarningPending = false;
|
||||
|
||||
@@ -350,50 +361,6 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
}
|
||||
}
|
||||
|
||||
private buildAvailableModes(allowBypass: boolean): SessionModeState["availableModes"] {
|
||||
// Only AIR gets the mode kind.
|
||||
const air = this.options.airClient?.() ?? false;
|
||||
const kind = (value: string) => {
|
||||
const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
|
||||
return meta ? { _meta: meta } : {};
|
||||
};
|
||||
const modes: SessionModeState["availableModes"] = [
|
||||
{
|
||||
id: "default",
|
||||
name: "Manual",
|
||||
description: "Always ask before making changes",
|
||||
...kind("standard"),
|
||||
},
|
||||
{
|
||||
id: "acceptEdits",
|
||||
name: "Accept edits",
|
||||
description: "Automatically accept all file edits",
|
||||
...kind("standard"),
|
||||
},
|
||||
{
|
||||
id: "plan",
|
||||
name: "Plan",
|
||||
description: "Create a plan before making changes",
|
||||
...kind("plan"),
|
||||
},
|
||||
{
|
||||
id: "auto",
|
||||
name: "Auto",
|
||||
description: "Claude handles permission decisions",
|
||||
...kind("auto_review"),
|
||||
},
|
||||
];
|
||||
if (allowBypass) {
|
||||
modes.push({
|
||||
id: "bypassPermissions",
|
||||
name: "Bypass permissions",
|
||||
description: "Accepts all permissions",
|
||||
...kind("full_access"),
|
||||
});
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
private async trySyncMode(
|
||||
query: Pick<Query, "setPermissionMode">,
|
||||
mode: PermissionMode,
|
||||
@@ -406,3 +373,49 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildAvailableModes(
|
||||
allowBypass: boolean,
|
||||
air = false,
|
||||
): SessionModeState["availableModes"] {
|
||||
// Only AIR gets the mode kind.
|
||||
const kind = (value: string) => {
|
||||
const meta = airOnlyMeta(air, AIR_KIND_KEY, value);
|
||||
return meta ? { _meta: meta } : {};
|
||||
};
|
||||
const modes: SessionModeState["availableModes"] = [
|
||||
{
|
||||
id: "default",
|
||||
name: "Manual",
|
||||
description: "Always ask before making changes",
|
||||
...kind("standard"),
|
||||
},
|
||||
{
|
||||
id: "acceptEdits",
|
||||
name: "Accept edits",
|
||||
description: "Automatically accept all file edits",
|
||||
...kind("standard"),
|
||||
},
|
||||
{
|
||||
id: "plan",
|
||||
name: "Plan",
|
||||
description: "Create a plan before making changes",
|
||||
...kind("plan"),
|
||||
},
|
||||
{
|
||||
id: "auto",
|
||||
name: "Auto",
|
||||
description: "Claude handles permission decisions",
|
||||
...kind("auto_review"),
|
||||
},
|
||||
];
|
||||
if (allowBypass) {
|
||||
modes.push({
|
||||
id: "bypassPermissions",
|
||||
name: "Bypass permissions",
|
||||
description: "Accepts all permissions",
|
||||
...kind("full_access"),
|
||||
});
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export type UltracodeSettingsReport = {
|
||||
};
|
||||
|
||||
// `Query.getSettings()` exists at runtime (sdk.mjs) but is missing from the
|
||||
// SDK's type declarations as of 0.3.312.
|
||||
// SDK's type declarations as of 0.3.316.
|
||||
type GetSettingsQuery = Query & {
|
||||
getSettings?: () => Promise<{
|
||||
applied?: { ultracodeAvailable?: unknown; ultracodeRequested?: unknown };
|
||||
|
||||
42
src/startup-failure.ts
Normal file
42
src/startup-failure.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { SDKStartupFailureReason } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
/** Makes the CLI write an `error_during_execution` result with a
|
||||
* `startup_failure_reason` before it exits on a known startup failure. Without
|
||||
* it, some failures (e.g. `proxy_invalid`) end with stderr and an exit code
|
||||
* alone. The SDK then rejects `initializationResult()` with an error that
|
||||
* carries the reason as `startupFailureReason`. */
|
||||
export const STARTUP_FAILURE_RESULTS_ENV = { CLAUDE_CODE_STARTUP_FAILURE_RESULTS: "1" };
|
||||
|
||||
/** The SDK's prefix on the message of an error built from an error result. */
|
||||
const ERROR_RESULT_PREFIX = "Claude Code returned an error result: ";
|
||||
|
||||
/** Reasons that signing in again, or with another account, can fix. */
|
||||
const AUTH_REQUIRED_REASONS: ReadonlySet<SDKStartupFailureReason> = new Set([
|
||||
"gateway_signin_required",
|
||||
"org_pin_mismatch",
|
||||
"org_config_refused",
|
||||
]);
|
||||
|
||||
/** The ACP error for a CLI that refused to start, or `undefined` when `error`
|
||||
* carries no `startupFailureReason` (older CLIs, failures without a known
|
||||
* cause). The message is the CLI's own, which says what to fix, e.g.
|
||||
* `Invalid proxy URL in HTTPS_PROXY: … Fix or unset HTTPS_PROXY and restart
|
||||
* Claude Code.`; `data.startupFailureReason` carries the reason. */
|
||||
export function startupFailureError(error: unknown): RequestError | undefined {
|
||||
if (!(error instanceof Error)) return undefined;
|
||||
const reason = (error as { startupFailureReason?: unknown }).startupFailureReason;
|
||||
if (typeof reason !== "string" || reason === "") return undefined;
|
||||
|
||||
const message = error.message.startsWith(ERROR_RESULT_PREFIX)
|
||||
? error.message.slice(ERROR_RESULT_PREFIX.length)
|
||||
: error.message;
|
||||
const data = { startupFailureReason: reason };
|
||||
if (AUTH_REQUIRED_REASONS.has(reason as SDKStartupFailureReason)) {
|
||||
return RequestError.authRequired(data, message);
|
||||
}
|
||||
if (reason === "cwd_unavailable") {
|
||||
return RequestError.invalidParams(data, message);
|
||||
}
|
||||
return RequestError.internalError(data, message);
|
||||
}
|
||||
@@ -24,6 +24,11 @@ export interface SubagentRecord {
|
||||
background: boolean;
|
||||
/** Whether the user stopped it. */
|
||||
stoppedByUser: boolean;
|
||||
/**
|
||||
* Its working directory, when Claude Code placed it outside its parent's:
|
||||
* in a worktree of its own or of an ancestor, or in a `cwd` of its own.
|
||||
*/
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
/** Where the subagent transcripts of a session are. */
|
||||
@@ -43,7 +48,9 @@ export interface SubagentHistory {
|
||||
* Claude keeps the history of a subagent in its own transcript,
|
||||
* `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
|
||||
* The `agent-<id>.meta.json` file beside it names the launching tool use.
|
||||
* The ids are empty for a session without a local transcript.
|
||||
* For a child that Claude Code placed elsewhere, it has `worktreePath`,
|
||||
* `inheritedWorktreePath` or `cwd`; then the child's working directory is the
|
||||
* `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
|
||||
*/
|
||||
export async function subagentHistory(sessionId: string): Promise<SubagentHistory> {
|
||||
const agents = new Map<string, SubagentRecord>();
|
||||
@@ -67,6 +74,12 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
|
||||
> | null;
|
||||
if (typeof meta?.toolUseId !== "string") return;
|
||||
const text = (value: unknown) => (typeof value === "string" ? value : undefined);
|
||||
const placement = [meta.cwd, meta.worktreePath, meta.inheritedWorktreePath]
|
||||
.map(text)
|
||||
.find((value) => value && path.isAbsolute(value));
|
||||
const cwd = placement
|
||||
? ((await recordedCwd(path.join(directory, `agent-${match[1]!}.jsonl`))) ?? placement)
|
||||
: undefined;
|
||||
agents.set(meta.toolUseId, {
|
||||
agentId: match[1]!,
|
||||
name: text(meta.name),
|
||||
@@ -74,6 +87,7 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
|
||||
description: text(meta.description),
|
||||
background: meta.requestShape === "background",
|
||||
stoppedByUser: meta.stoppedByUser === true,
|
||||
...(cwd ? { cwd } : {}),
|
||||
});
|
||||
} catch {
|
||||
// A damaged meta file leaves its subagent without history.
|
||||
@@ -87,6 +101,31 @@ export async function subagentHistory(sessionId: string): Promise<SubagentHistor
|
||||
return dir ? { agents, dir } : { agents };
|
||||
}
|
||||
|
||||
/**
|
||||
* The `cwd` of the first record of the transcript `file` that has one, or
|
||||
* undefined when the file cannot be read. Claude Code writes the working
|
||||
* directory of the session or subagent on each record.
|
||||
*/
|
||||
async function recordedCwd(file: string): Promise<string | undefined> {
|
||||
const stream = createReadStream(file, { encoding: "utf8" });
|
||||
try {
|
||||
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
|
||||
if (!line.includes('"cwd"')) continue;
|
||||
try {
|
||||
const cwd = (JSON.parse(line) as { cwd?: unknown } | null)?.cwd;
|
||||
if (typeof cwd === "string" && path.isAbsolute(cwd)) return cwd;
|
||||
} catch {
|
||||
// A damaged record; a later one can have the `cwd`.
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// An unreadable transcript; the caller falls back to the metadata's path.
|
||||
} finally {
|
||||
stream.destroy();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** The folder of the subagent transcripts, next to the session transcript `transcript`. */
|
||||
function subagentsDirectory(transcript: string, sessionId: string): string {
|
||||
return path.join(path.dirname(transcript), sessionId, "subagents");
|
||||
@@ -162,6 +201,7 @@ export async function subagentLineage(
|
||||
agentId: current,
|
||||
launchId: launch.launchId,
|
||||
...(launcher !== null ? { parentAgentId: launcher } : {}),
|
||||
...(launch.record.cwd ? { cwd: launch.record.cwd } : {}),
|
||||
...(launch.record.name ? { name: launch.record.name } : {}),
|
||||
...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
|
||||
...(launch.record.description ? { description: launch.record.description } : {}),
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
*/
|
||||
import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { statSync } from "node:fs";
|
||||
import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
|
||||
import {
|
||||
childModelOption,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
responseModelEntry,
|
||||
rfdEndState,
|
||||
rfdLabels,
|
||||
sameDirectory,
|
||||
type SubagentModels,
|
||||
type WorkState,
|
||||
} from "./native-subagents.js";
|
||||
@@ -71,6 +73,8 @@ export type ReplayChild = {
|
||||
/** The state of the child's last persisted task notification. */
|
||||
notified?: SubagentState;
|
||||
announced: boolean;
|
||||
/** The `cwd` that the replay reported for it; undefined while it inherits its parent's. */
|
||||
cwd?: string;
|
||||
promptSent: boolean;
|
||||
/** Whether it handed a report to its parent with a SubagentHandback call. */
|
||||
handedBack?: boolean;
|
||||
@@ -118,6 +122,8 @@ export class RfdSubagentReplay {
|
||||
* {@link currentStates}, after the load response.
|
||||
*/
|
||||
private readonly mapsDuringReplay = true,
|
||||
/** The working directory of the root session, which a child inherits unless it reports its own. */
|
||||
private readonly rootCwd?: string,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -166,8 +172,9 @@ export class RfdSubagentReplay {
|
||||
|
||||
/**
|
||||
* Announces `child` on its parent, as live: running, with the parent's
|
||||
* labels. Then it sends `firstModel`, the model of the child's first
|
||||
* response, when the transcript has one.
|
||||
* labels, and with its recorded `cwd` when that differs from its parent's.
|
||||
* Then it sends `firstModel`, the model of the child's first response, when
|
||||
* the transcript has one.
|
||||
*/
|
||||
async announce(child: ReplayChild, firstModel?: string): Promise<void> {
|
||||
if (child.announced) return;
|
||||
@@ -176,6 +183,12 @@ export class RfdSubagentReplay {
|
||||
child.record.agentType ?? child.input.subagent_type,
|
||||
child.record.description ?? child.input.description,
|
||||
);
|
||||
const inherited = this.effectiveCwd(child.parentSessionId);
|
||||
const cwd =
|
||||
child.record.cwd &&
|
||||
(inherited === undefined || !(await sameDirectory(child.record.cwd, inherited)))
|
||||
? child.record.cwd
|
||||
: undefined;
|
||||
await this.publish({
|
||||
sessionId: child.parentSessionId,
|
||||
update: {
|
||||
@@ -183,13 +196,30 @@ export class RfdSubagentReplay {
|
||||
sessionId: child.sessionId,
|
||||
...(labels.title ? { title: labels.title } : {}),
|
||||
...(labels.description ? { description: labels.description } : {}),
|
||||
...(cwd ? { cwd } : {}),
|
||||
state: { state: "running" },
|
||||
},
|
||||
});
|
||||
child.cwd = cwd;
|
||||
child.announced = true;
|
||||
await this.responseModel(child, firstModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* The working directory of the session `sessionId` as the client holds it:
|
||||
* a child's reported `cwd`, else its parent's, up to the root's.
|
||||
*/
|
||||
private effectiveCwd(sessionId: string): string | undefined {
|
||||
const seen = new Set<string>();
|
||||
let child = this.byAgentId.get(sessionId);
|
||||
while (child && !seen.has(child.sessionId)) {
|
||||
if (child.cwd) return child.cwd;
|
||||
seen.add(child.sessionId);
|
||||
child = this.byAgentId.get(child.parentSessionId);
|
||||
}
|
||||
return this.rootCwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the model of a replayed response of the child `owner`. A child
|
||||
* whose model changes gets its `model` option again.
|
||||
@@ -370,6 +400,10 @@ export class RfdSubagentReplay {
|
||||
* when a live session is loaded again. Such a child reports that state and
|
||||
* can be cancelled, as live. Any other child cannot be running, because the
|
||||
* process that ran it is gone, so its replayed end is its current state.
|
||||
*
|
||||
* A replayed `cwd` whose directory is gone, such as a worktree that Claude
|
||||
* Code removed when the child finished, is cleared with `cwd: null`, as the
|
||||
* RFD requires after a reconnection.
|
||||
*/
|
||||
currentStates(
|
||||
live: (sessionId: string) => WorkState["state"] | undefined,
|
||||
@@ -395,6 +429,7 @@ export class RfdSubagentReplay {
|
||||
sessionUpdate: "subagent_update",
|
||||
sessionId: child.sessionId,
|
||||
...(liveState !== undefined ? { capabilities: { cancel: {} } } : {}),
|
||||
...(child.cwd && !isDirectory(child.cwd) ? { cwd: null } : {}),
|
||||
state,
|
||||
},
|
||||
},
|
||||
@@ -574,6 +609,15 @@ function textOf(content: unknown): string {
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/** Whether `dir` is an existing directory. */
|
||||
function isDirectory(dir: string): boolean {
|
||||
try {
|
||||
return statSync(dir).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** A message that a SendMessage call delivered to the child. */
|
||||
function isDelivery(message: SessionMessage): boolean {
|
||||
return (message as { origin?: { kind?: unknown } | null }).origin?.kind === "coordinator";
|
||||
|
||||
@@ -464,6 +464,16 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
expect(response.modes.currentModeId).toBe("plan");
|
||||
});
|
||||
|
||||
it("uses the available configured mode when the restored mode is not in the catalog", async () => {
|
||||
const { permissionMode, response } = await resume([user("dontAsk")]);
|
||||
|
||||
expect(permissionMode).toBe("plan");
|
||||
expect(response.modes.currentModeId).toBe("plan");
|
||||
expect(response.configOptions.find((option: any) => option.id === "mode").currentValue).toBe(
|
||||
"plan",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses permissions.defaultMode when bypass is not available now", async () => {
|
||||
const { permissionMode, response } = await resume([user("bypassPermissions")], {
|
||||
claudeCode: { options: { allowDangerouslySkipPermissions: false } },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach, vi } from "vitest";
|
||||
import { spawn, spawnSync } from "child_process";
|
||||
import {
|
||||
type AnyMessage,
|
||||
AvailableCommand,
|
||||
ClientCapabilities,
|
||||
client as acpClient,
|
||||
@@ -55,9 +56,11 @@ import {
|
||||
type StreamedToolInputCache,
|
||||
} from "../acp-agent.js";
|
||||
import { SessionTitles } from "../session-titles.js";
|
||||
import { SessionBackgroundWork } from "../background-work.js";
|
||||
import { formatUsageResponse, isUsageCommandText, parseUsageResponse } from "../usage-markdown.js";
|
||||
import { Pushable } from "../utils.js";
|
||||
import { initializeClient, makeMockQuery } from "./helpers.js";
|
||||
import { heldRequest, initializeClient, makeMockQuery } from "./helpers.js";
|
||||
import { acpProtocolRouter } from "../serve.js";
|
||||
import * as agentSdk from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
deleteSession,
|
||||
@@ -3548,11 +3551,20 @@ describe("session/list", () => {
|
||||
expect(second.sessions.map((session) => session.sessionId)).toEqual(["session-1000"]);
|
||||
expect(second.nextCursor).toBeUndefined();
|
||||
expect(vi.mocked(listSessions).mock.calls).toEqual([
|
||||
[{ dir: "/workspace", limit: 1001, offset: 0 }],
|
||||
[{ dir: "/workspace", limit: 1001, offset: 1000 }],
|
||||
[{ dir: "/workspace", includeWorktrees: false, limit: 1001, offset: 0 }],
|
||||
[{ dir: "/workspace", includeWorktrees: false, limit: 1001, offset: 1000 }],
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects a relative cwd", async () => {
|
||||
vi.mocked(listSessions).mockClear();
|
||||
await expect(agent().listSessions({ cwd: "workspace" })).rejects.toMatchObject({
|
||||
code: -32602,
|
||||
data: { cwd: "workspace" },
|
||||
});
|
||||
expect(listSessions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a cursor that it did not issue", async () => {
|
||||
await expect(agent().listSessions({ cursor: "page-2" })).rejects.toThrow(
|
||||
"Unknown session/list cursor",
|
||||
@@ -4006,7 +4018,8 @@ describe("subagent transcript replay", () => {
|
||||
return [
|
||||
...response,
|
||||
`${on} subagent_update ${u.sessionId} ${JSON.stringify(u.state)}` +
|
||||
(u.capabilities ? ` capabilities=${JSON.stringify(u.capabilities)}` : ""),
|
||||
(u.capabilities ? ` capabilities=${JSON.stringify(u.capabilities)}` : "") +
|
||||
("cwd" in u ? ` cwd=${JSON.stringify(u.cwd)}` : ""),
|
||||
];
|
||||
case "session_message": {
|
||||
const references = [
|
||||
@@ -4046,6 +4059,51 @@ describe("subagent transcript replay", () => {
|
||||
}
|
||||
}
|
||||
|
||||
it("announces a worktree child with its recorded cwd, and clears it when the worktree is gone", async () => {
|
||||
const worktree = "/tmp/proj/.claude/worktrees/agent-wt1";
|
||||
const trace = await replayRfd(
|
||||
[
|
||||
user("u1", "Go"),
|
||||
assistant("a1", [
|
||||
toolUse("toolu_wt", "Agent", { description: "Isolated", prompt: "Work apart" }),
|
||||
toolUse("toolu_plain", "Agent", { description: "Shared", prompt: "Work here" }),
|
||||
]),
|
||||
user("u2", [toolResult("toolu_wt", "Done."), toolResult("toolu_plain", "Done.")]),
|
||||
],
|
||||
{
|
||||
wt1: {
|
||||
meta: {
|
||||
agentType: "general-purpose",
|
||||
toolUseId: "toolu_wt",
|
||||
worktreePath: worktree,
|
||||
spawnedWithWorktree: true,
|
||||
},
|
||||
records: [
|
||||
user("w1", "Work apart", { cwd: worktree }),
|
||||
assistant("w2", [{ type: "text", text: "Done." }], { cwd: worktree }),
|
||||
],
|
||||
},
|
||||
plain1: {
|
||||
meta: { agentType: "general-purpose", toolUseId: "toolu_plain" },
|
||||
records: [user("p1", "Work here"), assistant("p2", [{ type: "text", text: "Done." }])],
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ live: () => undefined },
|
||||
{ load: true },
|
||||
);
|
||||
|
||||
expect(trace.filter((line) => line.includes(" subagent_update "))).toEqual([
|
||||
`root subagent_update wt1 {"state":"running"} cwd="${worktree}"`,
|
||||
'root subagent_update plain1 {"state":"running"}',
|
||||
'root subagent_update plain1 {"state":"idle","stopReason":"end_turn"}',
|
||||
'root subagent_update wt1 {"state":"idle","stopReason":"end_turn"}',
|
||||
// After the response: the worktree no longer exists.
|
||||
'root subagent_update wt1 {"state":"idle","stopReason":"end_turn"} cwd=null',
|
||||
'root subagent_update plain1 {"state":"idle","stopReason":"end_turn"}',
|
||||
]);
|
||||
});
|
||||
|
||||
it("replays a fork child without the launching message and boilerplate it inherited", async () => {
|
||||
// A `subagent_type: "fork"` transcript starts with a copy of the
|
||||
// launching message (same API message id) and a directive message with
|
||||
@@ -4716,6 +4774,58 @@ describe("escape markdown", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("request validation", () => {
|
||||
function agent() {
|
||||
return new ClaudeAcpAgent({ sessionUpdate: async () => {} } as unknown as AcpClient, {
|
||||
log: () => {},
|
||||
error: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
it("rejects a session/new with a relative additional directory", async () => {
|
||||
await expect(
|
||||
agent().newSession({ cwd: os.tmpdir(), mcpServers: [], additionalDirectories: ["extra"] }),
|
||||
).rejects.toMatchObject({ code: -32602, data: { additionalDirectories: ["extra"] } });
|
||||
});
|
||||
|
||||
it("rejects authenticate with an unknown method id", async () => {
|
||||
await expect(agent().authenticate({ methodId: "unknown-method" })).rejects.toMatchObject({
|
||||
code: -32602,
|
||||
data: { methodId: "unknown-method" },
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects a v1 prompt with audio before it looks up the session", async () => {
|
||||
// An unknown session would fail with -32002 if the check ran later.
|
||||
await expect(
|
||||
agent().prompt({
|
||||
sessionId: "unknown-session",
|
||||
prompt: [
|
||||
{ type: "text", text: "listen" },
|
||||
{ type: "audio", data: "AAAA", mimeType: "audio/wav" },
|
||||
],
|
||||
}),
|
||||
).rejects.toMatchObject({ code: -32602, data: { type: "audio" } });
|
||||
});
|
||||
|
||||
it("drops slash commands whose name has whitespace, and repeats of a name", async () => {
|
||||
const a = agent();
|
||||
const supportedCommands = async () => [
|
||||
{ name: "review", description: "first" },
|
||||
{ name: "review", description: "second" },
|
||||
{ name: "my command", description: "spaced" },
|
||||
{ name: "compact", description: "Compact the conversation" },
|
||||
];
|
||||
a.sessions["test-session"] = mockSessionState({ query: { supportedCommands } }, a) as any;
|
||||
const commands = await a.availableCommands("test-session");
|
||||
expect(commands.map((command) => [command.name, command.description])).toEqual([
|
||||
["review", "first"],
|
||||
["compact", "Compact the conversation"],
|
||||
["mcp", expect.any(String)],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("prompt conversion", () => {
|
||||
it("should not change built-in slash commands", () => {
|
||||
const message = promptToClaude({
|
||||
@@ -4753,6 +4863,22 @@ describe("prompt conversion", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("rewrites an MCP slash command with multi-line input, in the first text block only", () => {
|
||||
const message = promptToClaude({
|
||||
sessionId: "test",
|
||||
prompt: [
|
||||
{ type: "resource_link", uri: "file:///work/a.ts", name: "a.ts" },
|
||||
{ type: "text", text: "/mcp:server:name first line\nsecond line" },
|
||||
{ type: "text", text: "/mcp:server:other args" },
|
||||
],
|
||||
});
|
||||
expect(message.message.content).toEqual([
|
||||
{ type: "text", text: "[@a.ts](file:///work/a.ts)" },
|
||||
{ type: "text", text: "/server:name (MCP) first line\nsecond line" },
|
||||
{ type: "text", text: "/mcp:server:other args" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("sends image, PDF and text blob resources to Claude, and skips other blobs", () => {
|
||||
const blob = (uri: string, mimeType: string, data: string) => ({
|
||||
type: "resource" as const,
|
||||
@@ -5076,6 +5202,7 @@ describe("permission request cancellation", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -5114,10 +5241,11 @@ describe("permission request cancellation", () => {
|
||||
// Let canUseTool reach the awaited requestPermission before cancelling.
|
||||
await Promise.resolve();
|
||||
|
||||
// The tool-call signal is threaded through as the cancellation signal.
|
||||
expect(receivedSignal).toBe(controller.signal);
|
||||
// The cancellation signal follows the tool-call signal.
|
||||
expect(receivedSignal?.aborted).toBe(false);
|
||||
|
||||
controller.abort();
|
||||
expect(receivedSignal?.aborted).toBe(true);
|
||||
|
||||
await expect(pending).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
@@ -5125,7 +5253,7 @@ describe("permission request cancellation", () => {
|
||||
it("does not wait for a permission client that ignores cancellation", async () => {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission: () => new Promise<RequestPermissionResponse>(() => {}),
|
||||
requestPermission: heldRequest<RequestPermissionResponse>,
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const session = injectSession(agent, "session-1");
|
||||
@@ -5144,14 +5272,14 @@ describe("permission request cancellation", () => {
|
||||
});
|
||||
|
||||
describe("a cancelled outcome", () => {
|
||||
function setup() {
|
||||
function setup(options: { v2?: boolean } = {}) {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission: async (): Promise<RequestPermissionResponse> => ({
|
||||
outcome: { outcome: "cancelled" },
|
||||
}),
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} }, options);
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.emittedToolCalls.add("tool-1");
|
||||
const ask = (agentID?: string) =>
|
||||
@@ -5173,6 +5301,29 @@ describe("permission request cancellation", () => {
|
||||
await expect(ask("agent-fg")).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
|
||||
it("denies the tool use of foreground work on v2 without a session/cancel", async () => {
|
||||
// On v2 the client also answers `cancelled` when the user dismissed the
|
||||
// prompt or its policy declined it. The turn goes on without the tool.
|
||||
const { session, ask } = setup({ v2: true });
|
||||
session.liveBackgroundTasks.set("agent-fg", { isSubagent: true });
|
||||
|
||||
for (const agentID of [undefined, "agent-fg"]) {
|
||||
await expect(ask(agentID)).resolves.toEqual({
|
||||
behavior: "deny",
|
||||
message: "User refused permission to run tool",
|
||||
toolUseID: "tool-1",
|
||||
decisionClassification: "user_reject",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("aborts the tool use of foreground work on v2 during a session/cancel", async () => {
|
||||
const { session, ask } = setup({ v2: true });
|
||||
session.cancelled = true;
|
||||
|
||||
await expect(ask()).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
|
||||
it("denies the tool use of a background subagent", async () => {
|
||||
const { session, ask } = setup();
|
||||
session.liveBackgroundTasks.set("agent-bg", { isSubagent: true, backgrounded: true });
|
||||
@@ -5252,7 +5403,7 @@ describe("permission request cancellation", () => {
|
||||
"does not wait for $name when its client ignores cancellation",
|
||||
async ({ start, aborted }) => {
|
||||
// The client holds the elicitation and never answers $/cancel_request.
|
||||
const createElicitation = vi.fn(() => new Promise<CreateElicitationResponse>(() => {}));
|
||||
const createElicitation = vi.fn(heldRequest<CreateElicitationResponse>);
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
createElicitation,
|
||||
@@ -5265,39 +5416,129 @@ describe("permission request cancellation", () => {
|
||||
const controller = new AbortController();
|
||||
const pending = start(agent, controller.signal);
|
||||
await vi.waitFor(() => expect(createElicitation).toHaveBeenCalledOnce());
|
||||
// The client was asked to cancel the request, and ignores that.
|
||||
expect(createElicitation.mock.calls[0]).toEqual([expect.anything(), controller.signal]);
|
||||
const [, requestSignal] = createElicitation.mock.calls[0] as unknown as [
|
||||
unknown,
|
||||
AbortSignal,
|
||||
];
|
||||
controller.abort();
|
||||
// The client was asked to cancel the request, and ignores that.
|
||||
expect(requestSignal.aborted).toBe(true);
|
||||
|
||||
await aborted(pending);
|
||||
expect(session.openUserInputs?.size ?? 0).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("observes a client rejection when the signal aborts during request creation", async () => {
|
||||
const controller = new AbortController();
|
||||
const clientOperation = Promise.reject(new Error("Request cancelled"));
|
||||
const then = vi.spyOn(clientOperation, "then");
|
||||
it.each(elicitations)(
|
||||
"cancels $name before session/close responds",
|
||||
async ({ start, aborted }) => {
|
||||
const events: string[] = [];
|
||||
const createElicitation = vi.fn((params: unknown, signal: AbortSignal) => {
|
||||
signal.addEventListener("abort", () => events.push("cancel_request"), { once: true });
|
||||
return heldRequest<CreateElicitationResponse>(params, signal);
|
||||
});
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
createElicitation,
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
await initializeClient(agent, { elicitation: { form: {}, url: {} } });
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.emittedToolCalls.add("tool-1");
|
||||
|
||||
// Claude Code's own signal for the callback never aborts here.
|
||||
const pending = start(agent, new AbortController().signal);
|
||||
await vi.waitFor(() => expect(createElicitation).toHaveBeenCalledOnce());
|
||||
await agent.closeSession({ sessionId: "session-1" });
|
||||
events.push("closed");
|
||||
|
||||
expect(events).toEqual(["cancel_request", "closed"]);
|
||||
await aborted(pending);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "a permission request",
|
||||
start: (agent: ClaudeAcpAgent, signal: AbortSignal) =>
|
||||
agent.canUseTool("session-1")("Bash", { command: "ls" }, {
|
||||
signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any),
|
||||
},
|
||||
elicitations[0],
|
||||
])(
|
||||
"cancels $name over the connection and stops waiting for a client that ignores it",
|
||||
async ({ start }) => {
|
||||
const toAgent = new TransformStream<AnyMessage, AnyMessage>();
|
||||
const toClient = new TransformStream<AnyMessage, AnyMessage>();
|
||||
let agent!: ClaudeAcpAgent;
|
||||
acpProtocolRouter(
|
||||
{ log: () => {}, error: () => {} },
|
||||
(created) => (agent = created),
|
||||
"v1",
|
||||
).connect({ readable: toAgent.readable, writable: toClient.writable });
|
||||
const cancelled: string[] = [];
|
||||
// The client holds each request. The signal also aborts when the
|
||||
// connection closes, with another reason than `-32800`.
|
||||
const hold = ({ method, signal }: { method: string; signal: AbortSignal }) =>
|
||||
new Promise<never>(() => {
|
||||
signal.addEventListener("abort", () => {
|
||||
if ((signal.reason as { code?: unknown } | undefined)?.code === -32800) {
|
||||
cancelled.push(method);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await acpClient({ name: "test-client" })
|
||||
.onRequest(methods.client.session.requestPermission, hold)
|
||||
.onRequest(methods.client.elicitation.create, hold)
|
||||
.connectWith(
|
||||
{ readable: toClient.readable, writable: toAgent.writable },
|
||||
async (client) => {
|
||||
await client.request(methods.agent.initialize, {
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: { elicitation: { form: {} } },
|
||||
});
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.emittedToolCalls.add("tool-1");
|
||||
const controller = new AbortController();
|
||||
const pending = start(agent, controller.signal);
|
||||
await vi.waitFor(() => expect(session.openUserInputs?.size).toBe(1));
|
||||
controller.abort();
|
||||
|
||||
await expect(pending).rejects.toThrow("Tool use aborted");
|
||||
await vi.waitFor(() => expect(cancelled).toHaveLength(1));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("cancels a permission request before session/close responds", async () => {
|
||||
const events: string[] = [];
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
requestPermission: () => {
|
||||
controller.abort();
|
||||
return clientOperation;
|
||||
requestPermission: (params: RequestPermissionRequest, signal?: AbortSignal) => {
|
||||
signal?.addEventListener("abort", () => events.push("cancel_request"), { once: true });
|
||||
return heldRequest<RequestPermissionResponse>(params, signal);
|
||||
},
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const session = injectSession(agent, "session-1");
|
||||
session.emittedToolCalls.add("tool-1");
|
||||
|
||||
await expect(
|
||||
agent.canUseTool("session-1")("Bash", { command: "ls" }, {
|
||||
signal: controller.signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any),
|
||||
).rejects.toThrow("Tool use aborted");
|
||||
const pending = agent.canUseTool("session-1")("Bash", { command: "ls" }, {
|
||||
signal: new AbortController().signal,
|
||||
suggestions: [],
|
||||
toolUseID: "tool-1",
|
||||
} as any);
|
||||
await vi.waitFor(() => expect(session.openUserInputs?.size).toBe(1));
|
||||
await agent.closeSession({ sessionId: "session-1" });
|
||||
events.push("closed");
|
||||
|
||||
expect(then).toHaveBeenCalledOnce();
|
||||
expect(events).toEqual(["cancel_request", "closed"]);
|
||||
await expect(pending).rejects.toThrow("Tool use aborted");
|
||||
});
|
||||
|
||||
it("does not wait for an eager tool-call update that ignores cancellation", async () => {
|
||||
@@ -6592,7 +6833,7 @@ describe("subagent permission attribution (issue #851)", () => {
|
||||
sessionUpdate: async (notification: AcpSessionNotification) => {
|
||||
updates.push(notification);
|
||||
},
|
||||
requestPermission: () => new Promise(() => {}),
|
||||
requestPermission: heldRequest,
|
||||
} as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
@@ -7129,7 +7370,7 @@ describe("subagent permission attribution (issue #851)", () => {
|
||||
updates.push(notification);
|
||||
},
|
||||
// A client that ignores $/cancel_request.
|
||||
createElicitation: () => new Promise(() => {}),
|
||||
createElicitation: heldRequest,
|
||||
} as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
@@ -8425,7 +8666,7 @@ describe("subagent permission attribution (issue #851)", () => {
|
||||
requestPermission: (params: { sessionId: string }, signal?: AbortSignal) => {
|
||||
log.push(`request @${params.sessionId}`);
|
||||
signal?.addEventListener("abort", () => log.push(`withdrawn @${params.sessionId}`));
|
||||
return new Promise(() => {});
|
||||
return heldRequest(params, signal);
|
||||
},
|
||||
} as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
@@ -14388,6 +14629,8 @@ describe("session/fork", () => {
|
||||
{ sessionUpdate: async (n: SessionNotification) => updates.push(n) } as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
// The scripted sessions run in a cwd that does not exist here.
|
||||
vi.spyOn(agent as any, "validateCwd").mockResolvedValue(undefined);
|
||||
const createSession = vi
|
||||
.spyOn(agent as any, "createSession")
|
||||
.mockImplementation(async (params: any, options: any) => {
|
||||
@@ -14484,6 +14727,35 @@ describe("session/fork", () => {
|
||||
).rejects.toThrow("Claude Code did not start");
|
||||
});
|
||||
|
||||
it("rejects a bad cwd or a relative additional directory before copying the transcript", async () => {
|
||||
const { agent, createSession } = forkingAgent();
|
||||
(agent as any).validateCwd.mockRestore();
|
||||
const missing = path.join(os.tmpdir(), `missing-${randomUUID()}`);
|
||||
|
||||
for (const params of [
|
||||
{ cwd: "workspace" },
|
||||
{ cwd: missing },
|
||||
{ cwd: os.tmpdir(), additionalDirectories: ["extra"] },
|
||||
]) {
|
||||
await expect(
|
||||
agent.unstable_forkSession({ sessionId: "source-id", mcpServers: [], ...params }),
|
||||
).rejects.toMatchObject({ code: -32602 });
|
||||
}
|
||||
expect(forkSession).not.toHaveBeenCalled();
|
||||
expect(createSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports an unknown source session as not found", async () => {
|
||||
const { agent, createSession } = forkingAgent();
|
||||
// What the Claude SDK `forkSession` throws for a session without a transcript.
|
||||
vi.mocked(forkSession).mockRejectedValueOnce(new Error("Session source-id not found"));
|
||||
|
||||
await expect(
|
||||
agent.unstable_forkSession({ sessionId: "source-id", cwd: "/workspace", mcpServers: [] }),
|
||||
).rejects.toMatchObject({ code: -32002, data: { sessionId: "source-id" } });
|
||||
expect(createSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("forks the latest turn in the requested workspace", async () => {
|
||||
const { agent, createSession } = forkingAgent();
|
||||
vi.mocked(forkSession).mockResolvedValueOnce({ sessionId: "fork-id" });
|
||||
@@ -14728,6 +15000,7 @@ describe("session/close", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -14850,6 +15123,7 @@ describe("session/delete", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -14945,7 +15219,10 @@ describe("getOrCreateSession param change detection", () => {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
} as unknown as AcpClient;
|
||||
return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
// The injected sessions run in cwds that do not exist here, such as "/test".
|
||||
vi.spyOn(agent as any, "validateCwd").mockResolvedValue(undefined);
|
||||
return agent;
|
||||
}
|
||||
|
||||
function injectSession(
|
||||
@@ -15010,6 +15287,7 @@ describe("getOrCreateSession param change detection", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -15061,6 +15339,32 @@ describe("getOrCreateSession param change detection", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the live session when a resume or load names a bad cwd or additional directory", async () => {
|
||||
const agent = createMockAgent();
|
||||
(agent as any).validateCwd.mockRestore();
|
||||
const session = injectSession(agent, "s1", { cwd: "/old" });
|
||||
const createSessionSpy = vi.spyOn(agent as any, "createSession");
|
||||
const missing = path.join(os.tmpdir(), `missing-${randomUUID()}`);
|
||||
|
||||
for (const params of [
|
||||
{ cwd: "relative" },
|
||||
{ cwd: missing },
|
||||
{ cwd: os.tmpdir(), additionalDirectories: ["extra"] },
|
||||
]) {
|
||||
await expect(
|
||||
agent.resumeSession({ sessionId: "s1", mcpServers: [], ...params }),
|
||||
).rejects.toMatchObject({ code: -32602 });
|
||||
}
|
||||
vi.mocked(getSessionMessages).mockResolvedValueOnce([]);
|
||||
await expect(
|
||||
agent.loadSession({ sessionId: "s1", cwd: missing, mcpServers: [] }),
|
||||
).rejects.toMatchObject({ code: -32602 });
|
||||
|
||||
expect(agent.sessions["s1"]).toBe(session);
|
||||
expect(session.abortController.signal.aborted).toBe(false);
|
||||
expect(createSessionSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Claude Code writes the conversation at the first prompt, so a live session
|
||||
// that was never prompted has nothing to resume.
|
||||
it("starts a fresh query under the same id for a live session that was never prompted", async () => {
|
||||
@@ -19465,6 +19769,7 @@ describe("post-error recovery", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork("test-session", false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -19989,6 +20294,72 @@ describe("post-error recovery", () => {
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
});
|
||||
|
||||
it("does not cancel a prompt received while the cancel awaits", async () => {
|
||||
// Turn 1 runs and turn 2 is queued when the cancel arrives. Turn 3 arrives
|
||||
// while the cancel awaits `contextCompaction.interrupt()`: ACP v2 cancels
|
||||
// only prompts received before `session/cancel`, so turn 3 must run.
|
||||
const agent = createMockAgent();
|
||||
let releaseInterrupt!: () => void;
|
||||
const interruptGate = new Promise<void>((resolve) => (releaseInterrupt = resolve));
|
||||
let releaseStream!: () => void;
|
||||
const streamGate = new Promise<void>((resolve) => (releaseStream = resolve));
|
||||
const orphanResult = createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: "end_turn",
|
||||
is_error: false,
|
||||
});
|
||||
injectGeneratorSession(
|
||||
agent,
|
||||
(input) => {
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const u1 = await iter.next();
|
||||
yield userEcho(u1.value); // turn 1 active
|
||||
await iter.next(); // turn 2, queued before the cancel
|
||||
const u3 = await iter.next(); // turn 3, queued during the cancel
|
||||
await streamGate;
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" }; // turn 1 cancelled
|
||||
yield orphanResult; // turn 2's result, skipped as an orphan
|
||||
yield userEcho(u3.value);
|
||||
yield createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: "end_turn",
|
||||
is_error: false,
|
||||
});
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
return messageGenerator();
|
||||
},
|
||||
{ contextCompaction: { interrupt: () => interruptGate } },
|
||||
);
|
||||
|
||||
const first = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "first" }],
|
||||
});
|
||||
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
|
||||
const second = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "second" }],
|
||||
});
|
||||
await waitFor(() => (agent.sessions["test-session"]?.turnQueue?.length ?? 0) >= 2);
|
||||
|
||||
const cancel = agent.cancel({ sessionId: "test-session" });
|
||||
const third = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "third" }],
|
||||
});
|
||||
await waitFor(() => (agent.sessions["test-session"]?.turnQueue?.length ?? 0) >= 3);
|
||||
releaseInterrupt();
|
||||
await cancel;
|
||||
releaseStream();
|
||||
|
||||
await expect(first).resolves.toMatchObject({ stopReason: "cancelled" });
|
||||
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
|
||||
await expect(third).resolves.toMatchObject({ stopReason: "end_turn" });
|
||||
await agent.sessions["test-session"]?.consumer;
|
||||
});
|
||||
|
||||
it("drains the orphan count, then promotes a no-echo /compact while still cancelled", async () => {
|
||||
// The case that ONLY the orphan-count gate handles (the old `!cancelled`
|
||||
// gate would hang it): cancel removes a queued turn (count=1), its orphan
|
||||
@@ -21846,7 +22217,7 @@ describe("output after a cancel", () => {
|
||||
expect(texts).toEqual(["Starting the build.", "The build", " passed."]);
|
||||
});
|
||||
|
||||
it("drops the answer to a prompt sent while the cancel was still in progress", async () => {
|
||||
it("answers a prompt sent while the cancel was still in progress", async () => {
|
||||
const interrupting = Promise.withResolvers<void>();
|
||||
const release = Promise.withResolvers<void>();
|
||||
const cancelDone = Promise.withResolvers<void>();
|
||||
@@ -21895,14 +22266,20 @@ describe("output after a cancel", () => {
|
||||
await cancelling;
|
||||
cancelDone.resolve();
|
||||
|
||||
// The cancel covers only the prompts received before it.
|
||||
await expect(first).resolves.toMatchObject({ stopReason: "cancelled" });
|
||||
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
|
||||
await expect(second).resolves.toMatchObject({ stopReason: "end_turn" });
|
||||
await consumer;
|
||||
expect(texts).toEqual(["Starting the build.", "The build", " passed."]);
|
||||
expect(texts).toEqual([
|
||||
"Starting the build.",
|
||||
"Here is the second answer.",
|
||||
"The build",
|
||||
" passed.",
|
||||
]);
|
||||
expect(session.lastTurnFailed).not.toBe(true);
|
||||
});
|
||||
|
||||
it("drops the answer to a prompt sent during the cancel whose request comes before the cancel abandons it", async () => {
|
||||
it("answers a prompt sent during the cancel that Claude Code takes before the cancel ends", async () => {
|
||||
const interrupting = Promise.withResolvers<void>();
|
||||
const release = Promise.withResolvers<void>();
|
||||
const requested = Promise.withResolvers<void>();
|
||||
@@ -21955,10 +22332,16 @@ describe("output after a cancel", () => {
|
||||
await cancelling;
|
||||
swept.resolve();
|
||||
|
||||
// The cancel covers only the prompts received before it.
|
||||
await expect(first).resolves.toMatchObject({ stopReason: "cancelled" });
|
||||
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
|
||||
await expect(second).resolves.toMatchObject({ stopReason: "end_turn" });
|
||||
await consumer;
|
||||
expect(texts).toEqual(["Starting the build.", "The build", " passed."]);
|
||||
expect(texts).toEqual([
|
||||
"Starting the build.",
|
||||
"Here is the second answer.",
|
||||
"The build",
|
||||
" passed.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the prompts an earlier cancel stopped when the user cancels again", async () => {
|
||||
@@ -26538,7 +26921,7 @@ describe("turn steering (_session/steering)", () => {
|
||||
"uses priority:'now' again once $name is withdrawn, though its client never settles it",
|
||||
async ({ start }) => {
|
||||
// A client that ignores $/cancel_request: its request never settles.
|
||||
const userInputRequest = vi.fn(() => new Promise<never>(() => {}));
|
||||
const userInputRequest = vi.fn(heldRequest<never>);
|
||||
const input = new Pushable<any>();
|
||||
const inputPush = vi.spyOn(input, "push");
|
||||
const agent = new ClaudeAcpAgent(
|
||||
@@ -27822,6 +28205,7 @@ describe("session/cancel wedge recovery (issue #680)", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork("test-session", false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
@@ -29730,6 +30114,7 @@ describe("agent selection config option", () => {
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"AskUserQuestion","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Asking for your input","toolCallId":"toolu_ask"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Which database?","type":"text"},"type":"content"}],"rawInput":{"questions":[{"header":"Database","multiSelect":false,"options":[{"description":"Relational","label":"Postgres","preview":"CREATE TABLE t ();"},{"description":"Key-value","label":"Redis"}],"question":"Which database?"}]},"sessionUpdate":"tool_call_update","title":"Which database?","toolCallId":"toolu_ask"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"elicitation/create","payload":{"message":"Which database?","mode":"form","requestedSchema":{"properties":{"question_0":{"oneOf":[{"_meta":{"_claude/askUserQuestionOption":{"preview":"CREATE TABLE t ();"}},"const":"Postgres","description":"Relational","title":"Postgres"},{"const":"Redis","description":"Key-value","title":"Redis"}],"title":"Database","type":"string"},"question_0_custom":{"description":"Type your own answer, or add a note to the option you chose above (optional).","title":"Other","type":"string"}},"type":"object"},"sessionId":"<session>","toolCallId":"toolu_ask"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"User has answered your questions: \"Which database?\"=\"Postgres\".","type":"text"},"type":"content"}],"rawOutput":"User has answered your questions: \"Which database?\"=\"Postgres\".","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_ask"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bg"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bg","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bg"}}}
|
||||
@@ -15,4 +15,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_bg"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_bg"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bash"}}}
|
||||
@@ -14,4 +14,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":1},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_bash"}}}
|
||||
@@ -10,9 +10,9 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"ls -la"},"sessionUpdate":"tool_call_update","title":"ls -la","toolCallId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"command":"ls -la","description":"List files"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"terminalId":"toolu_bash","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{"command":"ls -la","description":"List files"},"title":"ls -la","toolCallId":"toolu_bash"},"type":"tool_call"},"title":"ls -la"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"output":{"data":"YS50cwpiLnRz"},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_bash"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_bash"}}}
|
||||
@@ -20,4 +20,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"ne.","type":"text"},"messageId":"msg_done","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","error":"Context too small","sessionUpdate":"compaction_update","status":"failed"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","content":{"text":"The summary.","type":"text"},"sessionUpdate":"compaction_summary_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"completed"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"in_progress"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","content":{"text":"The summary.","type":"text"},"sessionUpdate":"compaction_summary_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"compactionId":"00000000-0000-4000-8000-000000000001","sessionUpdate":"compaction_update","status":"completed"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"usage_update","size":200000,"used":100}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":100}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/src/app.ts"}],"rawInput":{"file_path":"<cwd>/src/app.ts"},"sessionUpdate":"tool_call_update","title":"Edit src/app.ts","toolCallId":"toolu_edit"}}}
|
||||
@@ -10,9 +10,9 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"value = 2","old_string":"value = 1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1 +1 @@\n-const value = 1;\n+const value = 2;\n"},"type":"diff"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1 +1 @@\n-const value = 1;\n+const value = 2;\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"value = 2","old_string":"value = 1"},"title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\nThe user doesn't want to proceed with this tool use. The tool use was rejected.\n```","type":"text"},"type":"content"}],"rawOutput":"The user doesn't want to proceed with this tool use. The tool use was rejected.","sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Edit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Edit","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/src/app.ts"}],"rawInput":{"file_path":"<cwd>/src/app.ts"},"sessionUpdate":"tool_call_update","title":"Edit src/app.ts","toolCallId":"toolu_edit"}}}
|
||||
@@ -10,10 +10,10 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"const value = 2;","old_string":"const value = 1;"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1,3 +1,3 @@\n line 1\n-const value = 1;\n+const value = 2;\n line 3\n"},"type":"diff"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"modify","path":"<cwd>/src/app.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/src/app.ts <cwd>/src/app.ts\n--- <cwd>/src/app.ts\n+++ <cwd>/src/app.ts\n@@ -1,3 +1,3 @@\n line 1\n-const value = 1;\n+const value = 2;\n line 3\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/src/app.ts"}],"name":"Edit","rawInput":{"file_path":"<cwd>/src/app.ts","new_string":"const value = 2;","old_string":"const value = 1;"},"title":"Edit src/app.ts","toolCallId":"toolu_edit"},"type":"tool_call"},"title":"Edit src/app.ts"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"The file <cwd>/src/app.ts has been updated successfully.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/src/app.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_edit"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"rawInput":{"plan":"# Plan\n1. Do it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"User has approved your plan. You can now start coding.\n\n## Approved Plan:\n# Plan\n1. Do it","sessionUpdate":"tool_call_update","status":"completed","title":"Exited Plan Mode","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"content":"# Plan\n1. Do it","planId":"plan-mode","type":"markdown"},"sessionUpdate":"plan_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Approve Plan","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"rawInput":{"plan":"# Plan\n1. Do it"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes, manually approve edits","optionId":"exit-plan-default"},{"kind":"allow_always","name":"Yes, clear context (0% used) and use auto mode","optionId":"exit-plan-clear-auto"},{"kind":"allow_always","name":"Yes, and use auto mode","optionId":"exit-plan-auto"},{"kind":"allow_always","name":"Yes, and bypass permissions","optionId":"exit-plan-bypass"},{"kind":"reject_once","name":"No, keep planning","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"# Plan\n1. Do it","type":"text"},"type":"content"}],"kind":"switch_mode","name":"ExitPlanMode","rawInput":{"plan":"# Plan\n1. Do it"},"title":"Approve Plan","toolCallId":"toolu_plan"},"type":"tool_call"},"title":"Ready to code?"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n```\nThe user doesn't want to proceed with this tool use.\n```\n```","type":"text"},"type":"content"}],"rawOutput":"The user doesn't want to proceed with this tool use.","sessionUpdate":"tool_call_update","status":"failed","toolCallId":"toolu_plan"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"/goal Ship the feature","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"search","name":"Grep","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"grep","toolCallId":"toolu_grep"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"value"},"sessionUpdate":"tool_call_update","title":"grep \"value\"","toolCallId":"toolu_grep"}}}
|
||||
@@ -16,4 +16,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"pattern":"**/*.ts"},"sessionUpdate":"tool_call_update","title":"Find `**/*.ts`","toolCallId":"toolu_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"src/app.ts\nsrc/b.ts","type":"text"},"type":"content"}],"rawOutput":"src/app.ts\nsrc/b.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_glob"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"mcp__docs__search","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"mcp__docs__search","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"q":"acp"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"_meta":{"claudeCode":{"mcpServer":{"name":"docs","source":"project"}}},"content":[],"kind":"other","name":"mcp__docs__search","rawInput":{"q":"acp"},"title":"mcp__docs__search","toolCallId":"toolu_mcp"},"type":"tool_call"},"title":"mcp__docs__search"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Found 2 documents.","type":"text"},"type":"content"}],"rawOutput":[{"text":"Found 2 documents.","type":"text"}],"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_mcp"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"mode":"select"}}},"kind":"read","locations":[{"path":"/memory/a.md"},{"path":"/memory/b.md"}],"name":"memory_recall","sessionUpdate":"tool_call_update","status":"completed","title":"Recalled 2 memories","toolCallId":"00000000-0000-4000-8000-000000000001"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"claudeCode":{"toolResponse":{"mode":"synthesize"}}},"content":[{"content":{"text":"The user prefers tabs.","type":"text"},"type":"content"}],"kind":"read","name":"memory_recall","sessionUpdate":"tool_call_update","status":"completed","title":"Recalled synthesized memory","toolCallId":"00000000-0000-4000-8000-000000000002"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"sessionUpdate":"tool_call_update","status":"pending","title":"SandboxNetworkAccess","toolCallId":"toolu_net"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"description":"Reason: Sandbox blocks network","options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"```json\n{\n \"host\": \"example.com\"\n}\n```","type":"text"},"type":"content"}],"kind":"other","name":"SandboxNetworkAccess","rawInput":{"host":"example.com"},"title":"example.com","toolCallId":"toolu_net"},"type":"tool_call"},"title":"example.com"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_net"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"NotebookEdit","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"NotebookEdit","toolCallId":"toolu_nb"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
|
||||
@@ -10,9 +10,9 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"cell_id":"cell-1","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"cell_id":"cell-1","edit_mode":"replace","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_nb"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[],"kind":"other","name":"NotebookEdit","rawInput":{"cell_id":"cell-1","edit_mode":"replace","new_source":"print('hi')","notebook_path":"<cwd>/n.ipynb"},"title":"NotebookEdit","toolCallId":"toolu_nb"},"type":"tool_call"},"title":"NotebookEdit"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Updated cell cell-1 with print('hi')","type":"text"},"type":"content"}],"rawOutput":"Updated cell cell-1 with print('hi')","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_nb"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_denied"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_denied","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_denied"}}}
|
||||
@@ -14,4 +14,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{},"sessionUpdate":"terminal_update","terminalId":"toolu_denied"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_denied","type":"terminal"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_denied"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Work","type":"text"},"messageId":"msg_rate","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"ing.","type":"text"},"messageId":"msg_rate","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6","_claude/rateLimit":{"resetsAt":1700000000,"status":"allowed_warning","utilization":0.9}},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6","_claude/origin":{"kind":"human"}},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/a.ts"}],"rawInput":{"file_path":"<cwd>/a.ts"},"sessionUpdate":"tool_call_update","title":"Read a.ts","toolCallId":"toolu_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"```\n1\tconst a = 1;\n```","type":"text"},"type":"content"}],"rawOutput":"1\tconst a = 1;\n<system-reminder>x</system-reminder>","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
{"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":"session_info_update","title":"Fix the app","updatedAt":"2026-01-01T00:00:00.000Z"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"idle"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"idle"}}}
|
||||
{"kind":"response","method":"session/resume","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}]}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"Skill","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Load skill","toolCallId":"toolu_skill"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"skill":"commits"},"sessionUpdate":"tool_call_update","title":"Load skill: commits","toolCallId":"toolu_skill"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"Launching skill: commits","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_skill"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Background review"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"a2","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Background review"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientMessageId":"toolu_agent","recipientSessionId":"a2","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"content":[{"text":"Review the diff","type":"text"}],"messageId":"toolu_agent","recipientSessionId":"a2","senderMessageId":"toolu_agent","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"Reviewi","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_agent"}},"content":{"text":"ng now.","type":"text"},"messageId":"msg_async_sub","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"a2","sessionUpdate":"subagent_update","state":{"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"a2","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"a2","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Check the logs"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Check the logs"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientMessageId":"toolu_late","recipientSessionId":"agent_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"text":"Check logs","type":"text"}],"messageId":"toolu_late","recipientSessionId":"agent_late","senderMessageId":"toolu_late","senderSessionId":"<session>","sessionUpdate":"session_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late"}},"content":[],"kind":"read","locations":[],"name":"Read","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Read File","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"locations":[{"line":1,"path":"/var/log/app.log"}],"rawInput":{"file_path":"/var/log/app.log"},"sessionUpdate":"tool_call_update","title":"Read /var/log/app.log","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"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":"<session>","update":{"sessionId":"agent_late","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_late","toolResponse":{"elapsedTimeSeconds":4}}},"sessionUpdate":"tool_call_update","status":"in_progress","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_late","update":{"content":[{"content":{"text":"```\n1\tok\n```","type":"text"},"type":"content"}],"rawOutput":"1\tok","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_late_read"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Fix the build"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"general-purpose","sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Fix the build"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"background":[],"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"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
@@ -21,17 +21,17 @@
|
||||
{"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":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"background":[],"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"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"},"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":{"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":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"background":[],"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":{"sessionId":"agent_n","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"error"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_n","update":{"background":[],"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}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Plan","sessionId":"agent_outer","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Plan the work"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Plan","sessionId":"agent_outer","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Plan the work"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"background":[],"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":{"_meta":{"claudeCode":{"parentToolUseId":"toolu_outer"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_inner","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Read the spec"}}}
|
||||
{"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":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_inner","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Read the spec"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"background":[],"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"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
@@ -19,12 +19,12 @@
|
||||
{"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":{"sessionId":"agent_inner","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_inner","update":{"background":[],"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":{"sessionId":"agent_outer","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_outer","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Explore code"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Explore code"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"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"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
@@ -20,13 +20,13 @@
|
||||
{"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":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"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"},"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":{"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":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"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"}}}
|
||||
@@ -34,19 +34,19 @@
|
||||
{"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":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"requires_action"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"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"},"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":{"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":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"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":{"sessionId":"agent_1","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_1","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_t","sessionUpdate":"subagent_update","state":{"state":"running"},"title":"Summarize"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"capabilities":{"cancel":{}},"description":"Explore","sessionId":"agent_t","sessionUpdate":"subagent_update","state":{"background":[],"state":"running"},"title":"Summarize"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"background":[],"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"}},"configOptions":[{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","name":"Model","options":[{"name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionUpdate":"config_option_update"}}}
|
||||
@@ -18,7 +18,7 @@
|
||||
{"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":{"sessionId":"agent_t","sessionUpdate":"subagent_update","state":{"background":[],"state":"idle","stopReason":"end_turn"}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"agent_t","update":{"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"entries":[{"content":"Write tests","priority":"medium","status":"pending"}],"planId":"plan","type":"items"},"sessionUpdate":"plan_update"}}}
|
||||
@@ -13,13 +13,13 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"entries":[{"content":"Write tests","priority":"medium","status":"completed"}],"planId":"plan","type":"items"},"sessionUpdate":"plan_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"entries":[{"content":"Write tests","priority":"medium","status":"completed"}],"planId":"plan","type":"items"},"sessionUpdate":"plan_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 2","type":"text"}],"messageId":"<id-2>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-2>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"All ","type":"text"},"messageId":"msg_follow","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"done.","type":"text"},"messageId":"msg_follow","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"other","name":"TaskOutput","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"TaskOutput","toolCallId":"toolu_out"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"task_id":"bash_1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_out"}}}
|
||||
@@ -14,4 +14,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"task_id":"bash_1"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_stop"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"Successfully stopped task: bash_1","type":"text"},"type":"content"}],"rawOutput":"Successfully stopped task: bash_1","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_stop"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Let me thin","type":"text"},"messageId":"msg_text","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"k about it.","type":"text"},"messageId":"msg_text","sessionUpdate":"agent_thought_chunk"}}}
|
||||
@@ -10,4 +10,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"he answer.","type":"text"},"messageId":"msg_text","sessionUpdate":"agent_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"plan":{"entries":[{"content":"Writing tests","priority":"medium","status":"in_progress"},{"content":"Ship","priority":"medium","status":"pending"}],"planId":"plan","type":"items"},"sessionUpdate":"plan_update"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"terminal_update","terminalId":"toolu_slow"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"terminalId":"toolu_slow","type":"terminal"}],"kind":"execute","name":"Bash","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Terminal","toolCallId":"toolu_slow"}}}
|
||||
@@ -16,4 +16,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"exitStatus":{"exitCode":0},"sessionUpdate":"terminal_update","terminalId":"toolu_slow"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_slow"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"fetch","name":"WebFetch","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Fetch","toolCallId":"toolu_fetch"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"url":"https://example.com"},"sessionUpdate":"tool_call_update","title":"Fetch https://example.com","toolCallId":"toolu_fetch"}}}
|
||||
{"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 page","type":"text"},"type":"content"}],"rawInput":{"prompt":"Summarize the page","url":"https://example.com"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_fetch"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"allow_always","name":"Yes, and don't ask again for example.com","optionId":"allow-with-updates"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"content":{"text":"Summarize the page","type":"text"},"type":"content"}],"kind":"fetch","name":"WebFetch","rawInput":{"prompt":"Summarize the page","url":"https://example.com"},"title":"Fetch https://example.com","toolCallId":"toolu_fetch"},"type":"tool_call"},"title":"Fetch https://example.com"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"The page is an example.","type":"text"},"type":"content"}],"rawOutput":"The page is an example.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_fetch"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"fetch","name":"WebSearch","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Web search","toolCallId":"toolu_search"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"query":"acp protocol"},"sessionUpdate":"tool_call_update","title":"Search \"acp protocol\"","toolCallId":"toolu_search"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"content":{"text":"ACP (https://agentclientprotocol.com)\nA summary.","type":"text"},"type":"content"}],"rawOutput":"Web search results for query: \"acp protocol\"\n\nLinks: [...]","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_search"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Write","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Preparing file…","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/old.ts"}],"rawInput":{"file_path":"<cwd>/old.ts"},"sessionUpdate":"tool_call_update","title":"Write old.ts","toolCallId":"toolu_write"}}}
|
||||
@@ -12,4 +12,4 @@
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"The file <cwd>/old.ts has been updated successfully.","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"line":1,"path":"<cwd>/old.ts"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"default","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"kind":"edit","locations":[],"name":"Write","rawInput":{},"sessionUpdate":"tool_call_update","status":"pending","title":"Preparing file…","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"locations":[{"path":"<cwd>/new.ts"}],"rawInput":{"file_path":"<cwd>/new.ts"},"sessionUpdate":"tool_call_update","title":"Write new.ts","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"sessionUpdate":"usage_update","size":200000,"used":3}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawInput":{"content":"export const x = 1;\n","file_path":"<cwd>/new.ts"},"sessionUpdate":"tool_call_update","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"changes":[{"fileType":"text","operation":"add","path":"<cwd>/new.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/new.ts <cwd>/new.ts\nnew file mode 100644\n--- /dev/null\n+++ <cwd>/new.ts\n@@ -0,0 +1 @@\n+export const x = 1;\n"},"type":"diff"}],"sessionUpdate":"tool_call_update","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"requires_action"}}}
|
||||
{"kind":"request","method":"session/request_permission","payload":{"options":[{"kind":"allow_once","name":"Yes","optionId":"allow-once"},{"kind":"reject_once","name":"No","optionId":"reject"}],"sessionId":"<session>","subject":{"toolCall":{"content":[{"changes":[{"fileType":"text","operation":"add","path":"<cwd>/new.ts"}],"patch":{"format":"git_patch","text":"diff --git <cwd>/new.ts <cwd>/new.ts\nnew file mode 100644\n--- /dev/null\n+++ <cwd>/new.ts\n@@ -0,0 +1 @@\n+export const x = 1;\n"},"type":"diff"}],"kind":"edit","locations":[{"path":"<cwd>/new.ts"}],"name":"Write","rawInput":{"content":"export const x = 1;\n","file_path":"<cwd>/new.ts"},"title":"Write new.ts","toolCallId":"toolu_write"},"type":"tool_call"},"title":"Write new.ts"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"background":[],"sessionUpdate":"state_update","state":"running"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"rawOutput":"File created successfully at: <cwd>/new.ts","sessionUpdate":"tool_call_update","status":"completed","toolCallId":"toolu_write"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"_claude/model":"claude-sonnet-4-6"},"cost":{"amount":0,"currency":"USD"},"sessionUpdate":"usage_update","size":200000,"used":2}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"_meta":{"quota":{"model_usage":[],"token_count":{"cachedInputTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"reasoningOutputTokens":0,"totalTokens":0}}},"background":[],"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn","usage":{"cachedReadTokens":0,"cachedWriteTokens":0,"inputTokens":0,"outputTokens":0,"totalTokens":0}}}}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
129
src/tests/background-output.test.ts
Normal file
129
src/tests/background-output.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { appendFile, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES,
|
||||
BACKGROUND_OUTPUT_READ_BYTES,
|
||||
type BackgroundOutput,
|
||||
BackgroundOutputTail,
|
||||
} from "../background-output.js";
|
||||
|
||||
describe("BackgroundOutputTail", () => {
|
||||
let dir: string;
|
||||
let file: string;
|
||||
let published: BackgroundOutput[];
|
||||
let tail: BackgroundOutputTail | undefined;
|
||||
const follow = () =>
|
||||
(tail = new BackgroundOutputTail(file, async (output) => void published.push(output)));
|
||||
|
||||
beforeEach(async () => {
|
||||
dir = await mkdtemp(path.join(tmpdir(), "background-output-"));
|
||||
file = path.join(dir, "bg1.output");
|
||||
published = [];
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
tail?.stop();
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("replaces the terminal's output with the file's first text, then appends what the file adds", async () => {
|
||||
await writeFile(file, "building\n");
|
||||
follow();
|
||||
await vi.waitFor(() => expect(published).toHaveLength(1), { timeout: 2000 });
|
||||
await appendFile(file, "done\n");
|
||||
await vi.waitFor(() => expect(published).toHaveLength(2), { timeout: 2000 });
|
||||
|
||||
expect(published).toEqual([
|
||||
{ text: "building\n", replaces: true },
|
||||
{ text: "done\n", replaces: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it("waits for a file that does not exist yet", async () => {
|
||||
follow();
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
expect(published).toEqual([]);
|
||||
|
||||
await writeFile(file, "started\n");
|
||||
await vi.waitFor(() => expect(published).toHaveLength(1), { timeout: 2000 });
|
||||
expect(published[0]).toEqual({ text: "started\n", replaces: true });
|
||||
});
|
||||
|
||||
it("holds a character that a read splits until its last byte is written", async () => {
|
||||
const check = Buffer.from("✓", "utf8");
|
||||
await writeFile(file, check.subarray(0, 1));
|
||||
follow();
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
expect(published).toEqual([]);
|
||||
|
||||
await appendFile(file, check.subarray(1));
|
||||
await tail!.finish();
|
||||
expect(published).toEqual([{ text: "✓", replaces: true }]);
|
||||
});
|
||||
|
||||
it("reads the rest of the file on finish, and nothing after", async () => {
|
||||
await writeFile(file, "a");
|
||||
follow();
|
||||
await appendFile(file, "b");
|
||||
await tail!.finish();
|
||||
await appendFile(file, "c");
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
|
||||
expect(published.map((output) => output.text).join("")).toBe("ab");
|
||||
});
|
||||
|
||||
it("sends at most the backlog, read by read, from the file's last bytes as a snapshot", async () => {
|
||||
// More than the backlog, written faster than the polls read it.
|
||||
const skipped = "x".repeat(BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES);
|
||||
const kept = "y".repeat(BACKGROUND_OUTPUT_MAX_BACKLOG_BYTES - 4) + "end\n";
|
||||
await writeFile(file, skipped + kept);
|
||||
follow();
|
||||
await tail!.finish();
|
||||
|
||||
expect(published.map((output) => output.text).join("")).toBe(kept);
|
||||
expect(published[0].replaces).toBe(true);
|
||||
expect(published.slice(1).every((output) => !output.replaces)).toBe(true);
|
||||
expect(
|
||||
published.every((output) => Buffer.byteLength(output.text) <= BACKGROUND_OUTPUT_READ_BYTES),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("starts over with a snapshot when the file shrinks", async () => {
|
||||
await writeFile(file, "old output\n");
|
||||
follow();
|
||||
await vi.waitFor(() => expect(published).toHaveLength(1), { timeout: 2000 });
|
||||
await writeFile(file, "new\n");
|
||||
await tail!.finish();
|
||||
|
||||
expect(published).toEqual([
|
||||
{ text: "old output\n", replaces: true },
|
||||
{ text: "new\n", replaces: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it("replaces the placeholder with an empty output when the command wrote nothing", async () => {
|
||||
await writeFile(file, "");
|
||||
follow();
|
||||
await tail!.finish();
|
||||
|
||||
expect(published).toEqual([{ text: "", replaces: true }]);
|
||||
});
|
||||
|
||||
it("publishes nothing on finish when the file never appeared", async () => {
|
||||
follow();
|
||||
await tail!.finish();
|
||||
|
||||
expect(published).toEqual([]);
|
||||
});
|
||||
|
||||
it("reads nothing after stop", async () => {
|
||||
follow();
|
||||
tail!.stop();
|
||||
await writeFile(file, "late\n");
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
|
||||
expect(published).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-age
|
||||
import type { AcpSessionNotification } from "../acp-subagents.js";
|
||||
import { ALLOW_BYPASS } from "../permissions/modes.js";
|
||||
import { NativeSubagentRuntime } from "../native-subagents.js";
|
||||
import { startupFailure } from "./helpers.js";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
@@ -321,6 +322,61 @@ describe("createSession options merging", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("reports a worktree child's cwd from its SubagentStart hook, and none for a child in the session's cwd", async () => {
|
||||
const updates: AcpSessionNotification[] = [];
|
||||
const client = createMockClient();
|
||||
client.sessionUpdate = async (notification) => {
|
||||
updates.push(notification);
|
||||
};
|
||||
const observedAgent = new ClaudeAcpAgent(client);
|
||||
await observedAgent.initialize({ protocolVersion: 1, clientCapabilities: { subagents: {} } });
|
||||
const { sessionId } = await observedAgent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
const session = observedAgent.sessions[sessionId]!;
|
||||
const runtime = (session.nativeSubagentRuntime = new NativeSubagentRuntime(
|
||||
"rfd",
|
||||
sessionId,
|
||||
session,
|
||||
(notification) => client.sessionUpdate(notification),
|
||||
{ log: () => {} },
|
||||
));
|
||||
const subagentStart = async (agentId: string, cwd: string) => {
|
||||
const matcher = capturedOptions!.hooks!.SubagentStart!.find((matcher) => !matcher.matcher);
|
||||
for (const hook of matcher!.hooks) {
|
||||
await hook(
|
||||
{
|
||||
hook_event_name: "SubagentStart",
|
||||
session_id: sessionId,
|
||||
cwd,
|
||||
transcript_path: "/tmp/transcript",
|
||||
agent_id: agentId,
|
||||
agent_type: "general-purpose",
|
||||
},
|
||||
undefined,
|
||||
{ signal: new AbortController().signal },
|
||||
);
|
||||
}
|
||||
};
|
||||
const worktree = path.join(process.cwd(), ".claude", "worktrees", "agent-isolated");
|
||||
await subagentStart("isolated", worktree);
|
||||
await subagentStart("shared", process.cwd());
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "isolated", subagentType: "general-purpose" },
|
||||
async () => {},
|
||||
);
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "shared", subagentType: "general-purpose" },
|
||||
async () => {},
|
||||
);
|
||||
const announcements = updates.flatMap(({ update }) =>
|
||||
update.sessionUpdate === "subagent_update" ? [update] : [],
|
||||
);
|
||||
expect(announcements).toMatchObject([
|
||||
{ sessionId: "isolated", cwd: worktree },
|
||||
{ sessionId: "shared" },
|
||||
]);
|
||||
expect(announcements[1]).not.toHaveProperty("cwd");
|
||||
});
|
||||
|
||||
it("reports an AIR child's model switch under its current generation", async () => {
|
||||
const updates: AcpSessionNotification[] = [];
|
||||
const client = createMockClient();
|
||||
@@ -1346,6 +1402,36 @@ describe("createSession options merging", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("startup failures", () => {
|
||||
it("asks Claude Code to report why it could not start", async () => {
|
||||
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
|
||||
expect(capturedOptions?.env?.CLAUDE_CODE_STARTUP_FAILURE_RESULTS).toBe("1");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["proxy_invalid", RequestError.internalError().code, "Internal error"],
|
||||
["gateway_signin_required", RequestError.authRequired().code, "Authentication required"],
|
||||
["org_pin_mismatch", RequestError.authRequired().code, "Authentication required"],
|
||||
["org_config_refused", RequestError.authRequired().code, "Authentication required"],
|
||||
["cwd_unavailable", RequestError.invalidParams().code, "Invalid params"],
|
||||
])("rejects with %s as code %i", async (reason, code, prefix) => {
|
||||
initError = startupFailure(reason, "Fix the setting and restart Claude Code.");
|
||||
await expect(agent.newSession({ cwd: process.cwd(), mcpServers: [] })).rejects.toMatchObject({
|
||||
code,
|
||||
message: `${prefix}: Fix the setting and restart Claude Code.`,
|
||||
data: { startupFailureReason: reason },
|
||||
});
|
||||
expect(closeSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes on an exit error that names no reason", async () => {
|
||||
initError = new Error("Claude Code process exited with code 1. stderr: boom");
|
||||
await expect(agent.newSession({ cwd: process.cwd(), mcpServers: [] })).rejects.toBe(
|
||||
initError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("elicitation", () => {
|
||||
it("keeps AskUserQuestion disabled and omits callbacks without elicitation capability", async () => {
|
||||
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
|
||||
|
||||
@@ -466,6 +466,12 @@ describe("ACP v2 diffs", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a Windows path native in a git patch, so it matches the changes entry", () => {
|
||||
expect(gitPatchText("C:\\work\\App.ts", "update", [hunk], "absolute")).toContain(
|
||||
'diff --git "C:\\\\work\\\\App.ts" "C:\\\\work\\\\App.ts"\n--- "C:\\\\work\\\\App.ts"\n+++ "C:\\\\work\\\\App.ts"\n',
|
||||
);
|
||||
});
|
||||
|
||||
it("shows no diff at tool use, where the input holds a snippet or no old text", () => {
|
||||
const renderer = new AcpToolCallRenderer(v2);
|
||||
const edit = { file_path: "/work/a.ts", old_string: "old", new_string: "new" };
|
||||
|
||||
@@ -132,6 +132,31 @@ describe("createElicitationResponseToElicitResult", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards content values that only v2 allows, such as an object", () => {
|
||||
// ACP v2 lets an elicitation value be any JSON value. The MCP server checks
|
||||
// the content against its own schema, so none of it is dropped here.
|
||||
const content = {
|
||||
name: "Ada",
|
||||
address: { city: "London" },
|
||||
tags: ["a", 1, null],
|
||||
};
|
||||
const response = { action: "accept", content } as unknown as CreateElicitationResponse;
|
||||
expect(createElicitationResponseToElicitResult(response)).toEqual({
|
||||
action: "accept",
|
||||
content,
|
||||
});
|
||||
});
|
||||
|
||||
it("maps accept content that is no JSON object to an empty object", () => {
|
||||
for (const content of [null, "Ada", ["Ada"]]) {
|
||||
const response = { action: "accept", content } as unknown as CreateElicitationResponse;
|
||||
expect(createElicitationResponseToElicitResult(response)).toEqual({
|
||||
action: "accept",
|
||||
content: {},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("maps decline and cancel", () => {
|
||||
expect(
|
||||
createElicitationResponseToElicitResult({ action: "decline" } as CreateElicitationResponse),
|
||||
@@ -356,6 +381,28 @@ describe("applyAskElicitationResponse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reads only string, number, and boolean picks from v2 content", () => {
|
||||
// A v2 client may send an object or a mixed array. Such values are no
|
||||
// option label, so they count as no pick instead of "[object Object]".
|
||||
const response = {
|
||||
action: "accept",
|
||||
content: {
|
||||
question_0: { label: "A" },
|
||||
question_0_custom: { text: "ignored" },
|
||||
question_1: ["X", { label: "Y" }, null, 2],
|
||||
},
|
||||
} as unknown as CreateElicitationResponse;
|
||||
|
||||
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
|
||||
action: "answered",
|
||||
updatedInput: {
|
||||
questions,
|
||||
metadata: { source: "test" },
|
||||
answers: { "Multi?": "X, 2" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("uses a multi-select question's custom answer alone when nothing is selected", () => {
|
||||
const response = {
|
||||
action: "accept",
|
||||
@@ -652,10 +699,12 @@ describe("refusalFallbackResultFromResponse", () => {
|
||||
{ action: "accept", content: { choice: "cancelled" } },
|
||||
{ action: "accept", content: {} },
|
||||
{ action: "accept", content: { choice: "something-new" } },
|
||||
{ action: "accept", content: { choice: { value: "retry_fallback" } } },
|
||||
{ action: "accept", content: { choice: ["retry_fallback"] } },
|
||||
{ action: "accept" },
|
||||
{ action: "decline" },
|
||||
{ action: "cancel" },
|
||||
] as CreateElicitationResponse[];
|
||||
] as unknown as CreateElicitationResponse[];
|
||||
for (const response of cases) {
|
||||
expect(refusalFallbackResultFromResponse(response)).toBe("cancelled");
|
||||
}
|
||||
|
||||
@@ -256,6 +256,31 @@ describe("setSessionConfigOption Fast mode toggle", () => {
|
||||
expect(response.configOptions).toContainEqual(createFastModeConfigOption(true, false));
|
||||
});
|
||||
|
||||
it("rejects a value whose type differs from the option's advertised type", async () => {
|
||||
// Advertised as `type: "boolean"`: "on" is not a boolean.
|
||||
const booleanClient = setup({ useBooleanOption: true });
|
||||
await expect(
|
||||
booleanClient.agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: FAST_MODE_CONFIG_ID,
|
||||
value: FAST_MODE_ON,
|
||||
}),
|
||||
).rejects.toMatchObject({ code: -32602 });
|
||||
expect(booleanClient.applyFlagSettings).not.toHaveBeenCalled();
|
||||
|
||||
// Advertised as an on/off select: `true` is not a string.
|
||||
const selectClient = setup({ useBooleanOption: false });
|
||||
await expect(
|
||||
selectClient.agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
configId: FAST_MODE_CONFIG_ID,
|
||||
type: "boolean",
|
||||
value: true,
|
||||
}),
|
||||
).rejects.toMatchObject({ code: -32602 });
|
||||
expect(selectClient.applyFlagSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not change session state when the SDK rejects the flag", async () => {
|
||||
const { agent, applyFlagSettings } = setup({ useBooleanOption: true });
|
||||
applyFlagSettings.mockRejectedValueOnce(new Error("nope"));
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
* their own vi.fn spies via `overrides`.
|
||||
*/
|
||||
|
||||
import type { ClientCapabilities, InitializeRequest } from "@agentclientprotocol/sdk/unstable";
|
||||
import {
|
||||
RequestError,
|
||||
type ClientCapabilities,
|
||||
type InitializeRequest,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** The context-usage report the base mock query returns. `rawMaxTokens`
|
||||
* matches the agent's DEFAULT_CONTEXT_WINDOW so window-related assertions
|
||||
@@ -41,6 +45,19 @@ export function makeMockQuery(overrides: Record<string, unknown> = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The error `initializationResult()` rejects with when Claude Code refuses to
|
||||
* start with `CLAUDE_CODE_STARTUP_FAILURE_RESULTS` set, as SDK 0.3.316 builds
|
||||
* it from the CLI's error result (same message prefix and fields).
|
||||
*/
|
||||
export function startupFailure(reason: string, cliMessage: string): Error {
|
||||
return Object.assign(new Error(`Claude Code returned an error result: ${cliMessage}`), {
|
||||
telemetryMessage: "Claude Code returned an error result",
|
||||
errorClass: "error_result",
|
||||
startupFailureReason: reason,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares the client capabilities through `initialize`, as a real client
|
||||
* does. The agent reads its capability choices only there.
|
||||
@@ -51,3 +68,16 @@ export async function initializeClient(
|
||||
): Promise<void> {
|
||||
await agent.initialize({ protocolVersion: 1, clientCapabilities });
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to a client that never answers it, as the SDK connection returns
|
||||
* it with `stopWaitingOnCancel`: it rejects with `-32800` once `signal`
|
||||
* aborts. The real connection also sends `$/cancel_request` then.
|
||||
*/
|
||||
export function heldRequest<T>(_params?: unknown, signal?: AbortSignal): Promise<T> {
|
||||
return new Promise<T>((_resolve, reject) => {
|
||||
const cancel = () => reject(RequestError.requestCancelled(signal?.reason));
|
||||
if (signal?.aborted) cancel();
|
||||
else signal?.addEventListener("abort", cancel, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1002,6 +1002,129 @@ describe("NativeSubagentRuntime in the RFD form", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe("a child's working directory", () => {
|
||||
const worktree = "/project/.claude/worktrees/agent-1";
|
||||
function cwdRuntime(form: "rfd" | "air" = "rfd") {
|
||||
const published: AcpSessionNotification[] = [];
|
||||
const runtime = new NativeSubagentRuntime(
|
||||
form,
|
||||
"root",
|
||||
{ cwd: "/project" },
|
||||
async (notification) => {
|
||||
published.push(notification);
|
||||
},
|
||||
{ log: () => {} },
|
||||
);
|
||||
const deliver = async (notification: AcpSessionNotification) => {
|
||||
const routed = await runtime.route(notification, deliver);
|
||||
if (routed) published.push(routed);
|
||||
};
|
||||
const toolCall = (toolCallId: string, parentToolUseId: string): AcpSessionNotification =>
|
||||
({
|
||||
sessionId: "root",
|
||||
update: {
|
||||
sessionUpdate: "tool_call",
|
||||
toolCallId,
|
||||
title: "Read",
|
||||
status: "pending",
|
||||
_meta: { claudeCode: { toolName: "Read", parentToolUseId } },
|
||||
},
|
||||
}) as AcpSessionNotification;
|
||||
return { runtime, published, deliver, toolCall };
|
||||
}
|
||||
const trace = (published: AcpSessionNotification[]) =>
|
||||
published.map(({ sessionId, update }) => {
|
||||
const u = update as any;
|
||||
return update.sessionUpdate === "subagent_update"
|
||||
? `${sessionId} subagent_update ${u.sessionId}` +
|
||||
("cwd" in u ? ` cwd=${JSON.stringify(u.cwd)}` : "")
|
||||
: `${sessionId} ${update.sessionUpdate}`;
|
||||
});
|
||||
|
||||
it("announces a worktree child with its cwd, before its first tool call", async () => {
|
||||
const { runtime, published, deliver, toolCall } = cwdRuntime();
|
||||
// The child's SubagentStart hook can come before its task_started.
|
||||
await runtime.reportCwd("worker-1", worktree);
|
||||
expect(published).toEqual([]);
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await deliver(toolCall("read-1", "launch-1"));
|
||||
await deliver(launch("launch-1", {}));
|
||||
expect(trace(published)).toEqual([
|
||||
`root subagent_update worker-1 cwd="${worktree}"`,
|
||||
"worker-1 tool_call",
|
||||
]);
|
||||
// Later hooks of the same directory report nothing new.
|
||||
await runtime.reportCwd("worker-1", worktree);
|
||||
expect(published).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("reports the cwd of an announced child at once, and null when it is back in its parent's", async () => {
|
||||
const { runtime, published, deliver, toolCall } = cwdRuntime();
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await deliver(launch("launch-1", {}));
|
||||
await runtime.reportCwd("worker-1", worktree);
|
||||
await deliver(toolCall("read-1", "launch-1"));
|
||||
await runtime.reportCwd("worker-1", "/project/");
|
||||
expect(trace(published)).toEqual([
|
||||
"root subagent_update worker-1",
|
||||
`root subagent_update worker-1 cwd="${worktree}"`,
|
||||
"worker-1 tool_call",
|
||||
"root subagent_update worker-1 cwd=null",
|
||||
]);
|
||||
});
|
||||
|
||||
it("omits the cwd of a child in its parent's directory, also for a nested child in its parent's worktree", async () => {
|
||||
const { runtime, published, deliver } = cwdRuntime();
|
||||
await runtime.reportCwd("worker-1", "/project");
|
||||
await deliver(launch("launch-1", {}));
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await runtime.reportCwd("outer", worktree);
|
||||
await deliver(launch("launch-outer", {}));
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "outer", toolUseId: "launch-outer", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await runtime.reportCwd("inner", worktree);
|
||||
await deliver(launch("launch-inner", {}, "launch-outer"));
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "inner", toolUseId: "launch-inner", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
expect(
|
||||
trace(published).filter(
|
||||
(line) =>
|
||||
line.startsWith("root subagent_update") || line.startsWith("outer subagent_update"),
|
||||
),
|
||||
).toEqual([
|
||||
"root subagent_update worker-1",
|
||||
`root subagent_update outer cwd="${worktree}"`,
|
||||
"outer subagent_update inner",
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports no cwd in AIR's draft, whose subagent_spawned has no such field", async () => {
|
||||
const { runtime, published, deliver } = cwdRuntime("air");
|
||||
await runtime.reportCwd("worker-1", worktree);
|
||||
await deliver(launch("launch-1", {}));
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await runtime.reportCwd("worker-1", "/elsewhere");
|
||||
expect(published.map(({ update }) => update.sessionUpdate)).toEqual(["subagent_spawned"]);
|
||||
expect(JSON.stringify(published)).not.toContain("cwd");
|
||||
});
|
||||
});
|
||||
|
||||
it("serializes concurrent config publications and retries a failed snapshot", async () => {
|
||||
const started = deferred();
|
||||
const release = deferred();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { vi } from "vitest";
|
||||
import { randomUUID } from "crypto";
|
||||
import { SessionBackgroundWork } from "../background-work.js";
|
||||
import { SessionTitles } from "../session-titles.js";
|
||||
|
||||
/** Stand-in agent for a `SessionTitles` whose test doesn't care about titles:
|
||||
@@ -91,6 +92,7 @@ export function mockSessionState(
|
||||
toolUseCache: {},
|
||||
emittedToolCalls: new Set(),
|
||||
liveBackgroundTasks: new Map(),
|
||||
background: new SessionBackgroundWork(sessionId, false),
|
||||
emittedAssistantText: false,
|
||||
owedTrailingIdles: 0,
|
||||
messageIdToUuid: new Map(),
|
||||
|
||||
@@ -1240,7 +1240,7 @@ describe("old session/list path", () => {
|
||||
expect(page.sessions.map((s) => s.sessionId)).toEqual([session.id]);
|
||||
expect(page.sessions[0]).not.toHaveProperty("_meta");
|
||||
expect(vi.mocked(listSessions).mock.calls).toEqual([
|
||||
[{ dir: workspace, limit: 1001, offset: 0 }],
|
||||
[{ dir: workspace, includeWorktrees: false, limit: 1001, offset: 0 }],
|
||||
]);
|
||||
|
||||
// AIR without sessionIndex: the same SDK call.
|
||||
@@ -1251,7 +1251,7 @@ describe("old session/list path", () => {
|
||||
(await airAgent.listSessions({ cwd: workspace })).sessions.map((s) => s.sessionId),
|
||||
).toEqual([session.id]);
|
||||
expect(vi.mocked(listSessions).mock.calls).toEqual([
|
||||
[{ dir: workspace, limit: 1001, offset: 0 }],
|
||||
[{ dir: workspace, includeWorktrees: false, limit: 1001, offset: 0 }],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -2647,17 +2647,12 @@ describe("a session that another process holds", () => {
|
||||
});
|
||||
|
||||
describe("session/delete of an AIR client without sessionIndex", () => {
|
||||
it("fails for an unknown session as the SDK delete did", async () => {
|
||||
it("succeeds for an unknown session, as ACP asks", async () => {
|
||||
const { agent } = createAgent();
|
||||
await initializeClient(agent, air());
|
||||
for (const sessionId of [randomUUID(), "not-a-uuid"]) {
|
||||
const sdkError = await vi.mocked(deleteSession).getMockImplementation()!(sessionId).then(
|
||||
() => undefined,
|
||||
(error: Error) => error,
|
||||
);
|
||||
expect(sdkError).toBeInstanceOf(Error);
|
||||
vi.mocked(deleteSession).mockClear();
|
||||
await expect(agent.deleteSession({ sessionId })).rejects.toThrow(sdkError!.message);
|
||||
await expect(agent.deleteSession({ sessionId })).resolves.toEqual({});
|
||||
expect(deleteSession).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
@@ -2999,14 +2994,13 @@ describe("empty transcripts on delete", () => {
|
||||
const dir = path.join(configDir, "projects", encodeProjectPath(workspace));
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await fs.writeFile(path.join(dir, `${id}.jsonl`), "");
|
||||
const sdkError = await sdkDelete()(id).then(
|
||||
() => undefined,
|
||||
(error: Error) => error,
|
||||
);
|
||||
// The SDK delete does not find an empty transcript, so there is nothing to
|
||||
// archive: the delete succeeds and leaves the file as it is.
|
||||
await expect(sdkDelete()(id)).rejects.toThrow("not found");
|
||||
const service = new SessionIndexService({
|
||||
logError: () => {},
|
||||
});
|
||||
await expect(archiveInsteadOfDelete(id, service)).rejects.toThrow(sdkError!.message);
|
||||
await expect(archiveInsteadOfDelete(id, service)).resolves.toBeUndefined();
|
||||
expect(await fs.readFile(path.join(dir, `${id}.jsonl`), "utf8")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -253,15 +253,14 @@ describe("resolveInitialPermissionMode", () => {
|
||||
expect(resolveInitialPermissionMode({ defaultMode: "plan" }, logger(), true)).toBe("plan");
|
||||
});
|
||||
|
||||
it("continues in the mode of the resumed transcript instead of defaultMode", () => {
|
||||
expect(
|
||||
resolveInitialPermissionMode(
|
||||
{ resumed: "bypassPermissions", defaultMode: "plan" },
|
||||
logger(),
|
||||
true,
|
||||
),
|
||||
).toBe("bypassPermissions");
|
||||
});
|
||||
it.each(["default", "acceptEdits", "plan", "auto", "bypassPermissions"])(
|
||||
"continues in the supported restored mode %s instead of defaultMode",
|
||||
(resumed) => {
|
||||
expect(
|
||||
resolveInitialPermissionMode({ resumed, defaultMode: "dontAsk" }, logger(), true),
|
||||
).toBe(resumed);
|
||||
},
|
||||
);
|
||||
|
||||
it("lets an explicit mode win over the resumed transcript", () => {
|
||||
expect(
|
||||
@@ -285,9 +284,25 @@ describe("resolveInitialPermissionMode", () => {
|
||||
expect(log.error).toHaveBeenCalledWith(expect.stringContaining('"bypassPermissions"'));
|
||||
});
|
||||
|
||||
it("falls back to defaultMode for an unknown resumed mode", () => {
|
||||
it.each(["warp", "dontAsk"])("falls back to defaultMode for resumed mode %s", (resumed) => {
|
||||
expect(
|
||||
resolveInitialPermissionMode({ resumed: "warp", defaultMode: "acceptEdits" }, logger(), true),
|
||||
resolveInitialPermissionMode({ resumed, defaultMode: "acceptEdits" }, logger(), true),
|
||||
).toBe("acceptEdits");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ defaultMode: "plan", allowBypass: true, expected: "plan" },
|
||||
{ defaultMode: "bypassPermissions", allowBypass: true, expected: "bypassPermissions" },
|
||||
{ defaultMode: "bypassPermissions", allowBypass: false, expected: "default" },
|
||||
{ defaultMode: "dontAsk", allowBypass: true, expected: "default" },
|
||||
{ defaultMode: "warp", allowBypass: true, expected: "default" },
|
||||
{ defaultMode: undefined, allowBypass: true, expected: "default" },
|
||||
])(
|
||||
"uses available settings fallback $defaultMode for an unavailable restored mode",
|
||||
({ defaultMode, allowBypass, expected }) => {
|
||||
expect(
|
||||
resolveInitialPermissionMode({ resumed: "dontAsk", defaultMode }, logger(), allowBypass),
|
||||
).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -72,3 +72,27 @@ export function withRunningTerminal(
|
||||
if (Object.keys(meta).length === 0) delete next._meta;
|
||||
return { ...notification, update: next };
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps the tool call of a task that went on in the background running: the
|
||||
* tool result update of a tool call in `toolCallIds` reports `in_progress`
|
||||
* instead of `completed`.
|
||||
*
|
||||
* A Monitor or Workflow call returns as soon as its task starts. The consumer
|
||||
* reports the task's end, or its pause, on the tool call (see
|
||||
* `Session.backgroundToolCalls`).
|
||||
*/
|
||||
export function withRunningToolCall(
|
||||
notification: SessionNotification,
|
||||
toolCallIds: ReadonlySet<string>,
|
||||
): SessionNotification {
|
||||
const update = notification.update;
|
||||
if (
|
||||
update.sessionUpdate !== "tool_call_update" ||
|
||||
!toolCallIds.has(update.toolCallId) ||
|
||||
update.status !== "completed"
|
||||
) {
|
||||
return notification;
|
||||
}
|
||||
return { ...notification, update: { ...update, status: "in_progress" } };
|
||||
}
|
||||
|
||||
@@ -29,12 +29,13 @@ export type ToolCallUpdate = SessionNotification["update"];
|
||||
const PLAN_FILE_CLEAR_SUFFIX = ":plan-file-clear";
|
||||
|
||||
/**
|
||||
* ACP v2's `cancelled` tool call status. v1's statuses have no such value, and
|
||||
* the agent builds its reports in v1 types, so it travels as a v1 status. Only
|
||||
* a v2 client gets it, and the v2 surface sends it as it is
|
||||
* (`src/v2/tool-call.ts`).
|
||||
* ACP v2's `cancelled` and `paused` tool call statuses. v1's statuses have no
|
||||
* such values, and the agent builds its reports in v1 types, so they travel as
|
||||
* v1 statuses. Only a v2 client gets them, and the v2 surface sends them as
|
||||
* they are (`src/v2/tool-call.ts`).
|
||||
*/
|
||||
const V2_CANCELLED = "cancelled" as unknown as ToolCallStatus;
|
||||
export const V2_CANCELLED = "cancelled" as unknown as ToolCallStatus;
|
||||
export const V2_PAUSED = "paused" as unknown as ToolCallStatus;
|
||||
|
||||
/** Whether `status` ends a tool call: `completed`, `failed`, or, on v2, `cancelled`. */
|
||||
export function isFinalToolCallStatus(status: ToolCallStatus | null | undefined): boolean {
|
||||
|
||||
148
src/v2/agent.ts
148
src/v2/agent.ts
@@ -11,12 +11,14 @@ import type {
|
||||
CompleteElicitationNotification,
|
||||
CreateElicitationRequest,
|
||||
CreateElicitationResponse,
|
||||
McpServer,
|
||||
RequestPermissionResponse,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import { ClaudeAcpAgent, type AcpClient, type Logger } from "../acp-agent.js";
|
||||
import type { AcpSessionNotification } from "../acp-subagents.js";
|
||||
import type { AcpPermissionRequest } from "../permissions/presentation.js";
|
||||
import { stateUpdate, V2Background } from "./background.js";
|
||||
import { v1InitializeRequest, v2InitializeResponse } from "./initialize.js";
|
||||
import { v1PermissionResponse, v2PermissionRequest } from "./permission.js";
|
||||
import { v2Foreground, v2Prompt } from "./prompt.js";
|
||||
@@ -60,6 +62,14 @@ export function v2AgentApp(
|
||||
v2Foreground((update) => {
|
||||
void client.send({ sessionId, update });
|
||||
}),
|
||||
background: {
|
||||
changed: (report) => {
|
||||
for (const notification of client.background.changed(report)) {
|
||||
void client.send(notification);
|
||||
}
|
||||
},
|
||||
forget: (sessionId) => client.background.forget(sessionId),
|
||||
},
|
||||
});
|
||||
onAgent(agent);
|
||||
})
|
||||
@@ -76,11 +86,11 @@ export function v2AgentApp(
|
||||
agent.unstable_disableProvider(params),
|
||||
)
|
||||
.onRequest(v2.methods.agent.session.new, async ({ params }) => {
|
||||
const response = await agent.newSession(v1NewSessionRequest(params));
|
||||
return {
|
||||
...v2NewSessionResponse(response),
|
||||
...(await setupCommands(agent, response.sessionId, logger)),
|
||||
};
|
||||
const request = v1NewSessionRequest(params);
|
||||
const response = await agent.newSession(request);
|
||||
const commands = await setupCommands(agent, response.sessionId, logger);
|
||||
afterSetupResponse(agent, response.sessionId, request.mcpServers, commands, logger);
|
||||
return { ...v2NewSessionResponse(response), ...commands };
|
||||
})
|
||||
.onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
|
||||
.onRequest(v2.methods.agent.session.resume, async ({ params }) => {
|
||||
@@ -95,25 +105,48 @@ export function v2AgentApp(
|
||||
await agent.publishCurrentSessionInfo(params.sessionId);
|
||||
// v2 requires a snapshot of the foreground state after the replay and
|
||||
// before the response, also without a replay: a resumed live session can
|
||||
// be running a turn, or waiting on a permission request.
|
||||
// be running a turn, or waiting on a permission request. The current
|
||||
// state of each subagent, terminal and tool call it lists comes first,
|
||||
// as v2 requires. The subagents' go again after the response (see below).
|
||||
await agent.publishLiveSubagents(
|
||||
params.sessionId,
|
||||
client.background.listedSubagents(params.sessionId),
|
||||
);
|
||||
await agent.publishBackgroundToolCalls(
|
||||
params.sessionId,
|
||||
client.background.listedToolCalls(params.sessionId),
|
||||
);
|
||||
for (const notification of client.background.currentItems(params.sessionId)) {
|
||||
void client.send(notification);
|
||||
}
|
||||
void client.send({
|
||||
sessionId: params.sessionId,
|
||||
update: stateSnapshot(agent.foregroundState(params.sessionId)),
|
||||
update: stateUpdate(agent.foregroundState(params.sessionId)),
|
||||
});
|
||||
const commands = await setupCommands(agent, params.sessionId, logger);
|
||||
// The response ends a replay. The subagents RFD treats the replayed
|
||||
// subagent states before it as history, so their current states follow it.
|
||||
setTimeout(() => void agent.publishCurrentSubagentStates(params.sessionId), 0);
|
||||
afterSetupResponse(
|
||||
agent,
|
||||
params.sessionId,
|
||||
restore.request.mcpServers ?? [],
|
||||
commands,
|
||||
logger,
|
||||
() => void agent.publishCurrentSubagentStates(params.sessionId),
|
||||
);
|
||||
return { ...v2ResumeSessionResponse(response), ...commands };
|
||||
})
|
||||
.onRequest(v2.methods.agent.session.fork, ({ params }) =>
|
||||
agent.forking(params.sessionId, async () => {
|
||||
// The agent starts the forked session, so the client can prompt it.
|
||||
const response = await agent.unstable_forkSession(v1ForkSessionRequest(params));
|
||||
const request = v1ForkSessionRequest(params);
|
||||
const response = await agent.unstable_forkSession(request);
|
||||
const commands = await setupCommands(agent, response.sessionId, logger);
|
||||
afterSetupResponse(agent, response.sessionId, request.mcpServers ?? [], commands, logger);
|
||||
return {
|
||||
sessionId: response.sessionId,
|
||||
...v2ResumeSessionResponse(response),
|
||||
...(await setupCommands(agent, response.sessionId, logger)),
|
||||
...commands,
|
||||
};
|
||||
}),
|
||||
)
|
||||
@@ -144,38 +177,72 @@ export function v2AgentApp(
|
||||
void client.send({ sessionId: params.sessionId, update });
|
||||
}),
|
||||
)
|
||||
.onRequest(v2.methods.agent.terminal.cancel, async ({ params }) => {
|
||||
await agent.cancelBackgroundTerminal(params);
|
||||
return {};
|
||||
})
|
||||
.onRequest(v2.methods.agent.toolCall.cancel, async ({ params }) => {
|
||||
await agent.cancelBackgroundToolCall(params);
|
||||
return {};
|
||||
})
|
||||
.onNotification(v2.methods.agent.session.cancel, ({ params }) => agent.cancel(params));
|
||||
}
|
||||
|
||||
/** The `state_update` that reports `state` as the current foreground state. */
|
||||
function stateSnapshot(state: "idle" | "running" | "requires_action"): v2.SessionUpdate {
|
||||
if (state === "idle") return { sessionUpdate: "state_update", state: "idle" };
|
||||
if (state === "running") return { sessionUpdate: "state_update", state: "running" };
|
||||
return { sessionUpdate: "state_update", state: "requires_action" };
|
||||
}
|
||||
|
||||
/**
|
||||
* The `availableCommands` of a setup response, so a client can show the
|
||||
* session's commands without waiting for an update. The agent sends a v1
|
||||
* client the same list as an `available_commands_update` after the response,
|
||||
* and a v2 client none (`ClaudeAcpAgent.afterSetupResponse`). Later changes
|
||||
* still arrive as updates. An empty list is left out, as the draft asks. The
|
||||
* commands are optional, so a failure to read them does not fail the setup.
|
||||
* The `availableCommands` of a setup response, which v2 requires when the
|
||||
* session has commands. The agent sends a v1 client the same list as an
|
||||
* `available_commands_update` after the response, and a v2 client none
|
||||
* (`ClaudeAcpAgent.afterSetupResponse`). Later changes still arrive as
|
||||
* updates. An empty list is left out, as the draft asks. A failure to read
|
||||
* the commands does not fail the setup: the result is `undefined`, and
|
||||
* {@link afterSetupResponse} sends the commands in an update instead.
|
||||
*/
|
||||
async function setupCommands(
|
||||
agent: ClaudeAcpAgent,
|
||||
sessionId: string,
|
||||
logger: Logger | undefined,
|
||||
): Promise<{ availableCommands?: v2.AvailableCommand[] }> {
|
||||
): Promise<{ availableCommands?: v2.AvailableCommand[] } | undefined> {
|
||||
try {
|
||||
const commands = await agent.availableCommands(sessionId);
|
||||
return commands.length > 0 ? { availableCommands: v2AvailableCommands(commands) } : {};
|
||||
} catch (error) {
|
||||
(logger ?? console).error(`Session ${sessionId}: failed to read the session's commands`, error);
|
||||
return {};
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The work that follows a v2 setup response, run once the response is sent
|
||||
* (`ClaudeAcpAgent.afterSetupResponse` does it on v1):
|
||||
*
|
||||
* - When `commands` is `undefined` (reading them for the response failed),
|
||||
* an `available_commands_update` with the commands, read again.
|
||||
* - `then`, such as sending the current states of replayed subagents.
|
||||
* - OAuth for the MCP servers that need it. Its URL elicitation must not
|
||||
* reach the client before the response, which v2 asks against.
|
||||
*/
|
||||
function afterSetupResponse(
|
||||
agent: ClaudeAcpAgent,
|
||||
sessionId: string,
|
||||
mcpServers: McpServer[],
|
||||
commands: { availableCommands?: v2.AvailableCommand[] } | undefined,
|
||||
logger: Logger | undefined,
|
||||
then?: () => void,
|
||||
): void {
|
||||
setTimeout(() => {
|
||||
if (!commands) {
|
||||
agent.sendAvailableCommandsUpdate(sessionId).catch((error) => {
|
||||
(logger ?? console).error(
|
||||
`Session ${sessionId}: failed to send the session's commands`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
}
|
||||
then?.();
|
||||
agent.authenticateMcpServers(sessionId, mcpServers);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link AcpClient} of an ACP v2 connection: it sends the v1 messages of
|
||||
* the agent as v2 messages.
|
||||
@@ -205,6 +272,9 @@ class V2ClientConnection implements AcpClient {
|
||||
/** The `_meta` that the client stores for each tool call. */
|
||||
readonly toolCallMeta = new V2ToolCallMeta();
|
||||
|
||||
/** The `background` list of each session's `state_update`s. */
|
||||
readonly background = new V2Background(this.terminals);
|
||||
|
||||
constructor(
|
||||
private readonly ctx: v2.AgentContext,
|
||||
private readonly logger: Logger,
|
||||
@@ -252,9 +322,11 @@ class V2ClientConnection implements AcpClient {
|
||||
* request opening.)
|
||||
*/
|
||||
send(notification: v2.UpdateSessionNotification): Promise<void> {
|
||||
return this.ctx.notify(v2.methods.client.session.update, notification).catch((error) => {
|
||||
this.logger.error(`Failed to send a ${notification.update.sessionUpdate} update:`, error);
|
||||
});
|
||||
return this.ctx
|
||||
.notify(v2.methods.client.session.update, this.background.stamp(notification))
|
||||
.catch((error) => {
|
||||
this.logger.error(`Failed to send a ${notification.update.sessionUpdate} update:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
extNotification(method: string, params: Record<string, unknown>): Promise<void> {
|
||||
@@ -291,13 +363,17 @@ class V2ClientConnection implements AcpClient {
|
||||
childState = undefined;
|
||||
}
|
||||
// Every update goes to the connection before any is awaited, so an update
|
||||
// that another call sends meanwhile cannot come between them.
|
||||
await Promise.all([
|
||||
...updates.map((v2Update) =>
|
||||
this.ctx.notify(v2.methods.client.session.update, { ...notification, update: v2Update }),
|
||||
// that another call sends meanwhile cannot come between them. The parent's
|
||||
// copy is stamped first: a child's announcement starts its list.
|
||||
const stamped = [
|
||||
...updates.map((v2Update) => this.background.stamp({ ...notification, update: v2Update })),
|
||||
...(childState ? [this.background.stamp(childState)] : []),
|
||||
];
|
||||
await Promise.all(
|
||||
stamped.map((v2Notification) =>
|
||||
this.ctx.notify(v2.methods.client.session.update, v2Notification),
|
||||
),
|
||||
...(childState ? [this.ctx.notify(v2.methods.client.session.update, childState)] : []),
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,7 +437,7 @@ class V2ClientConnection implements AcpClient {
|
||||
const response = await this.ctx.request(
|
||||
v2.methods.client.session.requestPermission,
|
||||
v2PermissionRequest({ ...params, toolCall }),
|
||||
{ cancellationSignal: signal },
|
||||
{ cancellationSignal: signal, stopWaitingOnCancel: true },
|
||||
);
|
||||
return v1PermissionResponse(response);
|
||||
}
|
||||
@@ -377,7 +453,7 @@ class V2ClientConnection implements AcpClient {
|
||||
// one only in a received value. As on v1, an MCP server's schema is
|
||||
// relayed here unchanged, so this cast does not check it.
|
||||
params as v2.CreateElicitationRequest,
|
||||
{ cancellationSignal: signal },
|
||||
{ cancellationSignal: signal, stopWaitingOnCancel: true },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
199
src/v2/background.ts
Normal file
199
src/v2/background.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* The `background` lists of the v2 sessions of a connection (see
|
||||
* `background-work.ts`, which builds them).
|
||||
*
|
||||
* A session that reports its background work gets its list on every
|
||||
* `state_update` of its own stream, as v2 requires, from the list the agent
|
||||
* reports when the session starts. A subagent session of such a session
|
||||
* reports its own list from its announcement on, also in the parent's mirror
|
||||
* of its state (`subagent_update.state`).
|
||||
*/
|
||||
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import type { BackgroundItem, BackgroundReport, WorkStateName } from "../background-work.js";
|
||||
import type { V2Terminals } from "./terminal.js";
|
||||
|
||||
/** `state` as the current work state of a session, with no stop reason. */
|
||||
function currentState(state: WorkStateName): v2.StateUpdate {
|
||||
if (state === "idle") return { state: "idle" };
|
||||
if (state === "running") return { state: "running" };
|
||||
if (state === "requires_action") return { state: "requires_action" };
|
||||
return { state: "unknown" };
|
||||
}
|
||||
|
||||
/** The `state_update` that reports `state` as the session's current work state. */
|
||||
export function stateUpdate(state: WorkStateName): v2.SessionUpdate {
|
||||
return { sessionUpdate: "state_update", ...currentState(state) };
|
||||
}
|
||||
|
||||
/** `state` with the list `background`. */
|
||||
function withBackground(state: v2.StateUpdate, background: BackgroundItem[]): v2.StateUpdate {
|
||||
return { ...state, background } as v2.StateUpdate;
|
||||
}
|
||||
|
||||
/** The final tool call statuses. */
|
||||
const FINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
||||
|
||||
export class V2Background {
|
||||
/**
|
||||
* The latest list of each session that reports one, whether a state of the
|
||||
* session carried it, and the parent of a subagent session.
|
||||
*/
|
||||
private readonly sessions = new Map<
|
||||
string,
|
||||
{ items: BackgroundItem[]; reported: boolean; parent?: string }
|
||||
>();
|
||||
|
||||
/**
|
||||
* The tool calls that advertise `cancel`, by session and tool call. Like a
|
||||
* terminal's (see `V2Terminals.advertiseCancel`), the capability goes with
|
||||
* the final status.
|
||||
*/
|
||||
private readonly cancellableToolCalls = new Set<string>();
|
||||
|
||||
constructor(private readonly terminals: V2Terminals) {}
|
||||
|
||||
/**
|
||||
* `notification` with the session's list, when it is a `state_update` of a
|
||||
* session that reports one, or a `subagent_update` that mirrors the state of
|
||||
* such a child. A child of a session that reports its list reports its own
|
||||
* from its announcement on: empty, until the agent reports it.
|
||||
*/
|
||||
stamp(notification: v2.UpdateSessionNotification): v2.UpdateSessionNotification {
|
||||
const { sessionId, update } = notification;
|
||||
if (
|
||||
update.sessionUpdate === "tool_call_update" &&
|
||||
FINAL_STATUSES.has(String(update.status)) &&
|
||||
this.cancellableToolCalls.delete(`${sessionId} ${update.toolCallId}`)
|
||||
) {
|
||||
// A tool call that reached its final status can no longer be cancelled.
|
||||
return { ...notification, update: { ...update, capabilities: null } };
|
||||
}
|
||||
if (update.sessionUpdate === "state_update") {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return notification;
|
||||
session.reported = true;
|
||||
return { ...notification, update: { ...update, background: session.items } };
|
||||
}
|
||||
if (update.sessionUpdate === "subagent_update" && this.sessions.has(sessionId)) {
|
||||
const subagent = update as Extract<v2.SessionUpdate, { sessionUpdate: "subagent_update" }>;
|
||||
let child = this.sessions.get(subagent.sessionId);
|
||||
if (!child) {
|
||||
child = { items: [], reported: false, parent: sessionId };
|
||||
this.sessions.set(subagent.sessionId, child);
|
||||
}
|
||||
if (subagent.state) {
|
||||
return {
|
||||
...notification,
|
||||
update: { ...subagent, state: withBackground(subagent.state, child.items) },
|
||||
};
|
||||
}
|
||||
}
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the new list of a session, and returns the updates that report it:
|
||||
*
|
||||
* - For each terminal or tool call that the list adds, a `terminal_update`
|
||||
* or `tool_call_update` that lets the client cancel it. It comes before
|
||||
* the first list that names it, as v2 requires of an announcement: its
|
||||
* own updates came earlier.
|
||||
* - Once a state of the session carried a list, the state again with the
|
||||
* new list, without a stop reason: a repeated `idle` is no new
|
||||
* completion. A subagent's goes to its parent's mirror first, as its
|
||||
* other states do.
|
||||
*/
|
||||
changed(report: BackgroundReport): v2.UpdateSessionNotification[] {
|
||||
const { sessionId, items, state, parentSessionId } = report;
|
||||
const session = this.sessions.get(sessionId);
|
||||
const unchanged = session && JSON.stringify(session.items) === JSON.stringify(items);
|
||||
this.sessions.set(sessionId, {
|
||||
items,
|
||||
reported: session?.reported ?? false,
|
||||
parent: parentSessionId ?? session?.parent,
|
||||
});
|
||||
const known = new Set((session?.items ?? []).map((item) => JSON.stringify(item)));
|
||||
const updates: v2.UpdateSessionNotification[] = items.flatMap((item) =>
|
||||
known.has(JSON.stringify(item)) ? [] : this.advertiseCancel(sessionId, item, false),
|
||||
);
|
||||
if (session?.reported && state && !unchanged) {
|
||||
if (parentSessionId) {
|
||||
updates.push({
|
||||
sessionId: parentSessionId,
|
||||
update: { sessionUpdate: "subagent_update", sessionId, state: currentState(state) },
|
||||
});
|
||||
}
|
||||
updates.push({ sessionId, update: stateUpdate(state) });
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current `terminal_update` of each terminal that the session
|
||||
* `sessionId` lists, and the `cancel` capability of each tool call that it
|
||||
* lists, whose other fields the agent sends (see
|
||||
* `ClaudeAcpAgent.publishBackgroundToolCalls`). v2 requires them before the
|
||||
* state snapshot of `session/resume`, as a replay may not have delivered
|
||||
* them.
|
||||
*/
|
||||
currentItems(sessionId: string): v2.UpdateSessionNotification[] {
|
||||
return (this.sessions.get(sessionId)?.items ?? []).flatMap((item) =>
|
||||
this.advertiseCancel(sessionId, item, true),
|
||||
);
|
||||
}
|
||||
|
||||
/** The tool calls that the session `sessionId` lists. */
|
||||
listedToolCalls(sessionId: string): string[] {
|
||||
return (this.sessions.get(sessionId)?.items ?? []).flatMap((item) =>
|
||||
item.type === "tool_call" ? [item.toolCallId] : [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The update that lets the client cancel the listed terminal or tool call
|
||||
* `item`, unless it advertises that already. `again` sends it anyway, as
|
||||
* the item's current state.
|
||||
*/
|
||||
private advertiseCancel(
|
||||
sessionId: string,
|
||||
item: BackgroundItem,
|
||||
again: boolean,
|
||||
): v2.UpdateSessionNotification[] {
|
||||
if (item.type === "terminal") {
|
||||
const update = this.terminals.advertiseCancel(sessionId, item.terminalId, again);
|
||||
return update ? [{ sessionId, update }] : [];
|
||||
}
|
||||
if (item.type !== "tool_call") return [];
|
||||
const key = `${sessionId} ${item.toolCallId}`;
|
||||
if (this.cancellableToolCalls.has(key) && !again) return [];
|
||||
this.cancellableToolCalls.add(key);
|
||||
return [
|
||||
{
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: item.toolCallId,
|
||||
capabilities: { cancel: {} },
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** The subagent sessions that the session `sessionId` lists. */
|
||||
listedSubagents(sessionId: string): string[] {
|
||||
return (this.sessions.get(sessionId)?.items ?? []).flatMap((item) =>
|
||||
item.type === "subagent" ? [item.sessionId] : [],
|
||||
);
|
||||
}
|
||||
|
||||
/** Forgets the list of a session, and those of its subagent sessions. */
|
||||
forget(sessionId: string): void {
|
||||
this.sessions.delete(sessionId);
|
||||
for (const key of this.cancellableToolCalls) {
|
||||
if (key.startsWith(`${sessionId} `)) this.cancellableToolCalls.delete(key);
|
||||
}
|
||||
for (const [child, { parent }] of this.sessions) {
|
||||
if (parent === sessionId) this.forget(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type InitializeResponse,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import { AIR_ASYNC_TASKS_CAPABILITY, AIR_GOAL_KEY } from "../air-extension.js";
|
||||
import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
|
||||
|
||||
/** The v1 `initialize` request that a v2 client means. */
|
||||
@@ -65,15 +66,20 @@ const UNSERVED_EXTENSION_KEYS: readonly string[] = ["steering"];
|
||||
* The v2 `initialize` response for the v1 response of the agent.
|
||||
*
|
||||
* It does not advertise what the v2 surface does not serve yet: the
|
||||
* extensions of {@link UNSERVED_EXTENSION_KEYS}.
|
||||
* extensions of {@link UNSERVED_EXTENSION_KEYS}, and the AIR extensions of
|
||||
* {@link withoutUnservedAirExtensions}.
|
||||
*/
|
||||
export function v2InitializeResponse(response: InitializeResponse): v2.InitializeResponse {
|
||||
if (!response.agentInfo) {
|
||||
throw new Error("ACP v2 requires agentInfo, and the agent reported none");
|
||||
}
|
||||
const meta = response.agentCapabilities?._meta;
|
||||
const topMeta = Object.fromEntries(
|
||||
Object.entries(response._meta ?? {}).filter(([key]) => !UNSERVED_EXTENSION_KEYS.includes(key)),
|
||||
const topMeta = withoutUnservedAirExtensions(
|
||||
Object.fromEntries(
|
||||
Object.entries(response._meta ?? {}).filter(
|
||||
([key]) => !UNSERVED_EXTENSION_KEYS.includes(key),
|
||||
),
|
||||
),
|
||||
);
|
||||
return {
|
||||
protocolVersion: v2.PROTOCOL_VERSION,
|
||||
@@ -92,6 +98,30 @@ export function v2InitializeResponse(response: InitializeResponse): v2.Initializ
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `meta` without the AIR extensions whose methods the v2 surface does not
|
||||
* register: the `asyncTasks` capability (`_session/async_task/stop`) and the
|
||||
* `goal` entry (`_session/goal`) of `_meta.jetbrains.air`.
|
||||
*/
|
||||
function withoutUnservedAirExtensions(meta: Record<string, unknown>): Record<string, unknown> {
|
||||
const jetbrains = asRecord(meta.jetbrains);
|
||||
const air = asRecord(jetbrains.air);
|
||||
if (Object.keys(air).length === 0) return meta;
|
||||
const served = Object.fromEntries(Object.entries(air).filter(([key]) => key !== AIR_GOAL_KEY));
|
||||
if (Array.isArray(served.capabilities)) {
|
||||
served.capabilities = served.capabilities.filter(
|
||||
(capability) => capability !== AIR_ASYNC_TASKS_CAPABILITY,
|
||||
);
|
||||
}
|
||||
return { ...meta, jetbrains: { ...jetbrains, air: served } };
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* The session capabilities of the v2 surface, which serves each through a v1
|
||||
* method of the agent.
|
||||
@@ -105,7 +135,7 @@ export function v2InitializeResponse(response: InitializeResponse): v2.Initializ
|
||||
* v1 baseline; v2 has no SSE transport.
|
||||
* - `systemPrompt`, in `append` and `override` mode.
|
||||
*/
|
||||
const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = {
|
||||
export const V2_SESSION_CAPABILITIES: v2.SessionCapabilities = {
|
||||
prompt: { image: {}, embeddedContext: {} },
|
||||
mcp: { stdio: {}, http: {} },
|
||||
delete: {},
|
||||
|
||||
@@ -33,8 +33,11 @@ export function v2PermissionRequest(request: AcpPermissionRequest): v2.RequestPe
|
||||
* The v1 form of the answer to a permission request.
|
||||
*
|
||||
* v2 adds outcomes that v1 does not know, which the draft says are no
|
||||
* approval. They become `cancelled`, which the agent reads as an aborted tool
|
||||
* use, as it does a cancelled prompt.
|
||||
* approval. They become `cancelled`. In v2, `cancelled` also means that the
|
||||
* user dismissed the prompt or the client declined it by policy. So the agent
|
||||
* reads a `cancelled` outcome on v2 as the user refusing the tool, unless a
|
||||
* `session/cancel` is stopping the session's work: then the tool use is
|
||||
* aborted, as on v1.
|
||||
*/
|
||||
export function v1PermissionResponse(
|
||||
response: v2.RequestPermissionResponse,
|
||||
|
||||
@@ -148,9 +148,13 @@ function idle({ stopReason, usage, _meta }: TurnOutcome): v2.SessionUpdate {
|
||||
* so such a block fails the request with invalid params (-32602) rather than
|
||||
* being dropped from the prompt. The same goes for an embedded blob that
|
||||
* Claude cannot read, e.g. an `application/zip` resource: `promptToClaude`
|
||||
* would drop it.
|
||||
* would drop it. An empty `prompt` fails the same way: v2 requires at least
|
||||
* one content block.
|
||||
*/
|
||||
export function v1PromptRequest({ prompt, ...request }: v2.PromptRequest): PromptRequest {
|
||||
if (prompt.length === 0) {
|
||||
throw RequestError.invalidParams({ prompt }, "`prompt` must hold at least one content block");
|
||||
}
|
||||
return {
|
||||
...request,
|
||||
prompt: prompt.map((block) => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type SystemPrompt,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import { V2_SESSION_CAPABILITIES } from "./initialize.js";
|
||||
import { v1ContentBlock } from "./prompt.js";
|
||||
|
||||
export function v1NewSessionRequest(params: v2.NewSessionRequest): NewSessionRequest {
|
||||
@@ -27,7 +28,7 @@ export function v1NewSessionRequest(params: v2.NewSessionRequest): NewSessionReq
|
||||
// v1 requires the list; v2 treats an omitted list as an empty one.
|
||||
return {
|
||||
...request,
|
||||
mcpServers: (mcpServers ?? []).map(v1McpServer),
|
||||
mcpServers: v1McpServers(mcpServers) ?? [],
|
||||
...v1SystemPromptField(systemPrompt),
|
||||
};
|
||||
}
|
||||
@@ -51,9 +52,10 @@ export function v1RestoreSessionRequest(
|
||||
if (replayFrom == null) {
|
||||
return {
|
||||
method: "resume",
|
||||
request: { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) },
|
||||
request: { ...request, ...(mcpServers ? { mcpServers: v1McpServers(mcpServers) } : {}) },
|
||||
};
|
||||
}
|
||||
const servers = v1McpServers(mcpServers);
|
||||
if (replayFrom.type !== "start") {
|
||||
throw RequestError.invalidParams(
|
||||
{ replayFrom },
|
||||
@@ -62,7 +64,7 @@ export function v1RestoreSessionRequest(
|
||||
}
|
||||
return {
|
||||
method: "load",
|
||||
request: { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) },
|
||||
request: { ...request, mcpServers: servers ?? [] },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,7 +76,7 @@ export function v1ForkSessionRequest(params: v2.ForkSessionRequest): ForkSession
|
||||
const { mcpServers, systemPrompt, ...request } = params;
|
||||
return {
|
||||
...request,
|
||||
...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}),
|
||||
...(mcpServers ? { mcpServers: v1McpServers(mcpServers) } : {}),
|
||||
...v1SystemPromptField(systemPrompt),
|
||||
};
|
||||
}
|
||||
@@ -162,13 +164,23 @@ function v2ConfigOptionsField(options: SessionConfigOption[] | null | undefined)
|
||||
return options != null ? { configOptions: v2ConfigOptions(options) } : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* The v1 list of the MCP servers of a v2 setup request, or undefined for no
|
||||
* list. A request with two servers of the same `name`, or with a transport
|
||||
* that the agent does not advertise in `session.mcp` (such as `acp` or a
|
||||
* custom `_` type), fails with invalid params (-32602): the v1 session setup
|
||||
* would otherwise drop the server without telling the client.
|
||||
*/
|
||||
function v1McpServers(servers: v2.McpServer[] | null | undefined): McpServer[] | undefined {
|
||||
if (servers == null) return undefined;
|
||||
v2.assertSupportedMcpServers(servers, V2_SESSION_CAPABILITIES.mcp);
|
||||
return servers.map(v1McpServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* v2 requires a `type` on every transport and makes the empty lists optional.
|
||||
* The agent recognizes a v1 stdio server by its missing `type`, so the v2 tag
|
||||
* is dropped. Any other transport, such as `acp` or a custom `_` type, fails
|
||||
* the request with invalid params (-32602): the agent advertises only stdio
|
||||
* and HTTP, and the v1 session setup would otherwise drop the server without
|
||||
* telling the client.
|
||||
* is dropped. {@link v1McpServers} has already rejected every other transport.
|
||||
*/
|
||||
function v1McpServer(server: v2.McpServer): McpServer {
|
||||
if (v2.McpServer.isStdio(server)) {
|
||||
@@ -178,8 +190,5 @@ function v1McpServer(server: v2.McpServer): McpServer {
|
||||
if (v2.McpServer.isHttp(server)) {
|
||||
return { ...server, headers: server.headers ?? [] };
|
||||
}
|
||||
throw RequestError.invalidParams(
|
||||
{ type: server.type },
|
||||
`MCP servers of type ${server.type} are not supported`,
|
||||
);
|
||||
throw new Error(`Unreachable: MCP server of type ${server.type} passed the transport check`);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ const TERMINAL_KEYS = [
|
||||
/** The terminal updates of the tool call reports of one v2 connection. */
|
||||
export class V2Terminals {
|
||||
/** The terminal and the last reported command of each running command, by session and tool call. */
|
||||
private readonly running = new Map<string, { terminalId: string; command?: string }>();
|
||||
private readonly running = new Map<
|
||||
string,
|
||||
{ terminalId: string; command?: string; cancellable?: boolean }
|
||||
>();
|
||||
|
||||
/**
|
||||
* The terminal updates that a tool call report carries in the extension's
|
||||
@@ -73,6 +76,8 @@ export class V2Terminals {
|
||||
...(exit.exit_code != null ? { exitCode: exit.exit_code } : {}),
|
||||
...(exit.signal != null ? { signal: exit.signal } : {}),
|
||||
},
|
||||
// An exited terminal can no longer be cancelled.
|
||||
...(running.cancellable ? { capabilities: null } : {}),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
@@ -89,6 +94,30 @@ export class V2Terminals {
|
||||
return { terminal, report: withoutTerminalKeys(report) };
|
||||
}
|
||||
|
||||
/**
|
||||
* The `terminal_update` that lets the client cancel the running terminal
|
||||
* `terminalId` of the session `sessionId` with `terminal/cancel`, with its
|
||||
* command, or undefined when the terminal is not running. `again` sends it
|
||||
* also when the terminal advertises the capability already, as the current
|
||||
* state of the terminal.
|
||||
*/
|
||||
advertiseCancel(
|
||||
sessionId: string,
|
||||
terminalId: string,
|
||||
again = false,
|
||||
): v2.SessionUpdate | undefined {
|
||||
// The agent names a command's terminal after its tool call.
|
||||
const running = this.running.get(`${sessionId} ${terminalId}`);
|
||||
if (!running || (running.cancellable && !again)) return undefined;
|
||||
running.cancellable = true;
|
||||
return {
|
||||
sessionUpdate: "terminal_update",
|
||||
terminalId,
|
||||
...(running.command !== undefined ? { command: running.command } : {}),
|
||||
capabilities: { cancel: {} },
|
||||
};
|
||||
}
|
||||
|
||||
/** Forgets the terminals of a session. */
|
||||
forget(sessionId: string): void {
|
||||
for (const key of this.running.keys()) {
|
||||
|
||||
Reference in New Issue
Block a user