mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-11 03:41:17 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51f514d73c | ||
|
|
b1763c750a | ||
|
|
f6d6429187 | ||
|
|
9fca22a03b | ||
|
|
4a3ff6a1eb | ||
|
|
b73ee491c8 | ||
|
|
626015bf81 | ||
|
|
1ffbb6b577 | ||
|
|
36cb674589 | ||
|
|
f5d79167a6 | ||
|
|
a4a8534c23 | ||
|
|
6bc2e058cb | ||
|
|
99cc1f3245 | ||
|
|
bce309f482 | ||
|
|
6b86f7b46a | ||
|
|
b89e4497da | ||
|
|
7d95d30ec3 | ||
|
|
3d6eb7b5b6 | ||
|
|
de521df295 | ||
|
|
c09543d85a | ||
|
|
d202748976 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.103.0"
|
||||
".": "0.106.0"
|
||||
}
|
||||
|
||||
52
CHANGELOG.md
52
CHANGELOG.md
@@ -1,5 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
## [0.106.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.105.0...v0.106.0) (2026-11-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* report the auth detail as the account's description ([#1591](https://github.com/agentclientprotocol/claude-agent-acp/issues/1591)) ([f6d6429](https://github.com/agentclientprotocol/claude-agent-acp/commit/f6d64291876177f9f593415abfc7e50eb7e7ff56))
|
||||
* serve session/options and initial config options ([#1592](https://github.com/agentclientprotocol/claude-agent-acp/issues/1592)) ([b1763c7](https://github.com/agentclientprotocol/claude-agent-acp/commit/b1763c750aef83fd05ce16901ff1a0d9b4b90234))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* honour the client's settings option in idle compaction ([#1555](https://github.com/agentclientprotocol/claude-agent-acp/issues/1555)) ([b73ee49](https://github.com/agentclientprotocol/claude-agent-acp/commit/b73ee491c8f60a1584a95a7f03557f356e1a991c))
|
||||
* replay the messages before each compaction on ACP v1 and v2 ([#1430](https://github.com/agentclientprotocol/claude-agent-acp/issues/1430)) ([626015b](https://github.com/agentclientprotocol/claude-agent-acp/commit/626015bf81fd34f952406ae77bf60feb9ca39a16))
|
||||
* stop the release executable from loading the project's .env and bunfig.toml ([#1589](https://github.com/agentclientprotocol/claude-agent-acp/issues/1589)) ([4a3ff6a](https://github.com/agentclientprotocol/claude-agent-acp/commit/4a3ff6a1eb16075b96676616ab2bbb98694a4d24))
|
||||
* update to @agentclientprotocol/sdk 2.6.0 ([#1590](https://github.com/agentclientprotocol/claude-agent-acp/issues/1590)) ([9fca22a](https://github.com/agentclientprotocol/claude-agent-acp/commit/9fca22a03bdec5dc265bfdc42df316fb286a8142))
|
||||
|
||||
## [0.105.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.1...v0.105.0) (2026-11-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* serve session/set_title on ACP v1 and v2 ([#1585](https://github.com/agentclientprotocol/claude-agent-acp/issues/1585)) ([a4a8534](https://github.com/agentclientprotocol/claude-agent-acp/commit/a4a8534c23b08cdfd7f656d25f9ab16068fd8ffd))
|
||||
* serve session/status on v1 and v2 ([#1584](https://github.com/agentclientprotocol/claude-agent-acp/issues/1584)) ([6bc2e05](https://github.com/agentclientprotocol/claude-agent-acp/commit/6bc2e058cb27624b4307462ac46167535af400d1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include inline model settings in the model picker ([#1488](https://github.com/agentclientprotocol/claude-agent-acp/issues/1488)) ([36cb674](https://github.com/agentclientprotocol/claude-agent-acp/commit/36cb6745897bf9dabcb967b54233c42b860436b3))
|
||||
* keep a v2 session idle when the model changes between turns ([#1586](https://github.com/agentclientprotocol/claude-agent-acp/issues/1586)) ([f5d7916](https://github.com/agentclientprotocol/claude-agent-acp/commit/f5d79167a649737bdeb1ce3b55ad449a34bb9d47))
|
||||
* report Claude Code's API error message as a notice in v2 ([#1582](https://github.com/agentclientprotocol/claude-agent-acp/issues/1582)) ([99cc1f3](https://github.com/agentclientprotocol/claude-agent-acp/commit/99cc1f324586fad07f1da14bb9764cd16c64e920))
|
||||
|
||||
## [0.104.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.104.0...v0.104.1) (2026-11-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* carry the auth detail in the ACP account label ([#1573](https://github.com/agentclientprotocol/claude-agent-acp/issues/1573)) ([3d6eb7b](https://github.com/agentclientprotocol/claude-agent-acp/commit/3d6eb7b5b650a1f9e5d2e869e92e313c32dbdc86))
|
||||
* distinguish Ultra from Max in auth status ([#1574](https://github.com/agentclientprotocol/claude-agent-acp/issues/1574)) ([7d95d30](https://github.com/agentclientprotocol/claude-agent-acp/commit/7d95d30ec3a4813d580b3bfdedb08f03d93a3571))
|
||||
* update to @anthropic-ai/claude-agent-sdk 0.3.322 ([#1577](https://github.com/agentclientprotocol/claude-agent-acp/issues/1577)) ([b89e449](https://github.com/agentclientprotocol/claude-agent-acp/commit/b89e4497da9184072e7f3c4bb28de79801659222))
|
||||
|
||||
## [0.104.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.103.0...v0.104.0) (2026-11-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement the ACP auth/status RFD next to _auth/status_update ([#1570](https://github.com/agentclientprotocol/claude-agent-acp/issues/1570)) ([d202748](https://github.com/agentclientprotocol/claude-agent-acp/commit/d20274897684e8813477389b83a268f2b4f4ad11))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* offer Anthropic Console login in remote environments ([#1572](https://github.com/agentclientprotocol/claude-agent-acp/issues/1572)) ([c09543d](https://github.com/agentclientprotocol/claude-agent-acp/commit/c09543d85ae1067e8df315f1cd3179527701ea16))
|
||||
|
||||
## [0.103.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.102.0...v0.103.0) (2026-11-10)
|
||||
|
||||
|
||||
|
||||
@@ -140,7 +140,8 @@ for v2 is negotiated down to v1. With `v2`, the adapter rejects the `initialize`
|
||||
|
||||
On v2 the adapter serves the session baseline (`session/new`, `session/list`, `session/resume`
|
||||
with replay, `session/close`, `session/prompt`, `session/cancel`), `session/delete`,
|
||||
`session/fork`, additional directories, config options (the mode is one), plans, notices,
|
||||
`session/fork`, `session/status`, `session/options` (with initial `configOptions` on `session/new` and
|
||||
`session/resume`), `session/set_title`, additional directories, config options (the mode is one), plans, notices,
|
||||
compaction, display terminals for commands, permission requests, elicitation, providers, auth, and
|
||||
[subagents](#subagent-sessions). Steering is v1 only for now.
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ The adapter keeps them where they are.
|
||||
|
||||
## Auth source display
|
||||
|
||||
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
|
||||
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. The adapter also reports the same identity through the ACP `auth/status` method and `auth/status_update` notification (advertised as `auth.status`), with `kind`, `detail` and `plan` under the account's `_meta.claudeCode`. The account's `description` carries the `detail` too, e.g. `label: "Anthropic API key"` with `description: "ANTHROPIC_API_KEY"`. Each notification is sent when its own payload changed. `auth/status` answers with the identity last reported and starts a new read, whose change arrives as a notification. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
|
||||
|
||||
A command containing the whole word `central` and the word sequence `proxy start` is displayed as:
|
||||
|
||||
@@ -240,6 +240,8 @@ Matching is case-insensitive and accepts multiple whitespace characters between
|
||||
|
||||
Other active helpers use `label: "Anthropic API key"` and `detail: "API key helper"`; unknown key-source identifiers are not exposed as display text. CLI and SDK reads share this presentation, retain stale-probe protection, and suppress unchanged notifications.
|
||||
|
||||
For Max subscriptions, the existing `claude auth status --json` probe's optional `planTier` distinguishes `max_20x` (displayed as `Claude Ultra`) from `max_5x` (`Claude Max`). Missing, unrecognized, or malformed tiers fall back to the subscription's usual label. Other subscriptions and auth sources keep their existing classification. The raw `account.plan` and ACP `_meta.claudeCode.plan` remain unchanged. SDK account reads lack the tier, so they preserve the known label for the same account and plan; a fresh CLI read replaces it, including when the tier is absent. This is display metadata, not an entitlement check, and requires no additional probe or direct credential-file reads.
|
||||
|
||||
## Zed conventions
|
||||
|
||||
The adapter keeps these Zed and upstream conventions unchanged for every client.
|
||||
@@ -1406,6 +1408,8 @@ Each row carries `_meta.jetbrains.air` with flat row fields of the RFDs. `archiv
|
||||
|
||||
`_session/rename { "sessionId": "…", "title": "…" }` returns `{}`.
|
||||
|
||||
ACP's `session/set_title` renames the same way for every client, on v1 and v2, without `sessionIndex`, and returns `{ "title": "…" }`: the title as `session/list` shows it to the client. On v1 an AIR client gets it without the archive prefix, and every other client, and every v2 client, with it.
|
||||
|
||||
- A blank title is `-32602`. The adapter collapses whitespace and keeps the first 200 characters, trimmed, as the CLI does. An `[archived] ` prefix of the requested title is dropped: a rename never changes the archive state.
|
||||
- The title is stored as the title records of [Archive](#archive): a `custom-title` and an `agent-name` record. An archived session keeps its `[archived] ` prefix (and the title is cut to 200 characters with it), so a rename does not unarchive it.
|
||||
- A session that runs here on a CLI without the `rename_session` control request is refused with `-32600`: its CLI holds the title and would write it back.
|
||||
|
||||
230
package-lock.json
generated
230
package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.103.0",
|
||||
"version": "0.106.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.103.0",
|
||||
"version": "0.106.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.5.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.321",
|
||||
"@agentclientprotocol/sdk": "2.6.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.322",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
@@ -38,9 +38,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.5.0.tgz",
|
||||
"integrity": "sha512-MFlXeIuwN6nZSK45SAsSnVjbLgQ2r4fnthoF1lQoGJP3PdaPRpOZeCF2Dds8S4cN8yZP5QbxB1bkJccGskmcYw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.6.0.tgz",
|
||||
"integrity": "sha512-3FtL6MkAV5F9qxg0FQV8wmBszIm2xuVPoDWOILIFSDQ+Vq3Twdh7Cw6Hk2GOPUbevgrTUpUAqZXQy7Geaj79mw==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/server": "^2.4.0",
|
||||
@@ -53,22 +53,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.321.tgz",
|
||||
"integrity": "sha512-Smer/h/jQLTtT+l4sxaAUwZq7wif/9RzzuqltS5Gy+kAFo/iP89H7JHa8avhVT0xGkPBvBD/1Bbdg3pcArhQcw==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.322.tgz",
|
||||
"integrity": "sha512-r88ij7R3SPV2j3BtFuavgrbWZNE8SVVS42AOQ1KxlS+QnSwNNnUhmVXV3Es2dniZySBYX8ENebL43ffhiB0azw==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.14.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.321",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.321"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.322",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.322"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -77,9 +77,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.321.tgz",
|
||||
"integrity": "sha512-r718mW98hvRbmCa0TtSgImHkgyPUjPXSHOsORrehn2a8Z0FU1A2RkX9gCjY7EzZgL0RNbZXsBbkuab//kUjzJw==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.322.tgz",
|
||||
"integrity": "sha512-LoKkldW2zkLfaOLQD44mL1Ot4Aup8dBIo356LEeO3v2vF7JVNhMllQIXhhGpbYRz57QaYsGvOe0Kb4SbssHwcg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -90,9 +90,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.321.tgz",
|
||||
"integrity": "sha512-C08vEnnDylWDaFprx/B1B13RZK3la1HOqR986igkZSwhY//Yxsk5eRI+gz7sxMfhcLZX2NS8PftCOiXr3S74/Q==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.322.tgz",
|
||||
"integrity": "sha512-rTvLS9G1OzjMIaSXrJglQtmRtVcaRmMbJQC+ND02+atCA463ac7Ty/Wc6tyzBGk8zFFGKWnfAhKtLZTNg91eMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -103,9 +103,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.321.tgz",
|
||||
"integrity": "sha512-vdk9tON09SnsSh/6ULzYTBgsKRat6FyIaqnh4snYl3uy3qX0jpv5UuFUWgS5tvQ1ewI0qYF2k+D78El4gi7/iQ==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.322.tgz",
|
||||
"integrity": "sha512-H1v33+MxB5JMPlnJLZffiOQ+6muxyA7tgHPyYQ+So4TH7uRhaPcjv2X03CUo5U9gXleO6u+X8ngNNDRFtfUaSg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -119,9 +119,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.321.tgz",
|
||||
"integrity": "sha512-+8zFXMpJT2r+0j4E1v9BgRf+Qjtmmp4IG/3mDLT6jB6iA+9UvG9RLex+cqct09d+7xP2mDP8kL4cD1WtKUFX1w==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.322.tgz",
|
||||
"integrity": "sha512-zvgvVA/Mxpu1QttICZ1DGDlmgGo62HdVgKg6WbDGDQ3dgxtRC/kpCIepjtGEL8O1iO5NRxQ4mv8okLwCFcyrLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -135,9 +135,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.321.tgz",
|
||||
"integrity": "sha512-i/Y8Ps4q68SRwWB54yFXyItBwn5YHIOYY4R1gla6p4UwF/pM+xxrHycv+yecbC7oU30ShuBYKWN9mRIZ/BCMqw==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.322.tgz",
|
||||
"integrity": "sha512-M/i7uV5wQf1kvjiPJr2KF5PeA9BpftUpMTtwE+DpQ0Tj+scthQXSMsEhDvau/Rh/MfFOc+UHED4tltignmA5+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -151,9 +151,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.321.tgz",
|
||||
"integrity": "sha512-ils3IGsNKIDiURuOgSWorLsjMOlKcMdXQCQyaygEycdtPFItPoIBIYDVjoC1I0XV4tlfXXCHfUvNrs23g4HKNw==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.322.tgz",
|
||||
"integrity": "sha512-jprUZMcQ3XcpoxwKRQ0fi3qAM1zYcytmNGhlD0gWDknbbSu8di5jLU7231ihJrCr4gbbtDu3FiFUd6w9EBEjfw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -167,9 +167,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.321.tgz",
|
||||
"integrity": "sha512-v6/8ikUcYRSYhH5PSGcHq8L6+DKRq/M4I3BzFckis6kMh3degvSuUD1u3+M/oSJ/bcSOA8tXbUgTTwKrlOgvwQ==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.322.tgz",
|
||||
"integrity": "sha512-o5sPmBKT4NOglODC45wSTFmjD1AQIboC9NdGFQFOTa8kMF3bok+YqpB0DF81RmMDf3iR5tieonZvOXdYm1TDjQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -180,9 +180,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.321",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.321.tgz",
|
||||
"integrity": "sha512-9i7ziCR+Wg47cRhYONHnUpTGxUgxfy/+J3N3/4F8YGqz7WLSNH3hpGRj6uJzNK4EMyEhFsca532uD2we0ZMckQ==",
|
||||
"version": "0.3.322",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.322.tgz",
|
||||
"integrity": "sha512-gONOkYx76of3p73opgWAT9gqpV7PRL8+az3vKoWXLc4xaDW6vhZF7fixpCnLDeUmPWxjXhnQxf30WGpKneTzNw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -555,9 +555,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
|
||||
"integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz",
|
||||
"integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -573,9 +573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz",
|
||||
"integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -591,9 +591,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz",
|
||||
"integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -609,9 +609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz",
|
||||
"integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -627,9 +627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
|
||||
"integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz",
|
||||
"integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -645,9 +645,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
|
||||
"integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz",
|
||||
"integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -663,9 +663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz",
|
||||
"integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -684,9 +684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz",
|
||||
"integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -705,9 +705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz",
|
||||
"integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -726,9 +726,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz",
|
||||
"integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -747,9 +747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
|
||||
"integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz",
|
||||
"integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -768,9 +768,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
|
||||
"integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz",
|
||||
"integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -789,9 +789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
|
||||
"integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz",
|
||||
"integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -807,9 +807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz",
|
||||
"integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -825,9 +825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
|
||||
"integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz",
|
||||
"integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1945,9 +1945,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "8.7.3",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.3.tgz",
|
||||
"integrity": "sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==",
|
||||
"version": "8.7.4",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.4.tgz",
|
||||
"integrity": "sha512-LKBXzX700wz3zAGNNmVnjRI5mEbYkLv7UbMI4+9Xa+JPcg2UZh6AgY/2CZh9RrJjFM5ttC/N1lD+KzyFoZITCw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2408,9 +2408,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.2.16",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.16.tgz",
|
||||
"integrity": "sha512-4LSJpbuBuPdGrONCdZUy0xnxtLf8eEz+EY0SMXdHQEBU7bgUHDVblU/JAjryv3wID9/LK7/YuwFwq+AQNfMscQ==",
|
||||
"version": "6.2.17",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.17.tgz",
|
||||
"integrity": "sha512-wOuK9V1yujQFH5S0ZWmLcDZN7+IKh9bUCC2ZPCf2HDlol3WWHy+PPyNjEI75FXRLMp3vDKQcupP59xhVU95hcg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
@@ -3275,9 +3275,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.2.16",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
|
||||
"integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
|
||||
"version": "1.2.18",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz",
|
||||
"integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -3292,21 +3292,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.16",
|
||||
"@rolldown/binding-android-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.16",
|
||||
"@rolldown/binding-darwin-x64": "1.2.16",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.16",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.16",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.16",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.16",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.16",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.16",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.16"
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.18",
|
||||
"@rolldown/binding-android-arm64": "1.2.18",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.18",
|
||||
"@rolldown/binding-darwin-x64": "1.2.18",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.18",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.18",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.18",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.18",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.18",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.18",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.18",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.18",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.18",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.18",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.18"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3699,9 +3699,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
|
||||
"integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
|
||||
"version": "8.3.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz",
|
||||
"integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -3709,7 +3709,7 @@
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"postcss": "^8.5.32",
|
||||
"rolldown": "~1.2.16",
|
||||
"rolldown": "~1.2.18",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.103.0",
|
||||
"version": "0.106.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -68,8 +68,8 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.5.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.321",
|
||||
"@agentclientprotocol/sdk": "2.6.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.322",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
|
||||
@@ -74,9 +74,24 @@ try {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// By default a compiled executable loads `.env`, `.env.local` and `bunfig.toml`
|
||||
// from its working directory at startup. That directory is the user's project:
|
||||
// its dotenv values would reach Claude and every Bash command as real
|
||||
// environment variables (overriding the project's own `.env.test`), and a
|
||||
// `bunfig.toml` that fails to parse would stop the agent from starting.
|
||||
const bun = spawnSync(
|
||||
"bun",
|
||||
["build", "src/binary-entry.ts", "--compile", "--target", target, "--outfile", outfile],
|
||||
[
|
||||
"build",
|
||||
"src/binary-entry.ts",
|
||||
"--compile",
|
||||
"--no-compile-autoload-dotenv",
|
||||
"--no-compile-autoload-bunfig",
|
||||
"--target",
|
||||
target,
|
||||
"--outfile",
|
||||
outfile,
|
||||
],
|
||||
{ cwd: repoRoot, stdio: "inherit", shell: process.platform === "win32" },
|
||||
);
|
||||
if (bun.error) {
|
||||
|
||||
@@ -58,8 +58,9 @@ function check(ok, label, detail) {
|
||||
|
||||
// ---------------------------------------------------------------- basic
|
||||
|
||||
function runSync(args, [cmd, ...cmdArgs] = [command, ...commandArgs]) {
|
||||
function runSync(args, [cmd, ...cmdArgs] = [command, ...commandArgs], cwd) {
|
||||
const result = spawnSync(cmd, [...cmdArgs, ...args], {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -101,6 +102,26 @@ if (positionals.length === 0) {
|
||||
"without claude next to it, says the CLI is missing",
|
||||
missing.stdout || missing.error,
|
||||
);
|
||||
|
||||
// Started in a project directory, the executable must not read the
|
||||
// project's dotenv files or bunfig.toml. Node stands in for `claude`, to
|
||||
// print the environment the CLI is spawned with.
|
||||
copyFileSync(process.execPath, join(scratch, `claude${exe}`));
|
||||
const project = join(scratch, "project");
|
||||
mkdirSync(project);
|
||||
writeFileSync(join(project, ".env"), "ACP_SMOKE_DOTENV=leaked\n");
|
||||
writeFileSync(join(project, ".env.local"), "ACP_SMOKE_DOTENV_LOCAL=leaked\n");
|
||||
writeFileSync(join(project, "bunfig.toml"), "= not toml\n");
|
||||
const probe = runSync(
|
||||
["--cli", "-p", "[process.env.ACP_SMOKE_DOTENV, process.env.ACP_SMOKE_DOTENV_LOCAL].join()"],
|
||||
[alone],
|
||||
project,
|
||||
);
|
||||
check(
|
||||
probe.stdout === ",",
|
||||
"ignores .env, .env.local and bunfig.toml in the working directory",
|
||||
probe.stdout || probe.error,
|
||||
);
|
||||
} finally {
|
||||
rmSync(scratch, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
AuthStatusUpdateNotification,
|
||||
CompleteElicitationNotification,
|
||||
CreateElicitationRequest,
|
||||
CreateElicitationResponse,
|
||||
@@ -15,7 +16,7 @@ import type { AcpPermissionRequest } from "./permissions/presentation.js";
|
||||
* Client-facing surface the agent calls back into. This is the subset of ACP
|
||||
* client methods the agent actually uses, expressed as a narrow interface so
|
||||
* tests can supply lightweight mocks. In the v1 agent it is backed by
|
||||
* `ClientConnection` (`v1/acp-agent.ts`) over the SDK's typed `AgentContext`.
|
||||
* `ClientConnection` (`v1/client-connection.ts`) over the SDK's typed `AgentContext`.
|
||||
* The v2 session output passes one that only takes `sessionUpdate`
|
||||
* (`v2/stream.ts`).
|
||||
*/
|
||||
@@ -38,6 +39,8 @@ export interface AcpClient {
|
||||
signal?: AbortSignal,
|
||||
): Promise<CreateElicitationResponse>;
|
||||
completeElicitation(params: CompleteElicitationNotification): Promise<void>;
|
||||
/** Sends `auth/status_update`: the connection's auth state changed. */
|
||||
authStatusUpdate(params: AuthStatusUpdateNotification): Promise<void>;
|
||||
/** Send a custom (extension) notification, e.g. `_claude/sdkMessage`. */
|
||||
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { HIDE_CLAUDE_AUTH_FLAG } from "./hide-claude-auth.js";
|
||||
|
||||
/**
|
||||
* The command that starts this agent again with the flags it was started with,
|
||||
* for a client to run in a terminal (e.g. `terminal-auth` login).
|
||||
@@ -17,3 +19,15 @@ export function agentCommand(argv: readonly string[] = process.argv): {
|
||||
const compiled = script.includes("$bunfs") || script.includes("~BUN");
|
||||
return { command: process.execPath, args: compiled ? flags : [script, ...flags] };
|
||||
}
|
||||
|
||||
/**
|
||||
* The arguments for the Claude CLI under `--cli`: the flags this process was
|
||||
* started with, minus the ones the adapter owns. The terminal-auth command
|
||||
* reruns the agent with its own flags (`--hide-claude-auth --cli auth login`),
|
||||
* and the CLI rejects the unknown option `--hide-claude-auth`. `argv.slice(2)`
|
||||
* is the flags under both Node and a compiled executable (see
|
||||
* {@link agentCommand}).
|
||||
*/
|
||||
export function cliArgs(argv: readonly string[] = process.argv): string[] {
|
||||
return argv.slice(2).filter((arg) => arg !== "--cli" && arg !== HIDE_CLAUDE_AUTH_FLAG);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ import { shouldHideClaudeAuth } from "./hide-claude-auth.js";
|
||||
*
|
||||
* Without either, the client cannot run a login, and the list is empty. On a
|
||||
* remote machine (SSH, `NO_BROWSER`, `CLAUDE_CODE_REMOTE`, as the SDK's own
|
||||
* isRemote check) the OAuth redirect to localhost cannot reach a browser, and
|
||||
* `claude auth login` falls back to a manual flow that does not work over ACP,
|
||||
* so the one method runs the TUI's `claude /login` instead. The Claude
|
||||
* subscription method is left out when `shouldHideClaudeAuth()` says so.
|
||||
* isRemote check) the subscription login runs the TUI's `claude /login`:
|
||||
* clients such as Tidewave set `NO_BROWSER` to get it. The Anthropic Console
|
||||
* login is offered there too, because since Claude Code 2.1.126 `auth login`
|
||||
* accepts the OAuth code pasted into the terminal when the localhost callback
|
||||
* can't be reached. The Claude subscription method is left out when
|
||||
* `shouldHideClaudeAuth()` says so.
|
||||
*/
|
||||
export function terminalAuthMethods({
|
||||
terminal,
|
||||
@@ -42,10 +44,8 @@ export function terminalAuthMethods({
|
||||
process.env.SSH_TTY ||
|
||||
process.env.CLAUDE_CODE_REMOTE
|
||||
);
|
||||
if (isRemote) {
|
||||
if (shouldHideClaudeAuth()) return [];
|
||||
return [
|
||||
withMeta(
|
||||
const subscriptionLogin = isRemote
|
||||
? withMeta(
|
||||
{
|
||||
description: "Run `claude /login` in the terminal",
|
||||
name: "Log in with Claude",
|
||||
@@ -54,13 +54,8 @@ export function terminalAuthMethods({
|
||||
args: ["--cli"],
|
||||
},
|
||||
"Claude Login",
|
||||
),
|
||||
];
|
||||
}
|
||||
const methods: AuthMethod[] = [];
|
||||
if (!shouldHideClaudeAuth()) {
|
||||
methods.push(
|
||||
withMeta(
|
||||
)
|
||||
: withMeta(
|
||||
{
|
||||
description: "Use Claude subscription ",
|
||||
name: "Claude Subscription",
|
||||
@@ -69,9 +64,9 @@ export function terminalAuthMethods({
|
||||
args: ["--cli", "auth", "login", "--claudeai"],
|
||||
},
|
||||
"Claude Login",
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
const methods: AuthMethod[] = [];
|
||||
if (!shouldHideClaudeAuth()) methods.push(subscriptionLogin);
|
||||
methods.push(
|
||||
withMeta(
|
||||
{
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* The connection's `authStatus` state, shared by the v1 core and the v2
|
||||
* core: the last identity it reported, the `claude auth status --json` probe,
|
||||
* and the `_auth/status_update` pushes (see `auth-status.ts` for when each
|
||||
* core reads the identity).
|
||||
* the `_auth/status_update` and `auth/status_update` pushes, and the
|
||||
* `auth/status` answer (see `auth-status.ts` for when each core reads the
|
||||
* identity).
|
||||
*/
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
type Settings,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
type AcpAuthStatus,
|
||||
AUTH_STATUS_PROBE_TIMEOUT_MS,
|
||||
type AuthStatus,
|
||||
type AuthStatusKind,
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
fromCliStatus,
|
||||
mergeAuthStatus,
|
||||
sameAuthStatus,
|
||||
toAcpAuthStatus,
|
||||
} from "./auth-status.js";
|
||||
import { claudeCliPath } from "./claude-cli.js";
|
||||
import type { SettingsManager } from "./settings.js";
|
||||
@@ -36,6 +39,8 @@ export type AuthStatusSettings = Pick<SettingsManager, "getCwd" | "getApiKeyHelp
|
||||
export interface AuthStatusReporterOptions {
|
||||
/** Sends `_auth/status_update`. */
|
||||
push: (status: AuthStatus) => Promise<void>;
|
||||
/** Sends the ACP `auth/status_update`. */
|
||||
pushAcp: (status: AcpAuthStatus) => Promise<void>;
|
||||
logger: ReporterLogger;
|
||||
/** An ACP gateway login is active: the gateway, not the CLI store, is the identity. */
|
||||
gatewayLogin: () => boolean;
|
||||
@@ -72,11 +77,18 @@ export class AuthStatusReporter {
|
||||
/** Same, for the "the CLI probe timed out" warning: a wedged CLI stays wedged
|
||||
* and would otherwise warn once per probe, i.e. once per user prompt. */
|
||||
private loggedProbeTimeout = false;
|
||||
/** Numbers the probe runs in start order. */
|
||||
private probeRuns = 0;
|
||||
/** The latest-started probe run that published its read. */
|
||||
private publishedRun = 0;
|
||||
/** The last `auth/status_update` payload sent, as JSON. */
|
||||
private sentAcp?: string;
|
||||
|
||||
constructor(private readonly options: AuthStatusReporterOptions) {}
|
||||
|
||||
/**
|
||||
* Stores `next` and pushes it to the client.
|
||||
* Stores `next` and pushes it to the client, as `_auth/status_update` and
|
||||
* as the ACP `auth/status_update`.
|
||||
*
|
||||
* A push goes out only when the payload changed. The identity is read on many
|
||||
* occasions — each session create, each guarded turn, the start of each user
|
||||
@@ -103,6 +115,44 @@ export class AuthStatusReporter {
|
||||
.catch((error) => {
|
||||
this.options.logger.error("Failed to send _auth/status_update:", error);
|
||||
});
|
||||
// Compared on its own payload: two identities can map to the same one.
|
||||
const acp = toAcpAuthStatus(next);
|
||||
const sent = JSON.stringify(acp);
|
||||
if (sent === this.sentAcp) {
|
||||
return;
|
||||
}
|
||||
this.sentAcp = sent;
|
||||
void Promise.resolve()
|
||||
.then(() => this.options.pushAcp(acp))
|
||||
.catch((error) => {
|
||||
this.options.logger.error("Failed to send auth/status_update:", error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The `auth/status` answer: the identity the connection holds, which is
|
||||
* also what the last `auth/status_update` said.
|
||||
*
|
||||
* - No identity yet: it waits for a probe, joining the one `initialize`
|
||||
* started, so a query right after `initialize` gets the identity. The
|
||||
* probe is bounded by {@link AUTH_STATUS_PROBE_TIMEOUT_MS}. The answer is
|
||||
* `unknown` when that probe publishes nothing: the CLI failed, printed
|
||||
* no JSON, or timed out, or a login or logout overtook the read.
|
||||
* - An identity is known: it answers at once and starts a new probe that
|
||||
* nothing waits for. A login or logout made outside the client then
|
||||
* arrives as `auth/status_update`.
|
||||
*
|
||||
* The probe is an ordinary one: it runs under the settings of the session
|
||||
* that last started or prompted, pushes a change, and reports its kind to
|
||||
* `onProbe`. Under an ACP gateway login it reads nothing.
|
||||
*/
|
||||
async status(): Promise<AcpAuthStatus> {
|
||||
if (this.current) {
|
||||
void this.probe({ fresh: true });
|
||||
} else {
|
||||
await this.probe();
|
||||
}
|
||||
return toAcpAuthStatus(this.current);
|
||||
}
|
||||
|
||||
/** A login or a logout: any probe already running read the world before it. */
|
||||
@@ -138,8 +188,8 @@ export class AuthStatusReporter {
|
||||
/**
|
||||
* Report the identity behind a session's `AccountInfo`.
|
||||
*
|
||||
* `AccountInfo` is richer than the CLI probe (it is what the live query
|
||||
* actually authenticates with). The v1 core calls it before the
|
||||
* `AccountInfo` describes what the live query authenticates with, but lacks
|
||||
* the CLI's plan tier. The v1 core calls it before the
|
||||
* `--hide-claude-auth` guard may refuse the session or the turn: that flag
|
||||
* blocks the *use* of a subscription, it does not make the state secret, and
|
||||
* a refusal is when the client most needs to know the account it was
|
||||
@@ -175,7 +225,7 @@ export class AuthStatusReporter {
|
||||
}
|
||||
const fromSession = fromAccountInfo(account, apiKeyHelper);
|
||||
if (fromSession) {
|
||||
this.set(fromSession);
|
||||
this.set(mergeAuthStatus(this.current, fromSession, "session"));
|
||||
} else if (!this.loggedUninformativeAccount) {
|
||||
this.loggedUninformativeAccount = true;
|
||||
this.options.logger.log(
|
||||
@@ -210,6 +260,7 @@ export class AuthStatusReporter {
|
||||
// auth-affecting event describes a world that no longer exists. Remember
|
||||
// which one this read belongs to and drop the answer if it moved on.
|
||||
const epoch = this.epoch;
|
||||
const run = ++this.probeRuns;
|
||||
const context = this.settings;
|
||||
const apiKeyHelper = context?.manager.getApiKeyHelper(context.override);
|
||||
// ACP gateway auth bypasses the CLI credential store entirely, so the
|
||||
@@ -276,10 +327,17 @@ export class AuthStatusReporter {
|
||||
logger.log("[authStatus] discarding a probe that predates the latest auth change");
|
||||
return this.current;
|
||||
}
|
||||
if (run < this.publishedRun) {
|
||||
// Probes can overlap (`fresh`) and land out of order. A read that
|
||||
// started before one already published is the older of the two.
|
||||
logger.log("[authStatus] discarding a probe that a newer one overtook");
|
||||
return this.current;
|
||||
}
|
||||
this.publishedRun = run;
|
||||
// The CLI probe can be poorer than the session `AccountInfo` for the very
|
||||
// same login (no organization, say). Keep those extra fields rather than
|
||||
// regressing the payload; a different identity replaces it wholesale.
|
||||
const merged = mergeAuthStatus(this.current, status);
|
||||
const merged = mergeAuthStatus(this.current, status, "cli");
|
||||
this.set(merged);
|
||||
this.options.onProbe?.(status.kind);
|
||||
return merged;
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
* identity the agent process itself uses (Claude subscription, API key,
|
||||
* gateway, external cloud credentials, or nothing).
|
||||
*
|
||||
* Push only. One carrier, connection-scoped (never per session): the
|
||||
* notification `_auth/status_update` → `{authStatus}`. The client never asks;
|
||||
* there is no request method. The empty `agentCapabilities._meta.authStatus`
|
||||
* marker means "this agent pushes its identity", nothing more.
|
||||
* Two carriers of the one identity, both connection-scoped (never per
|
||||
* session):
|
||||
* - the extension: the notification `_auth/status_update` → `{authStatus}`,
|
||||
* advertised by the empty `agentCapabilities._meta.authStatus` marker. It
|
||||
* has no request method;
|
||||
* - the ACP `auth/status` RFD: the `auth/status` request and the
|
||||
* `auth/status_update` notification, advertised as `auth.status`.
|
||||
* {@link toAcpAuthStatus} maps the identity to their payload.
|
||||
*
|
||||
* The agent reads the identity at these moments:
|
||||
* - `initialize`, with an asynchronous CLI probe. Nothing waits for it, and its
|
||||
@@ -20,7 +24,10 @@
|
||||
* login or a logout done in another terminal, including the sign-in a client
|
||||
* performs after a refusal, because the retried prompt is a new prompt.
|
||||
* Nothing waits for it and the push goes out mid-turn if that is when the
|
||||
* read lands.
|
||||
* read lands;
|
||||
* - each `auth/status` request, with an asynchronous CLI probe. The request
|
||||
* waits for it only while no identity is known yet (see
|
||||
* `AuthStatusReporter.status`).
|
||||
*
|
||||
* A probe whose kind differs from a live session's account marks that session
|
||||
* for recreation under `--hide-claude-auth`. The mark is a flag, consumed at
|
||||
@@ -29,18 +36,18 @@
|
||||
* guard decides on the account it reports.
|
||||
*
|
||||
* A read does not always cause a push. The agent sends `_auth/status_update`
|
||||
* only when the payload is different from the last one it sent.
|
||||
* only when its payload is different from the last one it sent, and
|
||||
* `auth/status_update` likewise.
|
||||
*
|
||||
* "Cannot determine" is expressed by silence: an agent that cannot read its own
|
||||
* identity pushes nothing, and the client shows "not reported". A known
|
||||
* logged-out state is a payload of its own (`kind: "none"`), which is what
|
||||
* distinguishes the two.
|
||||
* identity pushes nothing, and `auth/status` answers `unknown`. A known
|
||||
* logged-out state is a payload of its own (`kind: "none"`, `unauthenticated`),
|
||||
* which is what distinguishes the two.
|
||||
*
|
||||
* Reporting only — there is no write path in v1. When the upstream RFD lands,
|
||||
* the same payload moves from `_meta` to first-class fields and this module
|
||||
* is retired.
|
||||
* Reporting only — there is no write path in v1.
|
||||
*/
|
||||
|
||||
import type { AuthAccount, AuthStatusResponse } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AccountInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
/** Change notification, agent → client. Fire and forget: clients that do not
|
||||
@@ -85,6 +92,47 @@ export function authStatusCapability(): AuthStatusCapability {
|
||||
return {};
|
||||
}
|
||||
|
||||
/** The fields of an `auth/status` response and of an `auth/status_update`
|
||||
* notification, which are the same on ACP v1 and v2. */
|
||||
export type AcpAuthStatus = Pick<AuthStatusResponse, "state" | "accounts">;
|
||||
|
||||
/**
|
||||
* Maps the identity to the ACP `auth/status` payload. The identity is the
|
||||
* agent's own login, so `state` does not count a route that the client set
|
||||
* with `providers/set`: a logged-out CLI is `unauthenticated` under one too.
|
||||
*
|
||||
* - No identity read yet (`undefined`): `unknown`, no accounts.
|
||||
* - `kind: "none"`: `unauthenticated`, no accounts.
|
||||
* - Any other kind: `authenticated`, with the identity as the one account,
|
||||
* marked `active`. `detail` is the account's `description`, e.g. label
|
||||
* "Anthropic API key" with description "ANTHROPIC_API_KEY".
|
||||
* `_meta.claudeCode` carries the `kind` and `plan`, and the `detail` again
|
||||
* for clients that read it there.
|
||||
*
|
||||
* `--hide-claude-auth` does not change the payload: it reports the login the
|
||||
* CLI holds, also when the subscription guard refuses to use it.
|
||||
*/
|
||||
export function toAcpAuthStatus(status: AuthStatus | undefined): AcpAuthStatus {
|
||||
if (!status) {
|
||||
return { state: "unknown" };
|
||||
}
|
||||
if (status.kind === "none") {
|
||||
return { state: "unauthenticated" };
|
||||
}
|
||||
const account: AuthAccount = { label: status.label, active: true };
|
||||
if (status.detail) account.description = status.detail;
|
||||
if (status.account?.email) account.email = status.account.email;
|
||||
if (status.account?.organization) account.organization = status.account.organization;
|
||||
account._meta = {
|
||||
claudeCode: {
|
||||
kind: status.kind,
|
||||
...(status.detail !== undefined ? { detail: status.detail } : {}),
|
||||
...(status.account?.plan !== undefined ? { plan: status.account.plan } : {}),
|
||||
},
|
||||
};
|
||||
return { state: "authenticated", accounts: [account] };
|
||||
}
|
||||
|
||||
/** Shape of `claude auth status --json`. Absent fields are omitted, not null,
|
||||
* and the logged-out case exits 1 while still printing valid JSON. */
|
||||
export type CliAuthStatus = {
|
||||
@@ -96,6 +144,8 @@ export type CliAuthStatus = {
|
||||
orgId?: string;
|
||||
orgName?: string;
|
||||
subscriptionType?: string;
|
||||
/** CLI-only tier, e.g. "max_5x" or "max_20x"; not in SDK AccountInfo. */
|
||||
planTier?: string;
|
||||
};
|
||||
|
||||
const NOT_LOGGED_IN: AuthStatus = {
|
||||
@@ -222,6 +272,7 @@ export function fromCliStatus(stdout: string, apiKeyHelper?: string): AuthStatus
|
||||
return mapAuthFields({
|
||||
apiProvider: status.apiProvider,
|
||||
subscriptionType: status.subscriptionType,
|
||||
planTier: status.planTier,
|
||||
apiKeySource: status.apiKeySource,
|
||||
email: status.email,
|
||||
organization: status.orgName,
|
||||
@@ -242,6 +293,7 @@ export function fromCliStatus(stdout: string, apiKeyHelper?: string): AuthStatus
|
||||
function mapAuthFields(fields: {
|
||||
apiProvider?: string;
|
||||
subscriptionType?: string;
|
||||
planTier?: string;
|
||||
apiKeySource?: string;
|
||||
email?: string;
|
||||
organization?: string;
|
||||
@@ -281,7 +333,7 @@ function mapAuthFields(fields: {
|
||||
if (organization) account.organization = organization;
|
||||
return {
|
||||
kind: "account",
|
||||
label: planLabel(subscriptionType),
|
||||
label: planLabel(subscriptionType, fields.planTier),
|
||||
// No detail: the client falls back to `account.email` for line 2.
|
||||
account,
|
||||
};
|
||||
@@ -289,12 +341,14 @@ function mapAuthFields(fields: {
|
||||
return notLoggedInAuthStatus();
|
||||
}
|
||||
|
||||
/** Names the plan with exactly one "Claude". A newer CLI already reports
|
||||
* "Claude Max", an older one reports "max", and both must read "Claude Max".
|
||||
* Each word is title-cased; a word that already carries capitals is kept. */
|
||||
function planLabel(plan: string): string {
|
||||
/** Names the plan with exactly one "Claude". Without a tier, "Claude Max"
|
||||
* from a newer CLI and "max" from an older one both read "Claude Max".
|
||||
* Each word is title-cased; a word that already carries capitals is kept.
|
||||
* The CLI's max_20x tier identifies Ultra; other tiers keep the plan label. */
|
||||
function planLabel(plan: string, planTier?: string): string {
|
||||
const titled = plan.replace(/\S+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1));
|
||||
return /^claude(\s|$)/i.test(plan) ? titled : `Claude ${titled}`;
|
||||
const label = /^claude(\s|$)/i.test(plan) ? titled : `Claude ${titled}`;
|
||||
return label.toLowerCase() === "claude max" && planTier === "max_20x" ? "Claude Ultra" : label;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,8 +376,14 @@ export function sameIdentity(a: AuthStatus, b: AuthStatus): boolean {
|
||||
* organization the CLI probe omits. So when the read describes the same
|
||||
* identity, its values win field by field but the fields it lacks are kept;
|
||||
* when it describes a different identity, it replaces the old one wholesale.
|
||||
* Session reads lack the CLI's plan tier, so keep the label for the same plan.
|
||||
* A CLI read always replaces the label, including when its tier is absent.
|
||||
*/
|
||||
export function mergeAuthStatus(previous: AuthStatus | undefined, next: AuthStatus): AuthStatus {
|
||||
export function mergeAuthStatus(
|
||||
previous: AuthStatus | undefined,
|
||||
next: AuthStatus,
|
||||
source: "cli" | "session",
|
||||
): AuthStatus {
|
||||
if (!previous || !sameIdentity(previous, next)) {
|
||||
return next;
|
||||
}
|
||||
@@ -334,6 +394,14 @@ export function mergeAuthStatus(previous: AuthStatus | undefined, next: AuthStat
|
||||
if (previous.account || next.account) {
|
||||
merged.account = { ...previous.account, ...next.account };
|
||||
}
|
||||
if (
|
||||
source === "session" &&
|
||||
previous.account?.plan &&
|
||||
next.account?.plan &&
|
||||
planLabel(previous.account.plan).toLowerCase() === planLabel(next.account.plan).toLowerCase()
|
||||
) {
|
||||
merged.label = previous.label;
|
||||
}
|
||||
if (merged.vendor === undefined && previous.vendor !== undefined) {
|
||||
merged.vendor = previous.vendor;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ import {
|
||||
import {
|
||||
forkSession as forkClaudeSession,
|
||||
getSessionMessages,
|
||||
importSessionToStore,
|
||||
type SessionStore,
|
||||
type SessionStoreEntry,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import { createHash } from "node:crypto";
|
||||
import { loadFullSessionHistory } from "./resumed-session.js";
|
||||
import { assistantMessageText } from "./session-failure-extension.js";
|
||||
import { SessionTiming } from "./session-timing.js";
|
||||
|
||||
@@ -69,19 +68,6 @@ function forkPointMessageIdCandidates(messageId: string): string[] {
|
||||
return protocolMessageId === messageId ? [messageId] : [messageId, protocolMessageId];
|
||||
}
|
||||
|
||||
async function loadFullSessionHistory(sessionId: string): Promise<SessionStoreEntry[]> {
|
||||
// getSessionMessages returns only the active parentUuid chain. The import keeps inactive branches that AIR can still reference.
|
||||
const entries: SessionStoreEntry[] = [];
|
||||
const store: SessionStore = {
|
||||
append: async (_key, batch) => {
|
||||
entries.push(...batch);
|
||||
},
|
||||
load: async () => null,
|
||||
};
|
||||
await importSessionToStore(sessionId, store, { includeSubagents: false });
|
||||
return entries;
|
||||
}
|
||||
|
||||
function assistantGroups(
|
||||
entries: SessionStoreEntry[],
|
||||
messageIdForGrouping: ForkSessionDependencies["messageIdForGrouping"],
|
||||
@@ -235,6 +221,7 @@ async function forkTranscript(
|
||||
.find((message) => dependencies.messageIdForGrouping(message) === candidateId)?.uuid,
|
||||
)
|
||||
.find(Boolean);
|
||||
// getSessionMessages returns only the active parentUuid chain. The full history keeps inactive branches that AIR can still reference.
|
||||
const fullHistoryUuid = messageUuid
|
||||
? undefined
|
||||
: resolveFromFullHistory(
|
||||
|
||||
@@ -35,8 +35,10 @@
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { AccountInfo, Query } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
export const HIDE_CLAUDE_AUTH_FLAG = "--hide-claude-auth";
|
||||
|
||||
export function shouldHideClaudeAuth(): boolean {
|
||||
return process.argv.includes("--hide-claude-auth");
|
||||
return process.argv.includes(HIDE_CLAUDE_AUTH_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,8 @@ export type IdleCompactionHost = {
|
||||
isBusy(): boolean;
|
||||
/** Context size after the last main-thread request, or undefined if unknown. */
|
||||
contextTokens(): number | undefined;
|
||||
/** Current merged Claude Code settings. */
|
||||
/** Current Claude Code settings, the client's `settings` option included
|
||||
* (`SettingsManager.getCompactionSettings`). */
|
||||
settings(): {
|
||||
idleCompaction?: boolean;
|
||||
autoCompactEnabled?: boolean;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { cliArgs } from "./agent-command.js";
|
||||
import { claudeCliPath } from "./claude-cli.js";
|
||||
import { applyManagedPolicyEnv } from "./managed-policy.js";
|
||||
import { acpProtocolsFromEnv, serveAcp } from "./serve.js";
|
||||
@@ -12,7 +13,7 @@ import { join } from "node:path";
|
||||
// handler below. Our version flag only applies when not delegating.
|
||||
if (process.argv.includes("--cli")) {
|
||||
const { spawn } = await import("node:child_process");
|
||||
const args = process.argv.slice(2).filter((arg) => arg !== "--cli");
|
||||
const args = cliArgs();
|
||||
const child = spawn(await claudeCliPath(), args, { stdio: "inherit" });
|
||||
|
||||
const signals =
|
||||
|
||||
63
src/initial-config-options.ts
Normal file
63
src/initial-config-options.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* The unstable "Session options" RFD, as both cores serve it: `session/options`
|
||||
* answers with what a session would start with, and a setup request's
|
||||
* `configOptions` set the initial values of the session's config options.
|
||||
*/
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { Logger } from "./logger.js";
|
||||
|
||||
/**
|
||||
* What a core advertises as `options` in its session capabilities:
|
||||
* `session/options`, and `configOptions` on its setup requests.
|
||||
*/
|
||||
export const SESSION_OPTIONS_CAPABILITY = {};
|
||||
|
||||
const INVALID_PARAMS = RequestError.invalidParams().code;
|
||||
|
||||
/**
|
||||
* The entries of a setup request's `configOptions`. Omitted and `null` are no
|
||||
* entries. Two entries with the same `configId` fail with `-32602`. Call it
|
||||
* before the session starts, so a refused request starts none.
|
||||
*/
|
||||
export function initialConfigOptions<E extends { configId: string }>(
|
||||
entries: readonly E[] | null | undefined,
|
||||
): readonly E[] {
|
||||
const seen = new Set<string>();
|
||||
for (const { configId } of entries ?? []) {
|
||||
if (seen.has(configId)) {
|
||||
throw RequestError.invalidParams(
|
||||
{ configId },
|
||||
`\`configOptions\` sets ${configId} more than once. Send one entry for each config option.`,
|
||||
);
|
||||
}
|
||||
seen.add(configId);
|
||||
}
|
||||
return entries ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets each entry with `set`, in list order. `set` checks an entry against the
|
||||
* options as the entries before it left them, so a model entry decides which
|
||||
* effort levels a later entry can pick.
|
||||
*
|
||||
* An entry that `set` refuses is skipped, and the entries after it still
|
||||
* apply. The option keeps its value, which the setup response reports:
|
||||
*
|
||||
* - `-32602`: an unknown `configId`, a value of another type than the
|
||||
* option's, or a value that the option does not offer.
|
||||
* - Any other failure, such as a model that Claude Code refuses. It is logged.
|
||||
*/
|
||||
export async function applyInitialConfigOptions<E extends { configId: string }>(
|
||||
entries: readonly E[],
|
||||
set: (entry: E) => Promise<unknown>,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
await set(entry);
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError && error.code === INVALID_PARAMS) continue;
|
||||
logger.error(`Could not set the initial value of config option ${entry.configId}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,22 @@ function hasMarkerTag(text: string, tag: string): boolean {
|
||||
return markerTagText(text, tag) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content of a user message is only the output of a local
|
||||
* command, such as the `<local-command-stdout>Set model to …</local-command-stdout>`
|
||||
* that Claude Code echoes when the model changes.
|
||||
*/
|
||||
export function isLocalCommandOutput(content: unknown): boolean {
|
||||
if (typeof content !== "string") return false;
|
||||
if (
|
||||
!hasMarkerTag(content, "local-command-stdout") &&
|
||||
!hasMarkerTag(content, "local-command-stderr")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return stripMarkerTags(content).trim() === "";
|
||||
}
|
||||
|
||||
function commandInvocationFromMarkerOnlyText(text: string): string | null {
|
||||
if (hasMarkerTag(text, "local-command-stdout") || hasMarkerTag(text, "local-command-stderr")) {
|
||||
return null;
|
||||
|
||||
13
src/paths.ts
13
src/paths.ts
@@ -5,3 +5,16 @@ import * as path from "node:path";
|
||||
export function claudeConfigDir(): string {
|
||||
return process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
||||
}
|
||||
|
||||
/** `<config>/projects`, normalized like the SDK does. */
|
||||
export function projectsRoot(): string {
|
||||
return path.join(claudeConfigDir(), "projects").normalize("NFC");
|
||||
}
|
||||
|
||||
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
|
||||
* transcript, and only such ids are safe to use as a file name. */
|
||||
export function isSessionId(value: unknown): value is string {
|
||||
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { getSessionMessages, type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
getSessionMessages,
|
||||
importSessionToStore,
|
||||
type SessionMessage,
|
||||
type SessionStore,
|
||||
type SessionStoreEntry,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import { access, open, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { isCompactSummaryMessage } from "./context-compaction.js";
|
||||
import { claudeConfigDir } from "./paths.js";
|
||||
import { SessionTiming } from "./session-timing.js";
|
||||
|
||||
@@ -68,6 +75,97 @@ export async function readResumedSession(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the messages of a session from its first prompt on, given the
|
||||
* `getSessionMessages` chain of the session.
|
||||
*
|
||||
* `getSessionMessages` follows `parentUuid` links. Claude Code writes each
|
||||
* `compact_boundary` with `parentUuid: null`, so after a compaction the chain
|
||||
* starts at its summary. The boundary keeps the last message before it in
|
||||
* `logicalParentUuid`. For a chain that starts at a summary, this links each
|
||||
* boundary to that message and reads the transcript through
|
||||
* `getSessionMessages` again. For a session compacted twice, the result has
|
||||
* both summaries and the messages before, between and after them. The result
|
||||
* ends at the last message of `messages`. Any other chain is returned as it
|
||||
* is, and so is the chain when the read fails or when its result does not
|
||||
* reach that last message (e.g. the transcript changed between the reads).
|
||||
*
|
||||
* `dir` is the project directory of the first read: both reads then resolve
|
||||
* the same transcript file. Without it, all project directories are searched.
|
||||
*
|
||||
* The preserved-message metadata of each boundary is dropped. With it,
|
||||
* `getSessionMessages` moves the messages that a compaction kept to after the
|
||||
* summary, and the relinked boundary then leads from them back to the summary:
|
||||
* the chain loops and ends there. Without it, the kept messages stay where
|
||||
* they were said, before the boundary.
|
||||
*/
|
||||
export async function readHistoryAcrossCompactions(
|
||||
sessionId: string,
|
||||
messages: SessionMessage[],
|
||||
logger?: ResumeLogger,
|
||||
dir?: string,
|
||||
): Promise<SessionMessage[]> {
|
||||
if (!isCompactSummaryMessage(messages[0])) return messages;
|
||||
try {
|
||||
const entries = (await loadFullSessionHistory(sessionId, dir)).map(linkCompactBoundary);
|
||||
const history = await getSessionMessages(sessionId, {
|
||||
dir,
|
||||
sessionStore: { append: async () => {}, load: async () => entries },
|
||||
});
|
||||
const leaf = messages.at(-1)?.uuid;
|
||||
const end = history.findIndex((message) => message.uuid === leaf);
|
||||
if (end < 0) {
|
||||
logger?.error(
|
||||
`The history before the last compaction of session ${sessionId} does not reach message ${leaf}: replaying the messages after it`,
|
||||
);
|
||||
return messages;
|
||||
}
|
||||
return history.slice(0, end + 1);
|
||||
} catch (error) {
|
||||
logger?.error(
|
||||
`Failed to read the history before the last compaction of session ${sessionId}:`,
|
||||
error,
|
||||
);
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
/** Every record of a session's transcript, including the inactive branches
|
||||
* and the history before a compaction that `getSessionMessages` leaves out.
|
||||
* Without `dir`, all project directories are searched. */
|
||||
export async function loadFullSessionHistory(
|
||||
sessionId: string,
|
||||
dir?: string,
|
||||
): Promise<SessionStoreEntry[]> {
|
||||
const entries: SessionStoreEntry[] = [];
|
||||
const store: SessionStore = {
|
||||
append: async (_key, batch) => {
|
||||
entries.push(...batch);
|
||||
},
|
||||
load: async () => null,
|
||||
};
|
||||
await importSessionToStore(sessionId, store, { dir, includeSubagents: false });
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** The record unchanged, or a compaction boundary linked to the message before it. */
|
||||
function linkCompactBoundary(entry: SessionStoreEntry): SessionStoreEntry {
|
||||
if (
|
||||
entry.type !== "system" ||
|
||||
entry.subtype !== "compact_boundary" ||
|
||||
typeof entry.logicalParentUuid !== "string"
|
||||
) {
|
||||
return entry;
|
||||
}
|
||||
const compactMetadata: Record<string, unknown> =
|
||||
typeof entry.compactMetadata === "object" && entry.compactMetadata !== null
|
||||
? { ...entry.compactMetadata }
|
||||
: {};
|
||||
delete compactMetadata.preservedSegment;
|
||||
delete compactMetadata.preservedMessages;
|
||||
return { ...entry, parentUuid: entry.logicalParentUuid, compactMetadata };
|
||||
}
|
||||
|
||||
/** What a resume restores from the end of the local transcript. */
|
||||
export type ResumedTail = {
|
||||
model?: string;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
import { createReadStream } from "node:fs";
|
||||
import { createInterface } from "node:readline";
|
||||
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../../task-notifications.js";
|
||||
import { TASK_NOTIFICATION_TAG, taskNotificationsOf } from "../task-notifications.js";
|
||||
|
||||
export type BackgroundTasks = {
|
||||
/** The ids of the tasks that started and did not end, in start order. */
|
||||
@@ -49,7 +49,8 @@ import {
|
||||
type LiveSessionRegistry,
|
||||
type LiveSnapshot,
|
||||
} from "./live-registry.js";
|
||||
import { isExactProjectDir, isSessionId, projectDirMatches, projectsRoot } from "./project-dirs.js";
|
||||
import { isSessionId, projectsRoot } from "../paths.js";
|
||||
import { isExactProjectDir, projectDirMatches } from "./project-dirs.js";
|
||||
import { type IndexRow, type SessionIndex, type TranscriptCandidate } from "./session-index.js";
|
||||
import { changeSignature, sessionInfoOf } from "./session-info.js";
|
||||
|
||||
@@ -23,7 +23,7 @@ import { execFile } from "node:child_process";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { claudeConfigDir } from "../../paths.js";
|
||||
import { claudeConfigDir } from "../paths.js";
|
||||
import { errorCode } from "./project-dirs.js";
|
||||
|
||||
const SNAPSHOT_TTL_MS = 5_000;
|
||||
@@ -14,24 +14,10 @@
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { claudeConfigDir } from "../../paths.js";
|
||||
|
||||
/** The SDK cuts an encoded path at this length and appends a hash. */
|
||||
const MAX_SANITIZED_LENGTH = 200;
|
||||
|
||||
const SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
/** Whether `value` is a Claude session id (a UUID). Only such ids name a
|
||||
* transcript, and only such ids are safe to use as a file name. */
|
||||
export function isSessionId(value: unknown): value is string {
|
||||
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
|
||||
}
|
||||
|
||||
/** `<config>/projects`, normalized like the SDK does. */
|
||||
export function projectsRoot(): string {
|
||||
return path.join(claudeConfigDir(), "projects").normalize("NFC");
|
||||
}
|
||||
|
||||
function sanitize(value: string): string {
|
||||
return value.replace(/[^a-zA-Z0-9]/g, "-");
|
||||
}
|
||||
115
src/session-index/rename.ts
Normal file
115
src/session-index/rename.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Client renames of a session: `session/set_title` (v1 and v2) and AIR's
|
||||
* `_session/rename` (v1). Both cores call {@link renameSession}, which stores
|
||||
* the title in every transcript of the session (see
|
||||
* {@link SessionIndexService.retitle}) and, for a session loaded on the
|
||||
* connection, makes it the client title that no generated title replaces.
|
||||
*/
|
||||
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { Query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { SessionTitles, TitledSession } from "../session-titles.js";
|
||||
import { storedTitle } from "./archive-title.js";
|
||||
import {
|
||||
renameTo,
|
||||
type RenameSessionRequest,
|
||||
type RetitleOptions,
|
||||
type SessionIndexService,
|
||||
type TitleChange,
|
||||
} from "./service.js";
|
||||
|
||||
/** A session loaded on the connection, and its title state. */
|
||||
export interface RetitledSession {
|
||||
readonly session: TitledSession;
|
||||
readonly titles: Pick<
|
||||
SessionTitles,
|
||||
"setExplicitTitle" | "storedTitle" | "rememberStoredTitle" | "shownTitle"
|
||||
>;
|
||||
}
|
||||
|
||||
type RenamingQuery = Query & {
|
||||
renameSession?: (title: string, sessionId?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Names a session; no generated title replaces it. An archived session stays
|
||||
* archived. `loaded` is the session loaded on this connection, if any;
|
||||
* `clientTitle` turns a stored title into the one the client shows (an AIR
|
||||
* client's drops the archive prefix). Returns that title, which the loaded
|
||||
* session also publishes as a `session_info_update`.
|
||||
*
|
||||
* A session that is not loaded and has no transcript is unknown (`-32002`).
|
||||
*/
|
||||
export async function renameSession(
|
||||
index: SessionIndexService,
|
||||
request: RenameSessionRequest,
|
||||
loaded: RetitledSession | undefined,
|
||||
clientTitle: (stored: string) => string = (stored) => stored,
|
||||
): Promise<string> {
|
||||
const { sessionId } = request;
|
||||
// As the CLI keeps it, and never with the archive prefix: a rename does
|
||||
// not change the archive state.
|
||||
const title = storedTitle(request.title, false, sessionId);
|
||||
// The title as stored: an archived one is cut with its prefix.
|
||||
const shown = (stored: string | undefined) => clientTitle(stored ?? title);
|
||||
const stored = await retitleSession(index, sessionId, renameTo(title), shown, loaded, {
|
||||
mayBeUnwritten: true,
|
||||
sidecar: "always",
|
||||
});
|
||||
return shown(stored);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retitles a session (see {@link SessionIndexService.retitle}) and returns
|
||||
* the title stored. A session that runs here is titled through its CLI
|
||||
* (`rename_session`). A loaded session's title state takes the change as a
|
||||
* client title, so no generated title replaces it; `publish` is the title it
|
||||
* then publishes.
|
||||
*/
|
||||
export async function retitleSession(
|
||||
index: SessionIndexService,
|
||||
sessionId: string,
|
||||
change: TitleChange,
|
||||
publish: Parameters<SessionTitles["setExplicitTitle"]>[0],
|
||||
loaded: RetitledSession | undefined,
|
||||
options: Pick<RetitleOptions, "mayBeUnwritten" | "sidecar">,
|
||||
): Promise<string | undefined> {
|
||||
let stored: string | undefined;
|
||||
// Decided once a title generation in flight has ended: the query may
|
||||
// have closed meanwhile.
|
||||
const persist = async () => {
|
||||
const session = loaded?.session;
|
||||
const query = session?.query as RenamingQuery | undefined;
|
||||
// A closed session's CLI is gone here: only a running query titles it.
|
||||
const running = session !== undefined && !session.queryClosed;
|
||||
if (running && typeof query?.renameSession !== "function") {
|
||||
// Its CLI holds the title and would write it back over ours.
|
||||
throw RequestError.invalidRequest(
|
||||
{ sessionId },
|
||||
"The Claude Code CLI of this session cannot change its title while it runs.",
|
||||
);
|
||||
}
|
||||
const live = running
|
||||
? {
|
||||
cwd: session.cwd,
|
||||
rename: (title: string) => query!.renameSession!(title, sessionId),
|
||||
stored: () => loaded!.titles.storedTitle,
|
||||
remember: (title: string) => loaded!.titles.rememberStoredTitle(title),
|
||||
shown: loaded!.titles.shownTitle,
|
||||
}
|
||||
: undefined;
|
||||
stored = await index.retitle(sessionId, change, {
|
||||
...options,
|
||||
// Without its CLI, a session needs its transcript.
|
||||
mayBeUnwritten: live !== undefined && options.mayBeUnwritten,
|
||||
live,
|
||||
});
|
||||
return stored;
|
||||
};
|
||||
if (loaded) {
|
||||
await loaded.titles.setExplicitTitle(publish, persist);
|
||||
} else {
|
||||
await persist();
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
type SessionInfo,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import { deleteSession as sdkDeleteSession } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { airExtensionMeta } from "../../air-extension.js";
|
||||
import { sanitizeTitle } from "../../session-titles.js";
|
||||
import { airExtensionMeta } from "../air-extension.js";
|
||||
import { sanitizeTitle } from "../session-titles.js";
|
||||
import type { OwnSessionState } from "./activity.js";
|
||||
import {
|
||||
effectiveTitle,
|
||||
@@ -36,11 +36,11 @@ import {
|
||||
type ListSubscriptionDeps,
|
||||
} from "./list-subscriptions.js";
|
||||
import { LiveSessionRegistry } from "./live-registry.js";
|
||||
import { isSessionId } from "../paths.js";
|
||||
import {
|
||||
canonicalPath,
|
||||
exists,
|
||||
isExactProjectDir,
|
||||
isSessionId,
|
||||
projectDirMatches,
|
||||
sameProjectPath,
|
||||
} from "./project-dirs.js";
|
||||
@@ -466,6 +466,18 @@ export type SessionIndexDeps = {
|
||||
logError: (message: string, error: unknown) => void;
|
||||
};
|
||||
|
||||
/** The transcript reads and writes of the session index, without its list
|
||||
* or watcher: what a client without `sessionIndex` uses for
|
||||
* `session/set_title`, AIR's archive on delete, and delete's ordering
|
||||
* after them (see {@link SessionIndexService.exclusive}). */
|
||||
export function transcriptWriter(logger: {
|
||||
error: (message: string, error: unknown) => void;
|
||||
}): SessionIndexService {
|
||||
return new SessionIndexService({
|
||||
logError: (message, error) => logger.error(`[session-index] ${message}:`, error),
|
||||
});
|
||||
}
|
||||
|
||||
export class SessionIndexService {
|
||||
readonly index: SessionIndex;
|
||||
readonly registry: LiveSessionRegistry;
|
||||
@@ -541,8 +553,10 @@ export class SessionIndexService {
|
||||
};
|
||||
}
|
||||
|
||||
/** Runs `mutation` after the previous mutation of the session ended. */
|
||||
private exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
|
||||
/** Runs `mutation` after the previous mutation of the session ended, such
|
||||
* as a delete that does not go through {@link delete}, so that a retitle
|
||||
* cannot append to a transcript the delete is removing. */
|
||||
exclusive<T>(sessionId: string, mutation: () => Promise<T>): Promise<T> {
|
||||
const previous = this.mutations.get(sessionId) ?? Promise.resolve();
|
||||
const next = previous.catch(() => undefined).then(mutation);
|
||||
const settled = next.catch(() => undefined);
|
||||
@@ -21,20 +21,19 @@
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { sanitizeTitle } from "../../session-titles.js";
|
||||
import { sanitizeTitle } from "../session-titles.js";
|
||||
import { effectiveTitle, visibleTitle } from "./archive-title.js";
|
||||
import { isSessionId, projectsRoot } from "../paths.js";
|
||||
import {
|
||||
canonicalPath,
|
||||
encodeProjectPath,
|
||||
errorCode,
|
||||
exists,
|
||||
isExactProjectDir,
|
||||
isSessionId,
|
||||
normalizePath,
|
||||
pathAndAncestors,
|
||||
projectDirMatches,
|
||||
projectDirsOf,
|
||||
projectsRoot,
|
||||
sameProjectPath,
|
||||
} from "./project-dirs.js";
|
||||
import {
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
|
||||
import { withAirMeta } from "../../air-extension.js";
|
||||
import { withAirMeta } from "../air-extension.js";
|
||||
import { deriveActivity, selectCost, type OwnSessionState } from "./activity.js";
|
||||
import type { LiveRecord } from "./live-registry.js";
|
||||
import type { IndexRow } from "./session-index.js";
|
||||
@@ -285,7 +285,16 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async selectMode(sessionId: string, modeId: string): Promise<PermissionMode> {
|
||||
/**
|
||||
* Sets the mode on the query. `auto` on a model without Auto runs as its
|
||||
* fallback, with the warning. With `deferWarning`, the warning waits for
|
||||
* the next prompt (see {@link deferFallbackWarning}).
|
||||
*/
|
||||
async selectMode(
|
||||
sessionId: string,
|
||||
modeId: string,
|
||||
deferWarning = false,
|
||||
): Promise<PermissionMode> {
|
||||
const session = this.requireOpenSession(sessionId);
|
||||
const requestedMode = this.parseMode(modeId);
|
||||
if (!this.isAvailable(session.modes.availableModes, requestedMode)) {
|
||||
@@ -308,7 +317,8 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
}
|
||||
|
||||
if (effectiveMode !== requestedMode) {
|
||||
await this.publishFallbackWarning(sessionId, session);
|
||||
if (deferWarning) this.deferFallbackWarning(session);
|
||||
else await this.publishFallbackWarning(sessionId, session);
|
||||
}
|
||||
return effectiveMode;
|
||||
}
|
||||
@@ -352,6 +362,16 @@ export class SessionModeManager<S extends SessionMode> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the Auto fallback's warning for the next prompt
|
||||
* (`autoModeFallbackWarningPending`), as a session's start does. For a
|
||||
* fallback during session setup: the client cannot place an update of a
|
||||
* session before the setup response names it.
|
||||
*/
|
||||
deferFallbackWarning(session: SessionMode): void {
|
||||
if (!session.autoModeFallbackWarningShown) session.autoModeFallbackWarningPending = true;
|
||||
}
|
||||
|
||||
async publishFallbackState(sessionId: string, session: SessionMode): Promise<void> {
|
||||
await this.publishCurrent(sessionId, AUTO_MODE_FALLBACK);
|
||||
await this.publishFallbackWarning(sessionId, session);
|
||||
|
||||
68
src/session-status.ts
Normal file
68
src/session-status.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* `session/status` (the unstable "Session status" RFD) of a session that the
|
||||
* connection does not hold: `saved` or unknown. Each core answers `open`
|
||||
* itself for the sessions it holds.
|
||||
*/
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
import { isSessionId, projectsRoot } from "./paths.js";
|
||||
|
||||
/** What a core advertises as `status` in its session capabilities. */
|
||||
export const SESSION_STATUS_CAPABILITY = {};
|
||||
|
||||
/**
|
||||
* `saved` when a project directory holds a transcript file of `sessionId`
|
||||
* that is not empty. Its content is not read, so a transcript without a
|
||||
* conversation is `saved` too, and its resume fails with `-32002`. Fails with
|
||||
* `-32002` and `data.sessionId` otherwise:
|
||||
*
|
||||
* - an id that is not a UUID, such as a subagent's session id;
|
||||
* - an id without a transcript: one never used, a session never prompted
|
||||
* (Claude Code writes the transcript at the first prompt), and a session
|
||||
* whose transcripts were removed.
|
||||
*
|
||||
* The request names no `cwd`, so a transcript of any project counts: a
|
||||
* resume with another `cwd` can still fail with `-32002`.
|
||||
*
|
||||
* It lists the project directories and stats the one file name in each. No
|
||||
* query starts and no session is loaded. A directory or file that cannot be
|
||||
* read for another reason than being absent fails the request: it does not
|
||||
* make the session unknown.
|
||||
*/
|
||||
export async function savedSessionStatus(sessionId: string): Promise<{ status: "saved" }> {
|
||||
if (!isSessionId(sessionId) || !(await hasTranscript(sessionId))) {
|
||||
throw RequestError.sessionNotFound(sessionId);
|
||||
}
|
||||
return { status: "saved" };
|
||||
}
|
||||
|
||||
async function hasTranscript(sessionId: string): Promise<boolean> {
|
||||
const root = projectsRoot();
|
||||
let projects: string[];
|
||||
try {
|
||||
projects = await fs.readdir(root);
|
||||
} catch (error) {
|
||||
// No session was ever stored.
|
||||
if (isAbsent(error)) return false;
|
||||
throw error;
|
||||
}
|
||||
const found = await Promise.all(
|
||||
projects.map((project) =>
|
||||
fs.stat(path.join(root, project, `${sessionId}.jsonl`)).then(
|
||||
(stats) => stats.isFile() && stats.size > 0,
|
||||
(error: unknown) => {
|
||||
// No such transcript, or `project` is a file.
|
||||
if (isAbsent(error)) return false;
|
||||
throw error;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
return found.includes(true);
|
||||
}
|
||||
|
||||
function isAbsent(error: unknown): boolean {
|
||||
const code = (error as { code?: unknown }).code;
|
||||
return code === "ENOENT" || code === "ENOTDIR";
|
||||
}
|
||||
@@ -37,6 +37,15 @@ export interface SettingsManagerOptions {
|
||||
logger?: { log: (...args: any[]) => void; error: (...args: any[]) => void };
|
||||
}
|
||||
|
||||
type ModelSelectionSettings = Pick<Settings, "availableModels" | "modelOverrides">;
|
||||
|
||||
/** The settings that decide whether Claude Code compacts (see `idle-compaction.ts`). */
|
||||
export type CompactionSettings = {
|
||||
idleCompaction?: boolean;
|
||||
autoCompactEnabled?: boolean;
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages Claude Code settings using the SDK's `resolveSettings` merge engine
|
||||
* so the values we see match what `query()` would observe.
|
||||
@@ -50,7 +59,9 @@ export class SettingsManager {
|
||||
private cwd: string;
|
||||
private effective: Settings = {};
|
||||
private managedDeniedModels: string[] = [];
|
||||
private managedModelSettings: ModelSelectionSettings = {};
|
||||
private managedApiKeyHelper?: string;
|
||||
private managedCompactionSettings: CompactionSettings = {};
|
||||
private watchers: fs.FSWatcher[] = [];
|
||||
private onChange?: () => void;
|
||||
private logger: { log: (...args: any[]) => void; error: (...args: any[]) => void };
|
||||
@@ -112,15 +123,36 @@ export class SettingsManager {
|
||||
this.managedDeniedModels = resolved.sources
|
||||
.filter((entry) => entry.source === "managed")
|
||||
.flatMap((entry) => entry.settings.deniedModels ?? []);
|
||||
this.managedModelSettings = Object.assign(
|
||||
{},
|
||||
...resolved.sources
|
||||
.filter((entry) => entry.source === "managed")
|
||||
.map((entry) => ({
|
||||
availableModels: entry.settings.availableModels,
|
||||
modelOverrides: entry.settings.modelOverrides,
|
||||
})),
|
||||
);
|
||||
this.managedApiKeyHelper =
|
||||
resolved.provenance.apiKeyHelper?.source === "managed"
|
||||
? resolved.effective.apiKeyHelper
|
||||
: undefined;
|
||||
const managed = resolved.sources
|
||||
.filter((entry) => entry.source === "managed")
|
||||
.map((entry) => entry.settings);
|
||||
this.managedCompactionSettings = {
|
||||
idleCompaction: managed.findLast((tier) => tier.idleCompaction !== undefined)
|
||||
?.idleCompaction,
|
||||
autoCompactEnabled: managed.findLast((tier) => tier.autoCompactEnabled !== undefined)
|
||||
?.autoCompactEnabled,
|
||||
env: Object.assign({}, ...managed.map((tier) => tier.env)),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to resolve settings:", error);
|
||||
this.effective = {};
|
||||
this.managedDeniedModels = [];
|
||||
this.managedModelSettings = {};
|
||||
this.managedApiKeyHelper = undefined;
|
||||
this.managedCompactionSettings = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +225,51 @@ export class SettingsManager {
|
||||
);
|
||||
}
|
||||
|
||||
/** Model selection settings including the inline SDK settings tier. */
|
||||
getModelSelectionSettings(override?: Settings): ModelSelectionSettings {
|
||||
// The SDK unions non-managed allowlists, but a managed list owns the
|
||||
// whole allowlist. Keep that policy when adding
|
||||
// models the SDK did not include in its initialization result.
|
||||
const lists = [this.effective.availableModels, override?.availableModels].filter(
|
||||
(list): list is string[] => Array.isArray(list),
|
||||
);
|
||||
const managed = this.managedModelSettings;
|
||||
return {
|
||||
availableModels:
|
||||
managed.availableModels ?? (lists.length > 0 ? [...new Set(lists.flat())] : undefined),
|
||||
modelOverrides: {
|
||||
...this.effective.modelOverrides,
|
||||
...override?.modelOverrides,
|
||||
...managed.modelOverrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `idleCompaction`, `autoCompactEnabled` and `env` as the CLI resolves them:
|
||||
* managed settings outrank the `--settings` override, which outranks the
|
||||
* user/project/local files. `env` merges entry by entry in the same order. A
|
||||
* key whose value is `undefined` in the override does not hide the files' value.
|
||||
*/
|
||||
getCompactionSettings(settingsOverride?: Settings): CompactionSettings {
|
||||
const managed = this.managedCompactionSettings;
|
||||
const env: Record<string, string> = {};
|
||||
for (const tier of [this.effective.env, settingsOverride?.env, managed.env]) {
|
||||
for (const [name, value] of Object.entries(tier ?? {})) {
|
||||
if (value !== undefined) env[name] = value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
idleCompaction:
|
||||
managed.idleCompaction ?? settingsOverride?.idleCompaction ?? this.effective.idleCompaction,
|
||||
autoCompactEnabled:
|
||||
managed.autoCompactEnabled ??
|
||||
settingsOverride?.autoCompactEnabled ??
|
||||
this.effective.autoCompactEnabled,
|
||||
env,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `deniedModels` entries from the managed tier, the only tier the CLI
|
||||
* honors them from.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The `<task-notification>` inputs that Claude Code writes to a transcript
|
||||
* when a background task stops. The replay of a session (`v1/acp-agent.ts`) and
|
||||
* the session index (`v1/session-index/background-tasks.ts`) read them here, so
|
||||
* the session index (`session-index/background-tasks.ts`) read them here, so
|
||||
* both read the same fields.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"options":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"Check the file.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_thought_chunk"}}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"content":{"text":"I will edit it.","type":"text"},"messageId":"msg_r1","sessionUpdate":"agent_message_chunk"}}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"resume":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"initialize","payload":{"_meta":{"jetbrains":{"air":{"capabilities":["sessionFailure","agentFileChangeReport","nativeSubagentSessions","asyncTasks","customInstructions","recommendedValue","diffPatch","planFile","backgroundSubagents"],"goal":{"actions":["set","clear"],"controlMethod":"_session/goal","version":1},"version":1}},"steering":{"supported":true}},"agentCapabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"logout":{},"status":{}},"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"embeddedContext":true,"image":true},"providers":{},"sessionCapabilities":{"additionalDirectories":{},"close":{},"delete":{},"fork":{},"list":{},"options":{},"resume":{},"status":{},"subagents":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"agentInfo":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"authMethods":[],"protocolVersion":1}}
|
||||
{"kind":"newSession","payload":{"configOptions":[{"category":"mode","currentValue":"auto","description":"Session permission mode","id":"mode","name":"Mode","options":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","name":"Manual","value":"default"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","id":"model","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"modes":{"availableModes":[{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Always ask before making changes","id":"default","name":"Manual"},{"_meta":{"jetbrains":{"air":{"kind":"standard","version":1}}},"description":"Automatically accept all file edits","id":"acceptEdits","name":"Accept edits"},{"_meta":{"jetbrains":{"air":{"kind":"plan","version":1}}},"description":"Create a plan before making changes","id":"plan","name":"Plan"},{"_meta":{"jetbrains":{"air":{"kind":"auto_review","version":1}}},"description":"Claude handles permission decisions","id":"auto","name":"Auto"},{"_meta":{"jetbrains":{"air":{"kind":"full_access","version":1}}},"description":"Accepts all permissions","id":"bypassPermissions","name":"Bypass permissions"}],"currentModeId":"auto"},"sessionId":"<session>"}}
|
||||
{"kind":"sessionUpdate","payload":{"sessionId":"<session>","update":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]"},"name":"mcp"}],"sessionUpdate":"available_commands_update"}}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"/goal Ship the feature","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":{"text":"Fix the bug","type":"text"},"messageId":"00000000-0000-4000-8000-00000000c001","sessionUpdate":"user_message_chunk"}}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[],"messageId":"msg_r1","sessionUpdate":"agent_thought"}}}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"prompt":{"embeddedContext":{},"image":{}},"systemPrompt":{"mode":{"override":{}}}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"initialize","payload":{"authMethods":[],"capabilities":{"_meta":{"authStatus":{},"claudeCode":{"promptQueueing":true}},"auth":{"status":{}},"providers":{},"session":{"additionalDirectories":{},"delete":{},"fork":{},"mcp":{"http":{},"stdio":{}},"options":{},"prompt":{"embeddedContext":{},"image":{}},"status":{},"systemPrompt":{"mode":{"override":{}}},"title":{}}},"info":{"name":"@agentclientprotocol/claude-agent-acp","title":"Claude Agent","version":"<version>"},"protocolVersion":2}}
|
||||
{"kind":"response","method":"session/new","payload":{"availableCommands":[{"description":"Show the MCP servers and their status, or reconnect, enable, or disable a server","input":{"hint":"[reconnect|enable|disable [<server>|all]]","type":"text"},"name":"mcp","type":"command"}],"configOptions":[{"category":"mode","configId":"mode","currentValue":"auto","description":"Session permission mode","name":"Mode","options":[{"description":"Always ask before making changes","name":"Manual","value":"default"},{"description":"Automatically accept all file edits","name":"Accept edits","value":"acceptEdits"},{"description":"Create a plan before making changes","name":"Plan","value":"plan"},{"description":"Claude handles permission decisions","name":"Auto","value":"auto"},{"description":"Accepts all permissions","name":"Bypass permissions","value":"bypassPermissions"}],"type":"select"},{"category":"model","configId":"model","currentValue":"claude-sonnet-4-6","description":"AI model to use","name":"Model","options":[{"description":"Fast","name":"Claude Sonnet 4.6","value":"claude-sonnet-4-6"}],"type":"select"}],"sessionId":"<session>"}}
|
||||
{"kind":"notification","method":"session/update","payload":{"sessionId":"<session>","update":{"content":[{"text":"prompt 1","type":"text"}],"messageId":"<id-1>","sessionUpdate":"user_message"}}}
|
||||
{"kind":"response","method":"session/prompt","payload":{"messageId":"<id-1>"}}
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
* - The `initialize` response also advertises
|
||||
* `agentCapabilities.sessionCapabilities.systemPrompt`, which origin/main
|
||||
* did not: the agent now applies the `systemPrompt` of session requests.
|
||||
* - The `initialize` response also advertises `agentCapabilities.auth.status`,
|
||||
* which origin/main did not: the agent now answers `auth/status` and sends
|
||||
* `auth/status_update`.
|
||||
* - The `initialize` response also advertises
|
||||
* `agentCapabilities.sessionCapabilities.status`, which origin/main did
|
||||
* not: the agent now serves `session/status`.
|
||||
* - The `initialize` response also advertises
|
||||
* `agentCapabilities.sessionCapabilities.title`, which origin/main did not:
|
||||
* the agent now serves `session/set_title`.
|
||||
* - The `initialize` response also advertises
|
||||
* `agentCapabilities.sessionCapabilities.options`, which origin/main did
|
||||
* not: the agent now serves `session/options` and takes `configOptions` on
|
||||
* session setup requests.
|
||||
* - A SandboxNetworkAccess tool call is settled by a `tool_call_update` with
|
||||
* only its final `status` once its permission request is answered. The
|
||||
* callback runs nothing, so origin/main never settled the call (see
|
||||
@@ -226,19 +239,30 @@ function isMeta(key: string): boolean {
|
||||
return key.startsWith("_meta.");
|
||||
}
|
||||
|
||||
/** `record` without the session capability `key`, when it is an `initialize` response. */
|
||||
function withoutSessionCapability(record: Recorded, key: string): Recorded {
|
||||
/** `record` without the session capabilities `keys`, when it is an `initialize` response. */
|
||||
function withoutSessionCapabilities(record: Recorded, keys: readonly string[]): Recorded {
|
||||
if (record.kind !== "initialize") return record;
|
||||
const payload = record.payload as Json;
|
||||
const agent = payload.agentCapabilities as Json | undefined;
|
||||
const session = agent?.sessionCapabilities as Json | undefined;
|
||||
if (!session || !(key in session)) return record;
|
||||
if (!session || !keys.some((key) => key in session)) return record;
|
||||
const remaining = keys.reduce((rest, key) => without(rest, key), session);
|
||||
return {
|
||||
...record,
|
||||
payload: {
|
||||
...payload,
|
||||
agentCapabilities: { ...agent, sessionCapabilities: without(session, key) },
|
||||
},
|
||||
payload: { ...payload, agentCapabilities: { ...agent, sessionCapabilities: remaining } },
|
||||
} as Recorded;
|
||||
}
|
||||
|
||||
/** `record` without `agentCapabilities.auth.status`, when it is an `initialize` response. */
|
||||
function withoutAuthStatusCapability(record: Recorded): Recorded {
|
||||
if (record.kind !== "initialize") return record;
|
||||
const payload = record.payload as Json;
|
||||
const agent = payload.agentCapabilities as Json | undefined;
|
||||
const auth = agent?.auth as Json | undefined;
|
||||
if (!auth || !("status" in auth)) return record;
|
||||
return {
|
||||
...record,
|
||||
payload: { ...payload, agentCapabilities: { ...agent, auth: without(auth, "status") } },
|
||||
} as Recorded;
|
||||
}
|
||||
|
||||
@@ -248,13 +272,19 @@ function withoutSessionCapability(record: Recorded, key: string): Recorded {
|
||||
*/
|
||||
export function compareWithBaseline(baseline: Recorded[], traffic: Recorded[]): string[] {
|
||||
const expected = baseline
|
||||
.map((record) => withoutSessionCapability(withoutAirOnlyKeys(record) as Recorded, "subagents"))
|
||||
.map((record) =>
|
||||
withoutSessionCapabilities(withoutAirOnlyKeys(record) as Recorded, ["subagents"]),
|
||||
)
|
||||
.filter((record) => {
|
||||
const update = updateOf(record);
|
||||
return !(update?.sessionUpdate === "session_info_update" && Object.keys(update).length === 1);
|
||||
});
|
||||
const current = withoutSandboxNetworkSettlements(
|
||||
traffic.map((record) => withoutSessionCapability(record, "systemPrompt")),
|
||||
traffic.map((record) =>
|
||||
withoutAuthStatusCapability(
|
||||
withoutSessionCapabilities(record, ["systemPrompt", "status", "title", "options"]),
|
||||
),
|
||||
),
|
||||
);
|
||||
const violations: string[] = [];
|
||||
const state = new Map<string, Map<string, string>>();
|
||||
|
||||
@@ -373,6 +373,8 @@ export async function runScenario(
|
||||
async extNotification(method: string, params: unknown) {
|
||||
record("extNotification", { method, params });
|
||||
},
|
||||
// Not recorded: no scenario reads the auth status.
|
||||
async authStatusUpdate() {},
|
||||
async readTextFile() {
|
||||
return { content: "" };
|
||||
},
|
||||
@@ -559,7 +561,8 @@ export async function runScenarioV2(
|
||||
"_auth/status_update",
|
||||
(params) => params,
|
||||
() => {},
|
||||
);
|
||||
)
|
||||
.onNotification(v2.methods.client.auth.statusUpdate, () => {});
|
||||
|
||||
try {
|
||||
await client.connectWith(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { agentCommand } from "../agent-command.js";
|
||||
import { agentCommand, cliArgs } from "../agent-command.js";
|
||||
|
||||
describe("agentCommand", () => {
|
||||
it.each([
|
||||
@@ -22,3 +22,44 @@ describe("agentCommand", () => {
|
||||
expect(agentCommand(argv)).toEqual({ command: process.execPath, args });
|
||||
});
|
||||
});
|
||||
|
||||
describe("cliArgs", () => {
|
||||
/**
|
||||
* The `--cli` passthrough hands the Claude CLI the adapter's flags without `--cli` and
|
||||
* `--hide-claude-auth`, under Node and in a compiled executable, and still forwards `--version`.
|
||||
*/
|
||||
it.each([
|
||||
{
|
||||
scenario: "Node, terminal-auth login rerun",
|
||||
argv: [
|
||||
"/usr/bin/node",
|
||||
"/opt/acp/dist/index.js",
|
||||
"--hide-claude-auth",
|
||||
"--cli",
|
||||
"auth",
|
||||
"login",
|
||||
"--console",
|
||||
],
|
||||
args: ["auth", "login", "--console"],
|
||||
},
|
||||
{
|
||||
scenario: "a compiled executable",
|
||||
argv: [
|
||||
"bun",
|
||||
"/$bunfs/root/claude-agent-acp",
|
||||
"--hide-claude-auth",
|
||||
"--cli",
|
||||
"auth",
|
||||
"status",
|
||||
],
|
||||
args: ["auth", "status"],
|
||||
},
|
||||
{
|
||||
scenario: "--version after --cli",
|
||||
argv: ["/usr/bin/node", "/opt/acp/dist/index.js", "--cli", "--version"],
|
||||
args: ["--version"],
|
||||
},
|
||||
])("drops the adapter's own flags for $scenario", ({ argv, args }) => {
|
||||
expect(cliArgs(argv)).toEqual(args);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
type ClientCapabilities,
|
||||
type InitializeRequest,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/** The context-usage report the base mock query returns. `rawMaxTokens`
|
||||
* matches the agent's DEFAULT_CONTEXT_WINDOW so window-related assertions
|
||||
@@ -82,3 +84,73 @@ export function heldRequest<T>(_params?: unknown, signal?: AbortSignal): Promise
|
||||
else signal?.addEventListener("abort", cancel, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
/** A transcript record of a user message. */
|
||||
export function transcriptUser(uuid: string, content: string, extra: object = {}) {
|
||||
return { type: "user", uuid, message: { role: "user", content }, ...extra };
|
||||
}
|
||||
|
||||
/** A transcript record of an assistant text response. */
|
||||
export function transcriptAssistant(uuid: string, text: string) {
|
||||
return {
|
||||
type: "assistant",
|
||||
uuid,
|
||||
message: {
|
||||
id: `msg_${uuid}`,
|
||||
role: "assistant",
|
||||
model: "claude-opus-5",
|
||||
content: [{ type: "text", text }],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** A `compact_boundary` as Claude Code writes it: no parent, and the last
|
||||
* message before it in `logicalParentUuid`. */
|
||||
export function transcriptCompactBoundary(
|
||||
uuid: string,
|
||||
logicalParentUuid: string,
|
||||
compactMetadata: object = {},
|
||||
) {
|
||||
return {
|
||||
type: "system",
|
||||
subtype: "compact_boundary",
|
||||
uuid,
|
||||
parentUuid: null,
|
||||
logicalParentUuid,
|
||||
compactMetadata: { trigger: "manual", preTokens: 1000, ...compactMetadata },
|
||||
};
|
||||
}
|
||||
|
||||
/** The summary message that follows a `compact_boundary`. */
|
||||
export function transcriptCompactSummary(
|
||||
uuid: string,
|
||||
content = "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\n" +
|
||||
"Summary:\n1. Primary Request and Intent:\n Say hi.\n\n" +
|
||||
"Continue the conversation from where it left off without asking the user any further questions. Resume directly — do not acknowledge the summary.",
|
||||
) {
|
||||
return transcriptUser(uuid, content, { isCompactSummary: true });
|
||||
}
|
||||
|
||||
/** Writes `<directory>/<sessionId>.jsonl` as Claude Code does: each record
|
||||
* links to the record before it, except a record that sets its own
|
||||
* `parentUuid`. */
|
||||
export async function writeTranscript(
|
||||
directory: string,
|
||||
sessionId: string,
|
||||
records: Record<string, unknown>[],
|
||||
): Promise<void> {
|
||||
await mkdir(directory, { recursive: true });
|
||||
let parentUuid: unknown = null;
|
||||
const lines = records.map((record) => {
|
||||
const line = {
|
||||
parentUuid,
|
||||
sessionId,
|
||||
isSidechain: false,
|
||||
timestamp: "2026-10-11T00:00:00.000Z",
|
||||
...record,
|
||||
};
|
||||
parentUuid = record.uuid;
|
||||
return JSON.stringify(line);
|
||||
});
|
||||
await writeFile(path.join(directory, `${sessionId}.jsonl`), lines.join("\n") + "\n");
|
||||
}
|
||||
|
||||
92
src/tests/model-selection-settings.test.ts
Normal file
92
src/tests/model-selection-settings.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { describe, it, expect, afterEach, vi } from "vitest";
|
||||
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { SettingsManager } from "../settings.js";
|
||||
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@anthropic-ai/claude-agent-sdk")>()),
|
||||
resolveSettings: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("model selection settings policy", () => {
|
||||
let manager: SettingsManager;
|
||||
afterEach(() => manager?.dispose());
|
||||
|
||||
it.each([{ availableModels: ["claude-haiku-4-5"] }, { availableModels: [] }])(
|
||||
"keeps the managed allowlist %j and mappings above inline settings",
|
||||
async ({ availableModels }) => {
|
||||
const managed = {
|
||||
availableModels,
|
||||
modelOverrides: { "claude-haiku-4-5": "managed-haiku" },
|
||||
deniedModels: ["claude-opus-5"],
|
||||
};
|
||||
vi.mocked(resolveSettings).mockResolvedValue({
|
||||
effective: managed,
|
||||
provenance: {},
|
||||
sources: [{ source: "managed", settings: managed }],
|
||||
});
|
||||
manager = new SettingsManager(process.cwd());
|
||||
await manager.initialize();
|
||||
expect(
|
||||
manager.getModelSelectionSettings({
|
||||
availableModels: ["claude-opus-5"],
|
||||
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
|
||||
}),
|
||||
).toEqual({
|
||||
availableModels: managed.availableModels,
|
||||
modelOverrides: managed.modelOverrides,
|
||||
});
|
||||
expect(manager.getManagedDeniedModels()).toEqual(managed.deniedModels);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps managed override keys above inline keys without discarding other inline mappings", async () => {
|
||||
const managed = { modelOverrides: { "claude-haiku-4-5": "managed-haiku" } };
|
||||
vi.mocked(resolveSettings).mockResolvedValue({
|
||||
effective: managed,
|
||||
provenance: {},
|
||||
sources: [{ source: "managed", settings: managed }],
|
||||
});
|
||||
manager = new SettingsManager(process.cwd());
|
||||
await manager.initialize();
|
||||
expect(
|
||||
manager.getModelSelectionSettings({
|
||||
availableModels: ["claude-haiku-4-5", "claude-sonnet-4-6"],
|
||||
modelOverrides: {
|
||||
"claude-haiku-4-5": "inline-haiku",
|
||||
"claude-sonnet-4-6": "inline-sonnet",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
availableModels: ["claude-haiku-4-5", "claude-sonnet-4-6"],
|
||||
modelOverrides: { "claude-haiku-4-5": "managed-haiku", "claude-sonnet-4-6": "inline-sonnet" },
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves file and inline mappings when the managed allowlist supplies no mappings", async () => {
|
||||
vi.mocked(resolveSettings).mockResolvedValue({
|
||||
effective: {
|
||||
availableModels: ["claude-haiku-4-5"],
|
||||
modelOverrides: { "claude-haiku-4-5": "file-haiku" },
|
||||
},
|
||||
provenance: {},
|
||||
sources: [
|
||||
{ source: "user", settings: { modelOverrides: { "claude-haiku-4-5": "file-haiku" } } },
|
||||
{ source: "managed", settings: { availableModels: ["claude-haiku-4-5"] } },
|
||||
],
|
||||
});
|
||||
manager = new SettingsManager(process.cwd());
|
||||
await manager.initialize();
|
||||
expect(manager.getModelSelectionSettings()).toEqual({
|
||||
availableModels: ["claude-haiku-4-5"],
|
||||
modelOverrides: { "claude-haiku-4-5": "file-haiku" },
|
||||
});
|
||||
expect(
|
||||
manager.getModelSelectionSettings({
|
||||
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
|
||||
}),
|
||||
).toEqual({
|
||||
availableModels: ["claude-haiku-4-5"],
|
||||
modelOverrides: { "claude-haiku-4-5": "inline-haiku" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,20 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getSessionMessages, type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import {
|
||||
transcriptAssistant,
|
||||
transcriptCompactBoundary,
|
||||
transcriptCompactSummary,
|
||||
transcriptUser,
|
||||
writeTranscript,
|
||||
} from "./helpers.js";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { readResumedTail, resumedModelFromTranscript } from "../resumed-session.js";
|
||||
import {
|
||||
readHistoryAcrossCompactions,
|
||||
readResumedTail,
|
||||
resumedModelFromTranscript,
|
||||
} from "../resumed-session.js";
|
||||
|
||||
function assistant(
|
||||
model: unknown,
|
||||
@@ -203,3 +214,187 @@ describe("readResumedTail", () => {
|
||||
expect(await readResumedTail("no-mode")).toEqual({ model: "claude-opus-5" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("readHistoryAcrossCompactions", () => {
|
||||
const sessionId = "33333333-3333-4333-8333-333333333333";
|
||||
let configDir: string;
|
||||
let originalConfigDir: string | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-compacted-"));
|
||||
originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
||||
process.env.CLAUDE_CONFIG_DIR = configDir;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (originalConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
||||
else process.env.CLAUDE_CONFIG_DIR = originalConfigDir;
|
||||
await rm(configDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const transcript = (records: Record<string, unknown>[], project = "-workspace") =>
|
||||
writeTranscript(path.join(configDir, "projects", project), sessionId, records);
|
||||
const user = transcriptUser;
|
||||
const assistant = transcriptAssistant;
|
||||
const boundary = transcriptCompactBoundary;
|
||||
const summary = (uuid: string) => transcriptCompactSummary(uuid, `Summary ${uuid}`);
|
||||
|
||||
const texts = (messages: SessionMessage[]) =>
|
||||
messages.map((message) => {
|
||||
const content = (message.message as { content: unknown }).content;
|
||||
return typeof content === "string" ? content : (content as { text: string }[])[0]!.text;
|
||||
});
|
||||
|
||||
it("reads the messages before each compaction", async () => {
|
||||
await transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "next"),
|
||||
assistant("a2", "Next"),
|
||||
boundary("b2", "a2"),
|
||||
summary("s2"),
|
||||
user("u3", "identify yourself"),
|
||||
assistant("a3", "Claude"),
|
||||
]);
|
||||
|
||||
// getSessionMessages stops at the last boundary.
|
||||
const chain = await getSessionMessages(sessionId);
|
||||
expect(texts(chain)).toEqual(["Summary s2", "identify yourself", "Claude"]);
|
||||
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
|
||||
"hi",
|
||||
"Hello",
|
||||
"Summary s1",
|
||||
"next",
|
||||
"Next",
|
||||
"Summary s2",
|
||||
"identify yourself",
|
||||
"Claude",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the messages that a compaction preserved where they were said", async () => {
|
||||
// Claude Code 2.1.293 keeps u2 and a2 in the context after the summary,
|
||||
// and records them in both forms of preserved-message metadata.
|
||||
await transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
user("u2", "keep this"),
|
||||
assistant("a2", "Kept"),
|
||||
boundary("b1", "a2", {
|
||||
preservedSegment: { headUuid: "u2", anchorUuid: "s1", tailUuid: "a2" },
|
||||
preservedMessages: { anchorUuid: "s1", uuids: ["u2", "a2"] },
|
||||
}),
|
||||
summary("s1"),
|
||||
user("u3", "after"),
|
||||
assistant("a3", "After"),
|
||||
]);
|
||||
|
||||
// getSessionMessages moves the preserved messages after the summary.
|
||||
const chain = await getSessionMessages(sessionId);
|
||||
expect(texts(chain)).toEqual(["Summary s1", "keep this", "Kept", "after", "After"]);
|
||||
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
|
||||
"hi",
|
||||
"Hello",
|
||||
"keep this",
|
||||
"Kept",
|
||||
"Summary s1",
|
||||
"after",
|
||||
"After",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns the chain of a session without a compaction as it is", async () => {
|
||||
await transcript([user("u1", "hi"), assistant("a1", "Hello")]);
|
||||
|
||||
const chain = await getSessionMessages(sessionId);
|
||||
expect(await readHistoryAcrossCompactions(sessionId, chain)).toBe(chain);
|
||||
});
|
||||
|
||||
it("ends at the last message of the chain it was given", async () => {
|
||||
await transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "identify yourself"),
|
||||
]);
|
||||
const chain = await getSessionMessages(sessionId);
|
||||
// Claude Code appends a response after the first read.
|
||||
await transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "identify yourself"),
|
||||
assistant("a2", "Claude"),
|
||||
]);
|
||||
|
||||
expect(texts(await readHistoryAcrossCompactions(sessionId, chain))).toEqual([
|
||||
"hi",
|
||||
"Hello",
|
||||
"Summary s1",
|
||||
"identify yourself",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["the transcript is gone", async () => rm(configDir, { recursive: true, force: true })],
|
||||
[
|
||||
"the transcript no longer has the last message",
|
||||
() =>
|
||||
transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u9", "other"),
|
||||
]),
|
||||
],
|
||||
])("returns the chain and logs when %s", async (_case, change) => {
|
||||
await transcript([
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "identify yourself"),
|
||||
]);
|
||||
const chain = await getSessionMessages(sessionId);
|
||||
await change();
|
||||
const error = vi.fn();
|
||||
|
||||
expect(await readHistoryAcrossCompactions(sessionId, chain, { log: () => {}, error })).toBe(
|
||||
chain,
|
||||
);
|
||||
expect(error).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("reads the transcript in the project directory it is given", async () => {
|
||||
await transcript(
|
||||
[
|
||||
user("u1", "elsewhere"),
|
||||
assistant("a1", "Elsewhere"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "identify yourself"),
|
||||
],
|
||||
"-a-other",
|
||||
);
|
||||
await transcript(
|
||||
[
|
||||
user("u1", "hi"),
|
||||
assistant("a1", "Hello"),
|
||||
boundary("b1", "a1"),
|
||||
summary("s1"),
|
||||
user("u2", "identify yourself"),
|
||||
],
|
||||
"-z-workspace",
|
||||
);
|
||||
const chain = await getSessionMessages(sessionId, { dir: "/z/workspace" });
|
||||
|
||||
expect(
|
||||
texts(await readHistoryAcrossCompactions(sessionId, chain, undefined, "/z/workspace")),
|
||||
).toEqual(["hi", "Hello", "Summary s1", "identify yourself"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,28 +2,28 @@ import { describe, expect, it } from "vitest";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { deriveActivity, selectCost, type ActivityState } from "../../v1/session-index/activity.js";
|
||||
import { LiveSessionRegistry, type LiveRecord } from "../../v1/session-index/live-registry.js";
|
||||
import { deriveActivity, selectCost, type ActivityState } from "../session-index/activity.js";
|
||||
import { LiveSessionRegistry, type LiveRecord } from "../session-index/live-registry.js";
|
||||
import { isSessionId } from "../paths.js";
|
||||
import {
|
||||
encodeProjectPath,
|
||||
isSessionId,
|
||||
pathAndAncestors,
|
||||
projectDirMatches,
|
||||
} from "../../v1/session-index/project-dirs.js";
|
||||
} from "../session-index/project-dirs.js";
|
||||
import {
|
||||
decodeListCursor,
|
||||
encodeListCursor,
|
||||
parseListOptions,
|
||||
parseRenameSessionRequest,
|
||||
type ListScope,
|
||||
} from "../../v1/session-index/service.js";
|
||||
} from "../session-index/service.js";
|
||||
import {
|
||||
effectiveTitle,
|
||||
isArchivedTitle,
|
||||
storedTitle,
|
||||
titleRecords,
|
||||
visibleTitle,
|
||||
} from "../../v1/session-index/archive-title.js";
|
||||
} from "../session-index/archive-title.js";
|
||||
import {
|
||||
agentWorktreeOf,
|
||||
continuedInSessionId,
|
||||
@@ -33,11 +33,11 @@ import {
|
||||
sdkTitles,
|
||||
transcriptAgentName,
|
||||
type TranscriptFacts,
|
||||
} from "../../v1/session-index/transcript-scan.js";
|
||||
import { firstPrompt } from "../../v1/session-index/first-prompt.js";
|
||||
import { DirListings, statFiles } from "../../v1/session-index/dir-listing.js";
|
||||
import { SessionIndex, type IndexRow } from "../../v1/session-index/session-index.js";
|
||||
import { changeSignature, sessionInfoOf } from "../../v1/session-index/session-info.js";
|
||||
} from "../session-index/transcript-scan.js";
|
||||
import { firstPrompt } from "../session-index/first-prompt.js";
|
||||
import { DirListings, statFiles } from "../session-index/dir-listing.js";
|
||||
import { SessionIndex, type IndexRow } from "../session-index/session-index.js";
|
||||
import { changeSignature, sessionInfoOf } from "../session-index/session-info.js";
|
||||
|
||||
const SESSION = "11111111-1111-4111-8111-111111111111";
|
||||
const lines = (...entries: object[]) => entries.map((entry) => JSON.stringify(entry)).join("\n");
|
||||
@@ -38,3 +38,62 @@ describe("SettingsManager.getApiKeyHelper", () => {
|
||||
manager.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("SettingsManager.getCompactionSettings", () => {
|
||||
it("ranks managed settings over the --settings override over the files, env entry by entry", async () => {
|
||||
const manager = await managerResolving({
|
||||
effective: {
|
||||
idleCompaction: true,
|
||||
autoCompactEnabled: false,
|
||||
env: {
|
||||
DISABLE_COMPACT: "1",
|
||||
DISABLE_AUTO_COMPACT: "1",
|
||||
CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS: "1",
|
||||
},
|
||||
},
|
||||
provenance: {},
|
||||
sources: [
|
||||
{ source: "userSettings", settings: { autoCompactEnabled: false } },
|
||||
{
|
||||
source: "managed",
|
||||
settings: { idleCompaction: true, env: { DISABLE_AUTO_COMPACT: "1" } },
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(
|
||||
manager.getCompactionSettings({
|
||||
idleCompaction: false,
|
||||
autoCompactEnabled: true,
|
||||
env: { DISABLE_COMPACT: "0", DISABLE_AUTO_COMPACT: "0" },
|
||||
}),
|
||||
).toEqual({
|
||||
idleCompaction: true,
|
||||
autoCompactEnabled: true,
|
||||
env: {
|
||||
DISABLE_COMPACT: "0",
|
||||
DISABLE_AUTO_COMPACT: "1",
|
||||
CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS: "1",
|
||||
},
|
||||
});
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
it("keeps the files' value for a key the override sets to undefined", async () => {
|
||||
const manager = await managerResolving({
|
||||
effective: { idleCompaction: false, env: { DISABLE_COMPACT: "1" } },
|
||||
provenance: {},
|
||||
sources: [],
|
||||
});
|
||||
expect(
|
||||
manager.getCompactionSettings({
|
||||
idleCompaction: undefined,
|
||||
env: { DISABLE_COMPACT: undefined as unknown as string },
|
||||
}),
|
||||
).toEqual({
|
||||
idleCompaction: false,
|
||||
autoCompactEnabled: undefined,
|
||||
env: { DISABLE_COMPACT: "1" },
|
||||
});
|
||||
manager.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -921,6 +921,136 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
return { setModelSpy };
|
||||
}
|
||||
|
||||
it.each(["environment", "caller object", "caller file"])(
|
||||
"includes configured models omitted by the SDK from %s settings",
|
||||
async (source) => {
|
||||
const configured = {
|
||||
availableModels: ["claude-haiku-4-5", "claude-haiku-5-5"],
|
||||
modelOverrides: {
|
||||
"claude-haiku-4-5": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"claude-haiku-5-5": "global.anthropic.claude-haiku-5-5",
|
||||
},
|
||||
};
|
||||
let settings: object | string | undefined;
|
||||
if (source === "environment") {
|
||||
vi.stubEnv("CLAUDE_MODEL_CONFIG", JSON.stringify(configured));
|
||||
} else if (source === "caller file") {
|
||||
settings = "caller-settings.json";
|
||||
await fs.promises.writeFile(path.join(tempDir, settings), JSON.stringify(configured));
|
||||
} else {
|
||||
settings = configured;
|
||||
}
|
||||
const { setModelSpy } = mockQueryWithModels([
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
{ value: "sonnet", displayName: "Sonnet", description: "" },
|
||||
]);
|
||||
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
|
||||
const agent = new ClaudeAcpAgent(createMockClient());
|
||||
try {
|
||||
const response = await (agent as any).createSession({
|
||||
cwd: tempDir,
|
||||
mcpServers: [],
|
||||
_meta: { claudeCode: { options: { settings } } },
|
||||
});
|
||||
expect(querySpy.mock.calls[0][0].options.settings).toEqual(configured);
|
||||
const ids = ["default", ...Object.values(configured.modelOverrides)];
|
||||
expect(
|
||||
agent.sessions[response.sessionId].models.availableModels.map(
|
||||
(model: any) => model.modelId,
|
||||
),
|
||||
).toEqual(ids);
|
||||
const modelOption = response.configOptions.find((option: any) => option.id === "model");
|
||||
expect(modelOption.options.map((option: any) => option.value)).toEqual(ids);
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: response.sessionId,
|
||||
configId: "model",
|
||||
value: ids[2],
|
||||
});
|
||||
expect(setModelSpy).toHaveBeenLastCalledWith(ids[2]);
|
||||
expect(agent.sessions[response.sessionId].modelInfos.map((model) => model.value)).toEqual(
|
||||
ids,
|
||||
);
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
vi.unstubAllEnvs();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("merges file settings with caller settings after caller keys replace the env config", async () => {
|
||||
await fs.promises.writeFile(
|
||||
path.join(tempDir, "settings.json"),
|
||||
JSON.stringify({
|
||||
availableModels: ["claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
modelOverrides: {
|
||||
"claude-sonnet-4-6": "file-sonnet",
|
||||
"claude-haiku-4-5": "file-haiku",
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.stubEnv(
|
||||
"CLAUDE_MODEL_CONFIG",
|
||||
JSON.stringify({
|
||||
availableModels: ["claude-opus-4-6"],
|
||||
modelOverrides: { "claude-sonnet-4-6": "env-sonnet", "claude-haiku-4-5": "env-haiku" },
|
||||
}),
|
||||
);
|
||||
mockQueryWithModels([{ value: "default", displayName: "Default", description: "" }]);
|
||||
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
|
||||
const agent = new ClaudeAcpAgent(createMockClient());
|
||||
const settings = {
|
||||
availableModels: ["claude-haiku-4-5", "claude-haiku-5-5"],
|
||||
modelOverrides: { "claude-haiku-4-5": "caller-haiku" },
|
||||
};
|
||||
try {
|
||||
const response = await (agent as any).createSession({
|
||||
cwd: tempDir,
|
||||
mcpServers: [],
|
||||
_meta: { claudeCode: { options: { settings } } },
|
||||
});
|
||||
expect(querySpy.mock.calls[0][0].options.settings).toEqual(settings);
|
||||
const modelOption = response.configOptions.find((option: any) => option.id === "model");
|
||||
expect(modelOption.options.map((option: any) => option.value)).toEqual([
|
||||
"default",
|
||||
"file-sonnet",
|
||||
"caller-haiku",
|
||||
"claude-haiku-5-5",
|
||||
]);
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
vi.unstubAllEnvs();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([[], undefined])(
|
||||
"preserves empty and absent configured allowlists: %j",
|
||||
async (availableModels) => {
|
||||
vi.stubEnv(
|
||||
"CLAUDE_MODEL_CONFIG",
|
||||
JSON.stringify({
|
||||
availableModels,
|
||||
modelOverrides: { "claude-haiku-4-5": "provider-haiku" },
|
||||
}),
|
||||
);
|
||||
mockQueryWithModels([
|
||||
{ value: "default", displayName: "Default", description: "" },
|
||||
{ value: "sonnet", displayName: "Sonnet", description: "" },
|
||||
]);
|
||||
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
|
||||
const agent = new ClaudeAcpAgent(createMockClient());
|
||||
try {
|
||||
const response = await (agent as any).createSession({ cwd: tempDir, mcpServers: [] });
|
||||
const modelOption = response.configOptions.find((option: any) => option.id === "model");
|
||||
expect(modelOption.options.map((option: any) => option.value)).toEqual(
|
||||
availableModels ? ["default"] : ["default", "sonnet"],
|
||||
);
|
||||
} finally {
|
||||
await agent.dispose();
|
||||
vi.unstubAllEnvs();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("restricts configOptions to the user's allowlist using their exact IDs", async () => {
|
||||
// Reproduces the scenario from
|
||||
// https://github.com/agentclientprotocol/claude-agent-acp/issues/620:
|
||||
|
||||
@@ -58,7 +58,16 @@ import {
|
||||
parseUsageResponse,
|
||||
} from "../../usage-markdown.js";
|
||||
import { Pushable } from "../../utils.js";
|
||||
import { heldRequest, initializeClient, makeMockQuery } from "../helpers.js";
|
||||
import {
|
||||
heldRequest,
|
||||
initializeClient,
|
||||
makeMockQuery,
|
||||
transcriptAssistant,
|
||||
transcriptCompactBoundary,
|
||||
transcriptCompactSummary,
|
||||
transcriptUser,
|
||||
writeTranscript,
|
||||
} from "../helpers.js";
|
||||
import { acpProtocolRouter } from "../../serve.js";
|
||||
import * as agentSdk from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
@@ -77,7 +86,7 @@ import {
|
||||
type SDKControlGetUsageResponse,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import { createHash, randomUUID } from "crypto";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
@@ -3629,6 +3638,63 @@ describe("usage-limit failure replay", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("compacted session replay", () => {
|
||||
it("replays the messages before the compaction, then the compaction", async () => {
|
||||
const configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-compacted-replay-"));
|
||||
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
||||
process.env.CLAUDE_CONFIG_DIR = configDir;
|
||||
try {
|
||||
const sessionId = "44444444-4444-4444-8444-444444444444";
|
||||
// The records of `/compact` between two prompts, as Claude Code writes
|
||||
// them: the boundary has no parent and links "Hello" logically.
|
||||
await writeTranscript(path.join(configDir, "projects", "-tmp-proj"), sessionId, [
|
||||
transcriptUser("u1", "hi"),
|
||||
transcriptAssistant("a1", "Hello"),
|
||||
transcriptCompactBoundary("b1", "a1"),
|
||||
transcriptCompactSummary("s1"),
|
||||
transcriptUser("u2", "identify yourself"),
|
||||
transcriptAssistant("a2", "Claude"),
|
||||
]);
|
||||
const updates: SessionNotification[] = [];
|
||||
const client = {
|
||||
sessionUpdate: async (update: SessionNotification) => {
|
||||
updates.push(update);
|
||||
},
|
||||
} as unknown as AcpClient;
|
||||
const agent = new ClaudeAcpAgent(client, { log: () => {}, error: () => {} });
|
||||
await initializeClient(agent, { session: { compaction: {} } } as ClientCapabilities);
|
||||
|
||||
await (
|
||||
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
|
||||
).replaySessionHistory(sessionId);
|
||||
|
||||
expect(
|
||||
updates.flatMap(({ update }) => {
|
||||
if (update.sessionUpdate === "compaction_update") return [["compaction", update.status]];
|
||||
if (
|
||||
(update.sessionUpdate === "user_message_chunk" ||
|
||||
update.sessionUpdate === "agent_message_chunk") &&
|
||||
update.content.type === "text"
|
||||
) {
|
||||
return [[update.sessionUpdate, update.content.text]];
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
).toEqual([
|
||||
["user_message_chunk", "hi"],
|
||||
["agent_message_chunk", "Hello"],
|
||||
["compaction", "completed"],
|
||||
["user_message_chunk", "identify yourself"],
|
||||
["agent_message_chunk", "Claude"],
|
||||
]);
|
||||
} finally {
|
||||
if (originalConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
||||
else process.env.CLAUDE_CONFIG_DIR = originalConfigDir;
|
||||
await rm(configDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/list", () => {
|
||||
const sdkSession = (index: number) => ({
|
||||
sessionId: `session-${index}`,
|
||||
@@ -14953,6 +15019,79 @@ describe("session/fork", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/status", () => {
|
||||
it("answers open for the sessions it holds, and the others from their stored transcripts", async () => {
|
||||
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
try {
|
||||
const agent = new ClaudeAcpAgent({ sessionUpdate: async () => {} } as unknown as AcpClient, {
|
||||
log: () => {},
|
||||
error: () => {},
|
||||
});
|
||||
const { agentCapabilities } = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {},
|
||||
});
|
||||
expect(agentCapabilities?.sessionCapabilities?.status).toEqual({});
|
||||
const status = (sessionId: string) => agent.sessionStatus({ sessionId });
|
||||
const unknown = (sessionId: string) => ({ code: -32002, data: { sessionId } });
|
||||
|
||||
const sessionId = randomUUID();
|
||||
agent.sessions[sessionId] = {} as ClaudeAcpAgent["sessions"][string];
|
||||
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
|
||||
// A held session whose query ended stays open: a load returns it as it is.
|
||||
agent.sessions[sessionId]!.queryClosed = true;
|
||||
await expect(status(sessionId)).resolves.toEqual({ status: "open" });
|
||||
// A subagent's session id, and a name that every object inherits.
|
||||
await expect(status("agent-1")).rejects.toMatchObject(unknown("agent-1"));
|
||||
await expect(status("constructor")).rejects.toMatchObject(unknown("constructor"));
|
||||
|
||||
// Closed, and never prompted: no projects directory exists yet.
|
||||
delete agent.sessions[sessionId];
|
||||
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
|
||||
// A transcript of any project counts, next to a project without one.
|
||||
await mkdir(path.join(configDir, "projects", "-other"), { recursive: true });
|
||||
const project = path.join(configDir, "projects", "-project");
|
||||
// A directory of that name is no transcript.
|
||||
await mkdir(path.join(project, `${sessionId}.jsonl`), { recursive: true });
|
||||
await expect(status(sessionId)).rejects.toMatchObject(unknown(sessionId));
|
||||
await rm(path.join(project, `${sessionId}.jsonl`), { recursive: true });
|
||||
await writeFile(path.join(project, `${sessionId}.jsonl`), "{}\n");
|
||||
await expect(status(sessionId)).resolves.toEqual({ status: "saved" });
|
||||
// The request loaded nothing.
|
||||
expect(agent.sessions[sessionId]).toBeUndefined();
|
||||
} finally {
|
||||
vi.unstubAllEnvs();
|
||||
await rm(configDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Not on Windows, where `chmod` does not take the read permission away.
|
||||
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
|
||||
"fails, not -32002, when a project directory cannot be read",
|
||||
async () => {
|
||||
const configDir = await mkdtemp(path.join(os.tmpdir(), "session-status-"));
|
||||
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
||||
const project = path.join(configDir, "projects", "-project");
|
||||
await mkdir(project, { recursive: true });
|
||||
await chmod(project, 0o000);
|
||||
try {
|
||||
const agent = new ClaudeAcpAgent(
|
||||
{ sessionUpdate: async () => {} } as unknown as AcpClient,
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
await expect(agent.sessionStatus({ sessionId: randomUUID() })).rejects.toMatchObject({
|
||||
code: "EACCES",
|
||||
});
|
||||
} finally {
|
||||
await chmod(project, 0o700);
|
||||
vi.unstubAllEnvs();
|
||||
await rm(configDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("session/close", () => {
|
||||
function createMockAgent() {
|
||||
const mockClient = {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
fromCliStatus,
|
||||
mergeAuthStatus,
|
||||
sameIdentity,
|
||||
toAcpAuthStatus,
|
||||
} from "../../auth-status.js";
|
||||
import { DEFAULT_CONTEXT_USAGE, makeMockQuery } from "../helpers.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
@@ -189,6 +190,48 @@ describe("auth status mappers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each<[string, AuthStatus | undefined, ReturnType<typeof toAcpAuthStatus>]>([
|
||||
["no identity read yet", undefined, { state: "unknown" }],
|
||||
["a logged-out CLI", { kind: "none", label: "Not logged in" }, { state: "unauthenticated" }],
|
||||
[
|
||||
"a subscription",
|
||||
{
|
||||
kind: "account",
|
||||
label: "Claude Max",
|
||||
account: { plan: "max", email: "user@example.com", organization: "ACME" },
|
||||
},
|
||||
{
|
||||
state: "authenticated",
|
||||
accounts: [
|
||||
{
|
||||
label: "Claude Max",
|
||||
active: true,
|
||||
email: "user@example.com",
|
||||
organization: "ACME",
|
||||
_meta: { claudeCode: { kind: "account", plan: "max" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
"an API key",
|
||||
{ kind: "api_key", label: "Anthropic API key", detail: "env" },
|
||||
{
|
||||
state: "authenticated",
|
||||
accounts: [
|
||||
{
|
||||
label: "Anthropic API key",
|
||||
description: "env",
|
||||
active: true,
|
||||
_meta: { claudeCode: { kind: "api_key", detail: "env" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
])("maps %s to the ACP auth/status payload", (_name, status, expected) => {
|
||||
expect(toAcpAuthStatus(status)).toEqual(expected);
|
||||
});
|
||||
|
||||
it("does not use an inactive helper or override an external backend", () => {
|
||||
const helper = "central proxy start";
|
||||
expect(fromCliStatus(CLI_BEDROCK, helper)).toEqual({ kind: "external", label: "AWS Bedrock" });
|
||||
@@ -321,6 +364,48 @@ describe("auth status mappers", () => {
|
||||
).toEqual({ kind: "account", label: "Claude subscription", detail: "OAuth token" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
["max", "max_20x", "Claude Ultra"],
|
||||
["Claude Max", "max_20x", "Claude Ultra"],
|
||||
["max", "max_5x", "Claude Max"],
|
||||
["max", "max_other", "Claude Max"],
|
||||
["max", undefined, "Claude Max"],
|
||||
["max", "future_tier", "Claude Max"],
|
||||
["max", null, "Claude Max"],
|
||||
["max", 20, "Claude Max"],
|
||||
["max", { tier: "max_20x" }, "Claude Max"],
|
||||
["max", ["max_20x"], "Claude Max"],
|
||||
["pro", "max_20x", "Claude Pro"],
|
||||
["Future Plan", "future_tier", "Claude Future Plan"],
|
||||
])("labels CLI plan %j with tier %j as %j", (subscriptionType, planTier, label) => {
|
||||
expect(
|
||||
fromCliStatus(
|
||||
JSON.stringify({
|
||||
...JSON.parse(CLI_SUBSCRIPTION),
|
||||
subscriptionType,
|
||||
planTier,
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
kind: "account",
|
||||
label,
|
||||
account: { plan: subscriptionType, email: "user@example.com", organization: "ACME" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ apiKeySource: "env" }, { kind: "api_key", label: "Anthropic API key", detail: "env" }],
|
||||
[{ apiProvider: "bedrock" }, { kind: "external", label: "AWS Bedrock" }],
|
||||
[{ apiProvider: "gateway" }, { kind: "gateway", label: "Custom model gateway" }],
|
||||
[{ loggedIn: false }, { kind: "none", label: "Not logged in" }],
|
||||
])("does not let a subscription tier override auth fields %j", (fields, expected) => {
|
||||
expect(
|
||||
fromCliStatus(
|
||||
JSON.stringify({ ...JSON.parse(CLI_SUBSCRIPTION), planTier: "max_20x", ...fields }),
|
||||
),
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
it("lets a third-party backend outrank the logged-out flag", () => {
|
||||
// `loggedIn` tracks the claude.ai login only; on Bedrock the credentials
|
||||
// are external and the agent is authenticated all the same.
|
||||
@@ -342,19 +427,27 @@ describe("auth status mappers", () => {
|
||||
};
|
||||
// Same login, read from a source without the organization: it survives.
|
||||
expect(
|
||||
mergeAuthStatus(stored, {
|
||||
kind: "account",
|
||||
label: "Claude Max",
|
||||
account: { plan: "max", email: "user@example.com" },
|
||||
}),
|
||||
mergeAuthStatus(
|
||||
stored,
|
||||
{
|
||||
kind: "account",
|
||||
label: "Claude Max",
|
||||
account: { plan: "max", email: "user@example.com" },
|
||||
},
|
||||
"cli",
|
||||
),
|
||||
).toEqual(stored);
|
||||
// A different login replaces everything, organization included.
|
||||
expect(
|
||||
mergeAuthStatus(stored, {
|
||||
kind: "account",
|
||||
label: "Claude Pro",
|
||||
account: { plan: "pro", email: "other@example.com" },
|
||||
}),
|
||||
mergeAuthStatus(
|
||||
stored,
|
||||
{
|
||||
kind: "account",
|
||||
label: "Claude Pro",
|
||||
account: { plan: "pro", email: "other@example.com" },
|
||||
},
|
||||
"cli",
|
||||
),
|
||||
).toEqual({
|
||||
kind: "account",
|
||||
label: "Claude Pro",
|
||||
@@ -362,7 +455,11 @@ describe("auth status mappers", () => {
|
||||
});
|
||||
// A different kind never merges, even when it looks adjacent.
|
||||
expect(
|
||||
mergeAuthStatus(stored, { kind: "api_key", label: "Anthropic API key", detail: "env" }),
|
||||
mergeAuthStatus(
|
||||
stored,
|
||||
{ kind: "api_key", label: "Anthropic API key", detail: "env" },
|
||||
"cli",
|
||||
),
|
||||
).toEqual({ kind: "api_key", label: "Anthropic API key", detail: "env" });
|
||||
// Key sources identify api_key payloads.
|
||||
expect(
|
||||
@@ -372,11 +469,36 @@ describe("auth status mappers", () => {
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each<["cli" | "session", string, string, string | undefined, string]>([
|
||||
["session", "max", "user@example.com", undefined, "Claude Ultra"],
|
||||
["session", "Claude Max", "user@example.com", undefined, "Claude Ultra"],
|
||||
["session", "pro", "user@example.com", undefined, "Claude Pro"],
|
||||
["session", "max", "other@example.com", undefined, "Claude Max"],
|
||||
["cli", "max", "user@example.com", "max_5x", "Claude Max"],
|
||||
["cli", "max", "user@example.com", undefined, "Claude Max"],
|
||||
["cli", "max", "user@example.com", "future_tier", "Claude Max"],
|
||||
])(
|
||||
"merges a %s read of %s for %s with tier %s as %s",
|
||||
(source, subscriptionType, email, planTier, label) => {
|
||||
const previous = fromCliStatus(
|
||||
JSON.stringify({ ...JSON.parse(CLI_SUBSCRIPTION), planTier: "max_20x" }),
|
||||
);
|
||||
const fields = { apiProvider: "firstParty" as const, subscriptionType, email };
|
||||
const next =
|
||||
source === "session"
|
||||
? fromAccountInfo(fields)
|
||||
: fromCliStatus(JSON.stringify({ ...fields, loggedIn: true, planTier }));
|
||||
expect(mergeAuthStatus(previous, next!, source).label).toBe(label);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("auth status over ACP", () => {
|
||||
let agent: ClaudeAcpAgentType;
|
||||
let extNotification: ReturnType<typeof vi.fn>;
|
||||
/** The `auth/status_update` notifications. */
|
||||
let authStatusUpdate: ReturnType<typeof vi.fn>;
|
||||
/** stdout the fake `claude auth status --json` prints, and whether the exec
|
||||
* fails (the logged-out case exits 1 with JSON still on stdout). */
|
||||
let statusStdout: string;
|
||||
@@ -450,9 +572,11 @@ describe("auth status over ACP", () => {
|
||||
});
|
||||
mockQuery.mockImplementation(() => makeMockQuery());
|
||||
extNotification = vi.fn().mockResolvedValue(undefined);
|
||||
authStatusUpdate = vi.fn().mockResolvedValue(undefined);
|
||||
agent = new ClaudeAcpAgent({
|
||||
sessionUpdate: async () => {},
|
||||
extNotification,
|
||||
authStatusUpdate,
|
||||
} as unknown as AcpClient);
|
||||
});
|
||||
|
||||
@@ -624,6 +748,124 @@ describe("auth status over ACP", () => {
|
||||
expect(response._meta?.authStatus).toBeUndefined();
|
||||
});
|
||||
|
||||
it("advertises auth.status and sends auth/status_update with each _auth/status_update", async () => {
|
||||
const response = await initialize();
|
||||
expect(response.agentCapabilities?.auth?.status).toEqual({});
|
||||
const subscription = {
|
||||
state: "authenticated",
|
||||
accounts: [
|
||||
{
|
||||
label: "Claude Max",
|
||||
active: true,
|
||||
email: "user@example.com",
|
||||
organization: "ACME",
|
||||
_meta: { claudeCode: { kind: "account", plan: "max" } },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(authStatusUpdate.mock.calls).toEqual([[subscription]]);
|
||||
|
||||
// An unchanged identity sends neither notification again.
|
||||
await agent.authStatusResponse();
|
||||
await settle();
|
||||
expect(updates()).toHaveLength(1);
|
||||
expect(authStatusUpdate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("sends auth/status_update only when its own payload changed", async () => {
|
||||
agent.setAuthStatus({ kind: "api_key", label: "Anthropic API key", detail: "env" });
|
||||
// Another `_auth/status_update` payload, the same `auth/status_update` one.
|
||||
agent.setAuthStatus({
|
||||
kind: "api_key",
|
||||
label: "Anthropic API key",
|
||||
detail: "env",
|
||||
vendor: { claudeCode: {} },
|
||||
});
|
||||
await settle();
|
||||
|
||||
expect(updates()).toHaveLength(2);
|
||||
expect(authStatusUpdate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("answers auth/status from the probe in flight while no identity is known", async () => {
|
||||
deferStatus = true;
|
||||
const init = agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
|
||||
const status = agent.authStatusResponse();
|
||||
await flushStatus();
|
||||
await init;
|
||||
|
||||
expect(await status).toMatchObject({
|
||||
state: "authenticated",
|
||||
accounts: [{ label: "Claude Max" }],
|
||||
});
|
||||
expect(statusCalls()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("answers auth/status at once with the known identity, and pushes what a new read finds", async () => {
|
||||
statusStdout = CLI_LOGGED_OUT;
|
||||
statusFails = true;
|
||||
await initialize();
|
||||
|
||||
// A login made outside the client, and a CLI that has not answered yet.
|
||||
statusStdout = CLI_SUBSCRIPTION;
|
||||
statusFails = false;
|
||||
deferStatus = true;
|
||||
expect(await agent.authStatusResponse()).toEqual({ state: "unauthenticated" });
|
||||
|
||||
await flushStatus();
|
||||
expect(authStatusUpdate.mock.calls.at(-1)?.[0]).toMatchObject({ state: "authenticated" });
|
||||
expect(await agent.authStatusResponse()).toMatchObject({ state: "authenticated" });
|
||||
});
|
||||
|
||||
it("discards a probe that lands after one that started later", async () => {
|
||||
statusStdout = CLI_LOGGED_OUT;
|
||||
statusFails = true;
|
||||
await initialize();
|
||||
|
||||
deferStatus = true;
|
||||
await agent.authStatusResponse();
|
||||
statusStdout = CLI_SUBSCRIPTION;
|
||||
statusFails = false;
|
||||
await agent.authStatusResponse();
|
||||
|
||||
await releaseStatus(1);
|
||||
await releaseStatus(0);
|
||||
expect(lastPush()?.kind).toBe("account");
|
||||
});
|
||||
|
||||
it("answers auth/status with the gateway login, without reading the CLI", async () => {
|
||||
await agent.authenticate({
|
||||
methodId: "gateway",
|
||||
_meta: { gateway: { baseUrl: "https://gw.example.com/v1", headers: {} } },
|
||||
} as never);
|
||||
|
||||
expect(await agent.authStatusResponse()).toEqual({
|
||||
state: "authenticated",
|
||||
accounts: [
|
||||
{
|
||||
label: "Custom model gateway",
|
||||
description: "gw.example.com",
|
||||
active: true,
|
||||
_meta: { claudeCode: { kind: "gateway", detail: "gw.example.com" } },
|
||||
},
|
||||
],
|
||||
});
|
||||
await settle();
|
||||
expect(statusCalls()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("answers auth/status with unknown while the CLI cannot be read, and keeps a known identity", async () => {
|
||||
statusStdout = "";
|
||||
statusFails = true;
|
||||
expect(await agent.authStatusResponse()).toEqual({ state: "unknown" });
|
||||
|
||||
agent.setAuthStatus({ kind: "api_key", label: "Anthropic API key", detail: "env" });
|
||||
expect(await agent.authStatusResponse()).toMatchObject({
|
||||
state: "authenticated",
|
||||
accounts: [{ label: "Anthropic API key", description: "env" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("pushes the first identity after the initialize response, never before it", async () => {
|
||||
// The client registers its receiver around `initialize`; a push that
|
||||
// overtook the response could be dropped. The probe is asynchronous, so
|
||||
@@ -696,6 +938,32 @@ describe("auth status over ACP", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["max_5x", undefined, "future_tier"])(
|
||||
"keeps Ultra across session reads and refreshes the label for CLI tier %j",
|
||||
async (planTier) => {
|
||||
statusStdout = JSON.stringify({ ...JSON.parse(CLI_SUBSCRIPTION), planTier: "max_20x" });
|
||||
await initialize();
|
||||
const sessionId = await scriptedSession({
|
||||
apiProvider: "firstParty",
|
||||
subscriptionType: "Claude Max",
|
||||
email: "user@example.com",
|
||||
});
|
||||
await promptOnce(sessionId);
|
||||
expect(updates().every((call) => call[1].authStatus.label === "Claude Ultra")).toBe(true);
|
||||
expect(await agent.authStatusResponse()).toMatchObject({
|
||||
accounts: [{ label: "Claude Ultra", _meta: { claudeCode: { plan: "max" } } }],
|
||||
});
|
||||
await settle();
|
||||
|
||||
statusStdout = JSON.stringify({ ...JSON.parse(CLI_SUBSCRIPTION), planTier });
|
||||
await promptOnce(sessionId);
|
||||
expect(lastPush()?.label).toBe("Claude Max");
|
||||
expect(authStatusUpdate.mock.calls.at(-1)?.[0]).toMatchObject({
|
||||
accounts: [{ label: "Claude Max" }],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("lets a probe of a different identity replace the stored one wholesale", async () => {
|
||||
const sessionId = await scriptedSession({
|
||||
apiProvider: "firstParty",
|
||||
@@ -1069,7 +1337,11 @@ describe("auth status over ACP", () => {
|
||||
logger = { log: vi.fn(), error: vi.fn(), warn: vi.fn() };
|
||||
extNotification = vi.fn().mockResolvedValue(undefined);
|
||||
agent = new ClaudeAcpAgent(
|
||||
{ sessionUpdate: async () => {}, extNotification } as unknown as AcpClient,
|
||||
{
|
||||
sessionUpdate: async () => {},
|
||||
extNotification,
|
||||
authStatusUpdate: async () => {},
|
||||
} as unknown as AcpClient,
|
||||
logger as Logger,
|
||||
);
|
||||
agent.setAuthStatus({ ...KNOWN });
|
||||
@@ -1101,6 +1373,14 @@ describe("auth status over ACP", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("answers auth/status with unknown when the first probe times out", async () => {
|
||||
(agent as any).authStatus.current = undefined;
|
||||
const status = agent.authStatusResponse();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
expect(await status).toEqual({ state: "unknown" });
|
||||
});
|
||||
|
||||
it("warns once however many probes time out", async () => {
|
||||
// A wedged CLI stays wedged: every later prompt would repeat the same
|
||||
// line otherwise.
|
||||
|
||||
@@ -1635,61 +1635,57 @@ describe("authorization", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("SSH session falls back to single legacy login method", async () => {
|
||||
const [agent] = await createAgentMock();
|
||||
vi.stubGlobal("process", { ...process, env: { ...process.env, SSH_TTY: "/dev/pts/0" } });
|
||||
|
||||
const initializeResponse = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: { auth: { terminal: true } },
|
||||
});
|
||||
|
||||
expect(initializeResponse.authMethods).toContainEqual(
|
||||
expect.objectContaining({ id: "claude-login" }),
|
||||
describe("remote environment", () => {
|
||||
const REMOTE_VARS = [
|
||||
"NO_BROWSER",
|
||||
"SSH_CONNECTION",
|
||||
"SSH_CLIENT",
|
||||
"SSH_TTY",
|
||||
"CLAUDE_CODE_REMOTE",
|
||||
] as const;
|
||||
const capabilityStyles = [
|
||||
["auth.terminal", { auth: { terminal: true } }],
|
||||
["_meta terminal-auth", { _meta: { "terminal-auth": true } }],
|
||||
] as const;
|
||||
const cases = REMOTE_VARS.flatMap((variable) =>
|
||||
capabilityStyles.flatMap(([style, clientCapabilities]) =>
|
||||
[false, true].map((hide) => ({ variable, style, clientCapabilities, hide })),
|
||||
),
|
||||
);
|
||||
expect(initializeResponse.authMethods).not.toContainEqual(
|
||||
expect.objectContaining({ id: "claude-ai-login" }),
|
||||
);
|
||||
expect(initializeResponse.authMethods).not.toContainEqual(
|
||||
expect.objectContaining({ id: "console-login" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("CLAUDE_CODE_REMOTE falls back to single legacy login method", async () => {
|
||||
const [agent] = await createAgentMock();
|
||||
vi.stubGlobal("process", { ...process, env: { ...process.env, CLAUDE_CODE_REMOTE: "1" } });
|
||||
/**
|
||||
* With only one remote variable set, initialize offers the TUI login plus the
|
||||
* console login, or only the console login under --hide-claude-auth.
|
||||
*/
|
||||
it.each(cases)(
|
||||
"$variable ($style, hide-claude-auth=$hide) offers console-login",
|
||||
async ({ variable, clientCapabilities, hide }) => {
|
||||
const [agent] = await createAgentMock();
|
||||
const env: Record<string, string | undefined> = { ...process.env };
|
||||
for (const name of REMOTE_VARS) delete env[name];
|
||||
env[variable] = "1";
|
||||
vi.stubGlobal("process", {
|
||||
...process,
|
||||
argv: hide ? ["--hide-claude-auth"] : [],
|
||||
env,
|
||||
});
|
||||
|
||||
const initializeResponse = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: { auth: { terminal: true } },
|
||||
});
|
||||
const initializeResponse = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities,
|
||||
});
|
||||
|
||||
expect(initializeResponse.authMethods).toContainEqual(
|
||||
expect.objectContaining({ id: "claude-login" }),
|
||||
);
|
||||
expect(initializeResponse.authMethods).not.toContainEqual(
|
||||
expect.objectContaining({ id: "claude-ai-login" }),
|
||||
);
|
||||
expect(initializeResponse.authMethods).not.toContainEqual(
|
||||
expect.objectContaining({ id: "console-login" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("remote environment respects hide-claude-auth", async () => {
|
||||
const [agent] = await createAgentMock();
|
||||
vi.stubGlobal("process", {
|
||||
...process,
|
||||
argv: ["--hide-claude-auth"],
|
||||
env: { ...process.env, SSH_CONNECTION: "192.168.1.1 12345 192.168.1.2 22" },
|
||||
});
|
||||
|
||||
const initializeResponse = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: { auth: { terminal: true } },
|
||||
});
|
||||
|
||||
expect(initializeResponse.authMethods).not.toContainEqual(
|
||||
expect.objectContaining({ id: "claude-login" }),
|
||||
expect((initializeResponse.authMethods ?? []).map((method) => method.id)).toEqual(
|
||||
hide ? ["console-login"] : ["claude-login", "console-login"],
|
||||
);
|
||||
const consoleLogin = initializeResponse.authMethods?.find(
|
||||
(method) => method.id === "console-login",
|
||||
);
|
||||
if ("_meta" in clientCapabilities) {
|
||||
const terminalAuth = (consoleLogin?._meta as any)?.["terminal-auth"];
|
||||
expect(terminalAuth.args.slice(-4)).toEqual(["--cli", "auth", "login", "--console"]);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ import * as fsSync from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { SessionInfo } from "@agentclientprotocol/sdk/unstable";
|
||||
import type { ActivityState } from "../../v1/session-index/activity.js";
|
||||
import { endedTaskId, searchBackgroundTasks } from "../../v1/session-index/background-tasks.js";
|
||||
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
|
||||
import { SessionIndex } from "../../v1/session-index/session-index.js";
|
||||
import { SessionIndexService } from "../../v1/session-index/service.js";
|
||||
import type { ActivityState } from "../../session-index/activity.js";
|
||||
import { endedTaskId, searchBackgroundTasks } from "../../session-index/background-tasks.js";
|
||||
import { encodeProjectPath } from "../../session-index/project-dirs.js";
|
||||
import { SessionIndex } from "../../session-index/session-index.js";
|
||||
import { SessionIndexService } from "../../session-index/service.js";
|
||||
|
||||
let configDir: string;
|
||||
let workspace: string;
|
||||
|
||||
@@ -1365,6 +1365,229 @@ describe("createSession options merging", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("session options", () => {
|
||||
const cwd = process.cwd();
|
||||
const TWO_MODELS = [
|
||||
{
|
||||
value: "claude-sonnet-4-6",
|
||||
displayName: "Claude Sonnet",
|
||||
description: "Fast",
|
||||
supportsAutoMode: true,
|
||||
supportsEffort: true,
|
||||
supportedEffortLevels: ["low", "medium", "high"],
|
||||
},
|
||||
{
|
||||
value: "claude-haiku-4-5",
|
||||
displayName: "Claude Haiku",
|
||||
description: "Faster",
|
||||
supportsEffort: true,
|
||||
supportedEffortLevels: ["low", "medium"],
|
||||
},
|
||||
];
|
||||
let updates: SessionNotification[];
|
||||
|
||||
const values = (response: { configOptions?: { id: string; currentValue: unknown }[] | null }) =>
|
||||
Object.fromEntries((response.configOptions ?? []).map((o) => [o.id, o.currentValue]));
|
||||
|
||||
beforeEach(() => {
|
||||
initModels = TWO_MODELS;
|
||||
updates = [];
|
||||
vi.spyOn(agent.client, "sessionUpdate").mockImplementation(async (notification) => {
|
||||
updates.push(notification as SessionNotification);
|
||||
});
|
||||
});
|
||||
|
||||
it("advertises session/options", async () => {
|
||||
const response = await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
|
||||
expect(response.agentCapabilities?.sessionCapabilities?.options).toEqual({});
|
||||
});
|
||||
|
||||
it("answers session/options with what session/new returns, from a query that it closes", async () => {
|
||||
const options = await agent.sessionOptions({ cwd, additionalDirectories: ["/extra"] });
|
||||
|
||||
expect(capturedOptions).toMatchObject({
|
||||
cwd,
|
||||
additionalDirectories: ["/extra"],
|
||||
mcpServers: {},
|
||||
persistSession: false,
|
||||
});
|
||||
expect(capturedOptions!.resume).toBeUndefined();
|
||||
expect(closeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(agent.sessions).toEqual({});
|
||||
expect(updates).toEqual([]);
|
||||
|
||||
const { sessionId, ...created } = await agent.newSession({ cwd, mcpServers: [] });
|
||||
expect(sessionId).toBeDefined();
|
||||
expect(capturedOptions!.persistSession).toBeUndefined();
|
||||
expect(options).toEqual(created);
|
||||
});
|
||||
|
||||
it("answers session/options of a held session with its current options, without a query", async () => {
|
||||
const { sessionId } = await agent.newSession({ cwd, mcpServers: [] });
|
||||
const { configOptions } = await agent.setSessionConfigOption({
|
||||
sessionId,
|
||||
configId: "model",
|
||||
value: "claude-haiku-4-5",
|
||||
});
|
||||
capturedOptions = undefined;
|
||||
|
||||
const options = await agent.sessionOptions({ cwd, sessionId });
|
||||
|
||||
expect(capturedOptions).toBeUndefined();
|
||||
expect(options).toEqual({ modes: agent.sessions[sessionId]!.modes, configOptions });
|
||||
expect(values(options).model).toBe("claude-haiku-4-5");
|
||||
});
|
||||
|
||||
it("answers session/options of a stored session from a query that resumes it", async () => {
|
||||
const sessionId = "7f1c5b0e-3c53-4c0c-9d0a-0d6c1f0a2b11";
|
||||
await agent.sessionOptions({ cwd, sessionId });
|
||||
|
||||
expect(capturedOptions).toMatchObject({ resume: sessionId, persistSession: false });
|
||||
expect(capturedOptions!.sessionId).toBeUndefined();
|
||||
expect(closeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(agent.sessions).toEqual({});
|
||||
});
|
||||
|
||||
it("fails session/options of an unknown session with -32002", async () => {
|
||||
const sessionId = "7f1c5b0e-3c53-4c0c-9d0a-0d6c1f0a2b11";
|
||||
initError = new Error(`No conversation found with session ID: ${sessionId}`);
|
||||
|
||||
await expect(agent.sessionOptions({ cwd, sessionId })).rejects.toMatchObject({
|
||||
code: RequestError.sessionNotFound(sessionId).code,
|
||||
data: { sessionId },
|
||||
});
|
||||
expect(closeSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("starts a new session with the request's configOptions, in list order, without an update", async () => {
|
||||
const setModels: string[] = [];
|
||||
setModelImpl = async (model) => void setModels.push(model);
|
||||
|
||||
const response = await agent.newSession({
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [
|
||||
{ configId: "model", value: "claude-haiku-4-5" },
|
||||
// Haiku offers no "high": the entry is checked against the options the model left.
|
||||
{ configId: "effort", value: "high" },
|
||||
{ configId: "mode", value: "plan" },
|
||||
],
|
||||
});
|
||||
|
||||
expect(setModels).toEqual(["claude-haiku-4-5"]);
|
||||
expect(values(response)).toMatchObject({ model: "claude-haiku-4-5", mode: "plan" });
|
||||
expect(values(response).effort).not.toBe("high");
|
||||
expect(response.modes?.currentModeId).toBe("plan");
|
||||
expect(response.configOptions).toEqual(agent.sessions[response.sessionId]!.configOptions);
|
||||
expect(updates.filter((u) => u.sessionId === response.sessionId)).toEqual([]);
|
||||
// A restart of the query restores the session's choices, not the request's.
|
||||
expect(agent.sessions[response.sessionId]!.creationParams).not.toHaveProperty(
|
||||
"configOptions",
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["an unknown configId", { configId: "nope", value: "x" }],
|
||||
["a boolean for a select", { configId: "model", type: "boolean" as const, value: true }],
|
||||
["a value the option does not offer", { configId: "model", value: "gpt-4" }],
|
||||
])("skips an entry with %s and sets the entries after it", async (_name, entry) => {
|
||||
const plain = await agent.newSession({ cwd, mcpServers: [] });
|
||||
|
||||
const response = await agent.newSession({
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [entry, { configId: "mode", value: "plan" }],
|
||||
});
|
||||
|
||||
expect(values(response)).toEqual({ ...values(plain), mode: "plan" });
|
||||
});
|
||||
|
||||
it("skips an entry that Claude Code refuses", async () => {
|
||||
setModelImpl = async () => {
|
||||
throw new Error("model refused");
|
||||
};
|
||||
const errors = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
const response = await agent.newSession({
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [
|
||||
{ configId: "model", value: "claude-haiku-4-5" },
|
||||
{ configId: "mode", value: "plan" },
|
||||
],
|
||||
});
|
||||
|
||||
expect(values(response)).toMatchObject({ model: "claude-sonnet-4-6", mode: "plan" });
|
||||
expect(errors).toHaveBeenCalledWith(
|
||||
"Could not set the initial value of config option model:",
|
||||
expect.any(Error),
|
||||
);
|
||||
errors.mockRestore();
|
||||
});
|
||||
|
||||
it.each(["newSession", "loadSession", "resumeSession"] as const)(
|
||||
"%s rejects two entries of one configId before it starts a query",
|
||||
async (method) => {
|
||||
await expect(
|
||||
agent[method]({
|
||||
sessionId: "7f1c5b0e-3c53-4c0c-9d0a-0d6c1f0a2b11",
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [
|
||||
{ configId: "mode", value: "plan" },
|
||||
{ configId: "mode", value: "default" },
|
||||
],
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: RequestError.invalidParams().code,
|
||||
data: { configId: "mode" },
|
||||
});
|
||||
expect(capturedOptions).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["loadSession", "resumeSession"] as const)(
|
||||
"%s sets the request's configOptions on the session, a held one too",
|
||||
async (method) => {
|
||||
const sessionId = "7f1c5b0e-3c53-4c0c-9d0a-0d6c1f0a2b11";
|
||||
const started = await agent[method]({
|
||||
sessionId,
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [{ configId: "mode", value: "plan" }],
|
||||
});
|
||||
expect(values(started).mode).toBe("plan");
|
||||
expect(started.modes?.currentModeId).toBe("plan");
|
||||
|
||||
const held = await agent[method]({
|
||||
sessionId,
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [{ configId: "model", value: "claude-haiku-4-5" }],
|
||||
});
|
||||
expect(values(held)).toMatchObject({ mode: "plan", model: "claude-haiku-4-5" });
|
||||
expect(updates.map((u) => u.update.sessionUpdate)).not.toContain("current_mode_update");
|
||||
expect(updates.map((u) => u.update.sessionUpdate)).not.toContain("config_option_update");
|
||||
},
|
||||
);
|
||||
|
||||
it("leaves the warning of an Auto mode fallback for the first prompt", async () => {
|
||||
const response = await agent.newSession({
|
||||
cwd,
|
||||
mcpServers: [],
|
||||
configOptions: [
|
||||
{ configId: "mode", value: "auto" },
|
||||
// Haiku cannot serve Auto.
|
||||
{ configId: "model", value: "claude-haiku-4-5" },
|
||||
],
|
||||
});
|
||||
|
||||
expect(values(response)).toMatchObject({ mode: "acceptEdits", model: "claude-haiku-4-5" });
|
||||
expect(updates.filter((u) => u.sessionId === response.sessionId)).toEqual([]);
|
||||
expect(agent.sessions[response.sessionId]!.autoModeFallbackWarningPending).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cwd validation", () => {
|
||||
it("rejects a relative cwd with invalidParams", async () => {
|
||||
await expect(
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import { messageIdForGrouping } from "../../sdk-to-acp.js";
|
||||
import { forkSession } from "../../fork-session.js";
|
||||
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
|
||||
import { encodeProjectPath } from "../../session-index/project-dirs.js";
|
||||
|
||||
// Use the real SDK against temporary transcripts, without starting Claude Code.
|
||||
let root: string;
|
||||
|
||||
@@ -226,7 +226,10 @@ function scriptClaudeCode({
|
||||
};
|
||||
}
|
||||
|
||||
async function connect(clientCapabilities: Record<string, unknown> = {}) {
|
||||
async function connect(
|
||||
clientCapabilities: Record<string, unknown> = {},
|
||||
sessionMeta?: Record<string, unknown>,
|
||||
) {
|
||||
vi.resetModules();
|
||||
const { ClaudeAcpAgent } = await import("../../v1/acp-agent.js");
|
||||
const updates: Array<{ update: Record<string, any> }> = [];
|
||||
@@ -241,7 +244,7 @@ async function connect(clientCapabilities: Record<string, unknown> = {}) {
|
||||
error: () => {},
|
||||
});
|
||||
await initializeClient(agent, clientCapabilities);
|
||||
const session = await agent.newSession({ cwd, mcpServers: [] });
|
||||
const session = await agent.newSession({ cwd, mcpServers: [], _meta: sessionMeta });
|
||||
return { agent, sessionId: session.sessionId, updates, logs };
|
||||
}
|
||||
|
||||
@@ -322,6 +325,22 @@ describe("idle compaction in the agent", () => {
|
||||
await agent.dispose();
|
||||
});
|
||||
|
||||
/** The client's `settings` option reaches the CLI as `--settings`, and idle compaction reads it too. */
|
||||
it("does not compact a session whose client turned idle compaction off in the settings option", async () => {
|
||||
const claude = scriptClaudeCode();
|
||||
const { agent, sessionId } = await connect(
|
||||
{},
|
||||
{ claudeCode: { options: { settings: { idleCompaction: false } } } },
|
||||
);
|
||||
|
||||
await promptHi(agent, sessionId);
|
||||
await claude.untilIdle();
|
||||
await vi.advanceTimersByTimeAsync(2 * 60 * MINUTE_MS);
|
||||
|
||||
expect(claude.received).toEqual(["hi"]);
|
||||
await agent.dispose();
|
||||
});
|
||||
|
||||
/** Closing the session during the wait drops the timer. */
|
||||
it("does not compact once the session is closed", async () => {
|
||||
const claude = scriptClaudeCode();
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
// The agent reads the CLI's auth status on each prompt. Answer it, so that the
|
||||
// tests checking `logError` don't see "claude auth status failed". The answer
|
||||
// makes the agent send an auth status `extNotification`; the client stubs it.
|
||||
// makes the agent send the auth status notifications; the client stubs them.
|
||||
vi.mock("node:child_process", async () => ({
|
||||
...(await vi.importActual<typeof import("node:child_process")>("node:child_process")),
|
||||
execFile: (...args: unknown[]) =>
|
||||
@@ -1270,6 +1270,7 @@ function createTestSession(
|
||||
},
|
||||
requestPermission: async () => ({ outcome: { outcome: "selected", optionId: "allow-once" } }),
|
||||
extNotification: async () => {},
|
||||
authStatusUpdate: async () => {},
|
||||
} as unknown as AcpClient,
|
||||
{ log: () => {}, error: logError },
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
type InitializeResponse,
|
||||
type SessionInfo,
|
||||
} from "@agentclientprotocol/sdk/unstable";
|
||||
import { encodeProjectPath } from "../../v1/session-index/project-dirs.js";
|
||||
import { encodeProjectPath } from "../../session-index/project-dirs.js";
|
||||
import {
|
||||
archiveInsteadOfDelete,
|
||||
archiveTo,
|
||||
@@ -31,16 +31,16 @@ import {
|
||||
parseBatchGetSessionsRequest,
|
||||
SessionIndexService,
|
||||
writeCustomTitleSidecar,
|
||||
} from "../../v1/session-index/service.js";
|
||||
import { scanTranscriptFile } from "../../v1/session-index/transcript-scan.js";
|
||||
import { repositoryWorktrees } from "../../v1/session-index/worktrees.js";
|
||||
} from "../../session-index/service.js";
|
||||
import { scanTranscriptFile } from "../../session-index/transcript-scan.js";
|
||||
import { repositoryWorktrees } from "../../session-index/worktrees.js";
|
||||
import {
|
||||
ListSubscriptions,
|
||||
MAX_SUBSCRIPTIONS,
|
||||
parseListSubscribeRequest,
|
||||
parseListUnsubscribeRequest,
|
||||
type ListChanges,
|
||||
} from "../../v1/session-index/list-subscriptions.js";
|
||||
} from "../../session-index/list-subscriptions.js";
|
||||
import { SessionTitles } from "../../session-titles.js";
|
||||
import { initializeClient } from "../helpers.js";
|
||||
import {
|
||||
@@ -1350,6 +1350,48 @@ describe("_session/rename", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/set_title", () => {
|
||||
it("titles a stored session for a client without the session index", async () => {
|
||||
const session = await writeTranscript({});
|
||||
const { agent } = createAgent();
|
||||
await initializeClient(agent, {});
|
||||
expect(
|
||||
await agent.setSessionTitle({ sessionId: session.id, title: " Investigate\n OAuth " }),
|
||||
).toEqual({ title: "Investigate OAuth" });
|
||||
expect(await lastRecords(session.file)).toEqual(titleRecords(session.id, "Investigate OAuth"));
|
||||
const page = await agent.listSessions({ cwd: workspace });
|
||||
expect(page.sessions[0]!.title).toBe("Investigate OAuth");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["an AIR client with sessionIndex", air("sessionIndex"), "Renamed"],
|
||||
["an AIR client without sessionIndex", air(), "Renamed"],
|
||||
["a client that is not AIR", {}, "[archived] Renamed"],
|
||||
])(
|
||||
"keeps an archived session archived and answers the title %s shows",
|
||||
async (_client, capabilities, shown) => {
|
||||
const session = await writeTranscript({ trailer: titleRecords("", "[archived] Fix it") });
|
||||
const { agent } = createAgent();
|
||||
await initializeClient(agent, capabilities);
|
||||
expect(await agent.setSessionTitle({ sessionId: session.id, title: "Renamed" })).toEqual({
|
||||
title: shown,
|
||||
});
|
||||
expect(await lastRecords(session.file)).toEqual(
|
||||
titleRecords(session.id, "[archived] Renamed"),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["an unknown session", () => ({ sessionId: randomUUID(), title: "x" }), -32002],
|
||||
["an empty title", () => ({ sessionId: randomUUID(), title: " \n " }), -32602],
|
||||
])("refuses %s", async (_case, params, code) => {
|
||||
const { agent } = createAgent();
|
||||
await initializeClient(agent, {});
|
||||
await expect(agent.setSessionTitle(params())).rejects.toMatchObject({ code });
|
||||
});
|
||||
});
|
||||
|
||||
describe("_session/archive and _session/unarchive", () => {
|
||||
it("append AIR's archive title records, are idempotent and keep updatedAt", async () => {
|
||||
const session = await writeTranscript({ prompt: "Fix the\nparser" });
|
||||
@@ -1854,7 +1896,7 @@ describe("_session/list/subscribe", () => {
|
||||
});
|
||||
|
||||
it("clears the failed turn of a session that runs here when a new turn starts", async () => {
|
||||
const { noteSessionState } = await import("../../v1/session-index/connection.js");
|
||||
const { noteSessionState } = await import("../../v1/session-index.js");
|
||||
const session = { lastTurnFailed: true } as any;
|
||||
noteSessionState(session, "idle", "running");
|
||||
expect(session.lastTurnFailed).toBe(false);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* schema, so a malformed v2 message fails the test.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { appendFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { appendFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
v1SetSessionConfigOptionRequest,
|
||||
v2ConfigOptions,
|
||||
} from "../../v2/session-requests.js";
|
||||
import { encodeProjectPath } from "../../session-index/project-dirs.js";
|
||||
import { startupFailure } from "../helpers.js";
|
||||
import packageJson from "../../../package.json" with { type: "json" };
|
||||
|
||||
@@ -108,6 +109,8 @@ const sdk = vi.hoisted(() => ({
|
||||
accountInfo: async (): Promise<Record<string, unknown>> => ({}),
|
||||
/** The query switches its model (`setModel`). */
|
||||
setModel: async (_model?: string): Promise<void> => {},
|
||||
/** Claude Code stores a session title (`rename_session`). */
|
||||
renameSession: async (_title: string, _sessionId?: string): Promise<void> => {},
|
||||
/** Set to serve Remote Control, as a query of a CLI that supports it does. */
|
||||
enableRemoteControl: undefined as
|
||||
((enabled: boolean, name?: string) => Promise<{ session_url?: string }>) | undefined,
|
||||
@@ -142,6 +145,7 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
|
||||
accountInfo: () => sdk.accountInfo(),
|
||||
setMcpServers: (servers: Record<string, unknown>) => sdk.setMcpServers(servers),
|
||||
setModel: (model?: string) => sdk.setModel(model),
|
||||
renameSession: (title: string, sessionId?: string) => sdk.renameSession(title, sessionId),
|
||||
...(sdk.enableRemoteControl ? { enableRemoteControl: sdk.enableRemoteControl } : {}),
|
||||
});
|
||||
return Object.assign(messages, controls);
|
||||
@@ -250,6 +254,8 @@ function v2Client({
|
||||
) => v2.CreateElicitationResponse | Promise<v2.CreateElicitationResponse>;
|
||||
} = {}) {
|
||||
const authUpdates: AuthStatusUpdateNotification[] = [];
|
||||
/** The `auth/status_update` notifications. */
|
||||
const acpAuthUpdates: v2.AuthStatusUpdateNotification[] = [];
|
||||
const sessionUpdates: v2.UpdateSessionNotification[] = [];
|
||||
const elicitations: v2.CreateElicitationRequest[] = [];
|
||||
const completedElicitations: v2.CompleteElicitationNotification[] = [];
|
||||
@@ -278,6 +284,9 @@ function v2Client({
|
||||
notifyAuthUpdate();
|
||||
},
|
||||
)
|
||||
.onNotification(v2.methods.client.auth.statusUpdate, ({ params }) => {
|
||||
acpAuthUpdates.push(params);
|
||||
})
|
||||
.onNotification(v2.methods.client.session.update, ({ params }) => {
|
||||
sessionUpdates.push(params);
|
||||
})
|
||||
@@ -315,6 +324,7 @@ function v2Client({
|
||||
return {
|
||||
app,
|
||||
authUpdate,
|
||||
acpAuthUpdates,
|
||||
sessionUpdates,
|
||||
updates,
|
||||
elicitations,
|
||||
@@ -388,6 +398,9 @@ describe("ACP protocol routing", () => {
|
||||
fork: {},
|
||||
additionalDirectories: {},
|
||||
systemPrompt: { mode: { override: {} } },
|
||||
status: {},
|
||||
options: {},
|
||||
title: {},
|
||||
});
|
||||
expect(response.capabilities?.providers).toEqual({});
|
||||
// Not `_session/steering`, which v1 advertises in the top-level `_meta`.
|
||||
@@ -741,6 +754,236 @@ describe("ACP v2 sessions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/status", () => {
|
||||
/** Stores a transcript of `sessionId`, as Claude Code does at the first prompt. */
|
||||
async function storeTranscript(sessionId: string, content = "{}\n") {
|
||||
const project = path.join(tempDir, "claude", "projects", "-project");
|
||||
await mkdir(project, { recursive: true });
|
||||
await writeFile(path.join(project, `${sessionId}.jsonl`), content);
|
||||
}
|
||||
const status = (agent: v2.ClientContext, sessionId: string) =>
|
||||
agent.request(v2.methods.agent.session.status, { sessionId });
|
||||
const unknown = (sessionId: string) => ({ code: -32002, data: { sessionId } });
|
||||
|
||||
it("answers from memory and the stored transcript, and starts or sends nothing", async () => {
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const neverUsed = randomUUID();
|
||||
await expect(status(agent, neverUsed)).rejects.toMatchObject(unknown(neverUsed));
|
||||
// Not a UUID: no file is looked up under such a name.
|
||||
await expect(status(agent, "../settings")).rejects.toMatchObject(unknown("../settings"));
|
||||
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
await client.authUpdate(1);
|
||||
// The agent's post-setup work runs on a timer.
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
const sent = client.sessionUpdates.length;
|
||||
// The state as a `state_update` snapshot holds it, with the session's list.
|
||||
expect(await status(agent, sessionId)).toEqual({
|
||||
status: "open",
|
||||
state: { state: "idle", background: [] },
|
||||
});
|
||||
|
||||
await agent.request(v2.methods.agent.session.close, { sessionId });
|
||||
// Claude Code writes the transcript at the first prompt: none is stored.
|
||||
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
|
||||
// An empty transcript holds no conversation to resume.
|
||||
await storeTranscript(sessionId, "");
|
||||
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
|
||||
await storeTranscript(sessionId);
|
||||
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
|
||||
|
||||
expect(sdk.queryOptions).toHaveLength(1);
|
||||
expect(client.sessionUpdates).toHaveLength(sent);
|
||||
});
|
||||
});
|
||||
|
||||
it("answers a session whose query ended without session/close from its transcript", async () => {
|
||||
// Claude Code exits right after it started.
|
||||
sdk.run = async function* () {};
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
await vi.waitFor(() => expect(v2Session(sessionId)).toBeUndefined());
|
||||
|
||||
await expect(status(agent, sessionId)).rejects.toMatchObject(unknown(sessionId));
|
||||
await storeTranscript(sessionId);
|
||||
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("answers saved while a resume still starts the session, without waiting for it", async () => {
|
||||
const sessionId = randomUUID();
|
||||
await storeTranscript(sessionId);
|
||||
let started!: () => void;
|
||||
const starting = new Promise<void>((resolve) => {
|
||||
started = resolve;
|
||||
});
|
||||
const initialization = sdk.initializationResult;
|
||||
sdk.initializationResult = async () => {
|
||||
await starting;
|
||||
return initialization();
|
||||
};
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const resumed = agent.request(v2.methods.agent.session.resume, { sessionId, cwd });
|
||||
await vi.waitFor(() => expect(sdk.queryOptions).toHaveLength(1));
|
||||
|
||||
expect(await status(agent, sessionId)).toEqual({ status: "saved" });
|
||||
|
||||
started();
|
||||
await resumed;
|
||||
expect(await status(agent, sessionId)).toMatchObject({ status: "open" });
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a session that waits for a permission as requires_action", async () => {
|
||||
let answer!: (response: v2.RequestPermissionResponse) => void;
|
||||
scriptTurns(async function* (options) {
|
||||
await options.canUseTool!("Bash", { command: "ls" }, {
|
||||
signal: new AbortController().signal,
|
||||
suggestions: [],
|
||||
toolUseID: "toolu_ls",
|
||||
requestId: randomUUID(),
|
||||
} as Parameters<NonNullable<Options["canUseTool"]>>[2]);
|
||||
yield result(options);
|
||||
yield idleState(options);
|
||||
});
|
||||
const client = v2Client({
|
||||
answerPermission: (() =>
|
||||
new Promise<v2.RequestPermissionResponse>((resolve) => {
|
||||
answer = resolve;
|
||||
})) as unknown as () => v2.RequestPermissionResponse,
|
||||
});
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("go") });
|
||||
await vi.waitFor(() => expect(client.permissionRequests).toHaveLength(1));
|
||||
await vi.waitFor(() =>
|
||||
expect(turnTrace(client.sessionUpdates).at(-1)).toBe("requires_action"),
|
||||
);
|
||||
|
||||
expect(await agent.request(v2.methods.agent.session.status, { sessionId })).toEqual({
|
||||
status: "open",
|
||||
state: { state: "requires_action", background: [] },
|
||||
});
|
||||
|
||||
answer({ outcome: { outcome: "selected", optionId: "allow-once" } });
|
||||
await vi.waitFor(() => expect(turnTrace(client.sessionUpdates).at(-1)).toMatch(/^idle/));
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
// The status request added no state to the turn's.
|
||||
expect(turnTrace(client.sessionUpdates)).toEqual([
|
||||
"user_message",
|
||||
"running",
|
||||
"requires_action",
|
||||
"running",
|
||||
"idle end_turn",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/set_title", () => {
|
||||
it("titles an active session through Claude Code and publishes the stored title", async () => {
|
||||
const renameSession = vi.fn(sdkDefaults.renameSession);
|
||||
sdk.renameSession = renameSession;
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
client.sessionUpdates.length = 0;
|
||||
const response = await agent.request(v2.methods.agent.session.setTitle, {
|
||||
sessionId,
|
||||
title: " Investigate\n OAuth timeout ",
|
||||
});
|
||||
expect(response).toEqual({ title: "Investigate OAuth timeout" });
|
||||
expect(renameSession).toHaveBeenCalledWith("Investigate OAuth timeout", sessionId);
|
||||
expect(client.sessionUpdates).toEqual([
|
||||
{
|
||||
sessionId,
|
||||
update: { sessionUpdate: "session_info_update", title: "Investigate OAuth timeout" },
|
||||
},
|
||||
]);
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("titles the query that a resume started while the rename ran", async () => {
|
||||
let release!: () => void;
|
||||
const renameSession = vi.fn(async (_title: string, _sessionId?: string) => {
|
||||
if (renameSession.mock.calls.length === 1) {
|
||||
await new Promise<void>((resolve) => (release = resolve));
|
||||
}
|
||||
});
|
||||
sdk.renameSession = renameSession;
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
const renamed = agent.request(v2.methods.agent.session.setTitle, {
|
||||
sessionId,
|
||||
title: "Mine",
|
||||
});
|
||||
await vi.waitFor(() => expect(renameSession).toHaveBeenCalledTimes(1));
|
||||
await agent.request(v2.methods.agent.session.close, { sessionId });
|
||||
await agent.request(v2.methods.agent.session.resume, { sessionId, cwd });
|
||||
release();
|
||||
expect(await renamed).toEqual({ title: "Mine" });
|
||||
expect(renameSession.mock.calls).toEqual([
|
||||
["Mine", sessionId],
|
||||
["Mine", sessionId],
|
||||
]);
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("titles a stored session that is not active in its transcript", async () => {
|
||||
const sessionId = randomUUID();
|
||||
const dir = path.join(tempDir, "claude", "projects", encodeProjectPath(cwd));
|
||||
await mkdir(dir, { recursive: true });
|
||||
const file = path.join(dir, `${sessionId}.jsonl`);
|
||||
const prompt = { type: "user", sessionId, cwd, message: { role: "user", content: "hi" } };
|
||||
await writeFile(file, JSON.stringify(prompt) + "\n");
|
||||
const { app, authUpdate } = v2Client();
|
||||
await app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
expect(
|
||||
await agent.request(v2.methods.agent.session.setTitle, { sessionId, title: "Stored" }),
|
||||
).toEqual({ title: "Stored" });
|
||||
await authUpdate(1);
|
||||
});
|
||||
const records = (await readFile(file, "utf8"))
|
||||
.trim()
|
||||
.split("\n")
|
||||
.slice(-2)
|
||||
.map((line) => JSON.parse(line));
|
||||
expect(records).toEqual([
|
||||
{ type: "custom-title", customTitle: "Stored", sessionId },
|
||||
{ type: "agent-name", agentName: "Stored", sessionId },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["an unknown session", { sessionId: randomUUID(), title: "Title" }, -32002],
|
||||
["a title that is empty once normalized", { sessionId: randomUUID(), title: " \n " }, -32602],
|
||||
])("refuses %s", async (_case, params, code) => {
|
||||
const { app, authUpdate } = v2Client();
|
||||
await app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
await expect(
|
||||
agent.request(v2.methods.agent.session.setTitle, params),
|
||||
).rejects.toMatchObject({ code });
|
||||
await authUpdate(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("offers a resumed Fable 5.2 session's model as `fable`, as a new session lists it", async () => {
|
||||
// On resume, the CLI lists the transcript's Fable row under its id, with no `fable` row.
|
||||
const row = (value: string, resolvedModel: string, displayName: string) => ({
|
||||
@@ -3969,47 +4212,60 @@ describe("ACP v2 idle compaction", () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Prompts "hi" in a session created with `meta`, answers it with a large
|
||||
* one-hour request, and waits 54 minutes. Returns the prompts Claude Code
|
||||
* received and the client.
|
||||
*/
|
||||
async function promptAndIdle(meta?: Record<string, unknown>) {
|
||||
const received: string[] = [];
|
||||
sdk.run = async function* (prompts, options) {
|
||||
const input = prompts[Symbol.asyncIterator]();
|
||||
for (;;) {
|
||||
const { value, done } = await input.next();
|
||||
if (done) return;
|
||||
received.push((value.message.content as Array<{ text: string }>)[0].text);
|
||||
if (received.length === 1) {
|
||||
yield {
|
||||
type: "user",
|
||||
message: value.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: value.uuid,
|
||||
session_id: options.sessionId,
|
||||
isReplay: true,
|
||||
};
|
||||
yield largeOneHourRequest(options);
|
||||
} else {
|
||||
yield { type: "system", subtype: "status", status: "compacting", uuid: randomUUID() };
|
||||
}
|
||||
yield result(options);
|
||||
yield idleState(options);
|
||||
}
|
||||
};
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, {
|
||||
cwd,
|
||||
_meta: meta,
|
||||
});
|
||||
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("hi") });
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await vi.advanceTimersByTimeAsync(54 * 60_000);
|
||||
for (let i = 0; i < 20 && received.length < 2; i++) await vi.advanceTimersByTimeAsync(0);
|
||||
for (let i = 0; i < 20 && turnTrace(client.sessionUpdates).length < 6; i++) {
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
}
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
return { received, client };
|
||||
}
|
||||
|
||||
/** The session is idle for 54 minutes after a large one-hour request, and the adapter's `/compact` runs. */
|
||||
it("reports the compaction that the adapter starts as running, then idle, with no user_message", async () => {
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
|
||||
try {
|
||||
const received: string[] = [];
|
||||
sdk.run = async function* (prompts, options) {
|
||||
const input = prompts[Symbol.asyncIterator]();
|
||||
for (;;) {
|
||||
const { value, done } = await input.next();
|
||||
if (done) return;
|
||||
received.push((value.message.content as Array<{ text: string }>)[0].text);
|
||||
if (received.length === 1) {
|
||||
yield {
|
||||
type: "user",
|
||||
message: value.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: value.uuid,
|
||||
session_id: options.sessionId,
|
||||
isReplay: true,
|
||||
};
|
||||
yield largeOneHourRequest(options);
|
||||
} else {
|
||||
yield { type: "system", subtype: "status", status: "compacting", uuid: randomUUID() };
|
||||
}
|
||||
yield result(options);
|
||||
yield idleState(options);
|
||||
}
|
||||
};
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("hi") });
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await vi.advanceTimersByTimeAsync(54 * 60_000);
|
||||
for (let i = 0; i < 20 && received.length < 2; i++) await vi.advanceTimersByTimeAsync(0);
|
||||
for (let i = 0; i < 20 && turnTrace(client.sessionUpdates).length < 6; i++) {
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
}
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
const { received, client } = await promptAndIdle();
|
||||
|
||||
expect(received).toEqual(["hi", "/compact"]);
|
||||
expect(turnTrace(client.sessionUpdates)).toEqual([
|
||||
@@ -4024,6 +4280,20 @@ describe("ACP v2 idle compaction", () => {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
/** The client's `settings` option reaches the CLI as `--settings`, and idle compaction reads it too. */
|
||||
it("does not compact a session whose client turned idle compaction off in the settings option", async () => {
|
||||
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
|
||||
try {
|
||||
const { received } = await promptAndIdle({
|
||||
claudeCode: { options: { settings: { idleCompaction: false } } },
|
||||
});
|
||||
|
||||
expect(received).toEqual(["hi"]);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Claude Code runs model cycles that no prompt started, for example its
|
||||
@@ -5671,6 +5941,57 @@ describe("ACP v2 requests that subagents make", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("refuses session/status of a subagent's session, which does not advertise it", async () => {
|
||||
let finish!: () => void;
|
||||
const finished = new Promise<void>((resolve) => {
|
||||
finish = resolve;
|
||||
});
|
||||
scriptTurns(async function* (options) {
|
||||
yield toolUseMessage(options, "toolu_agent-1", "Agent", {
|
||||
description: "Explore the project",
|
||||
prompt: "Look around",
|
||||
subagent_type: "Explore",
|
||||
});
|
||||
yield taskStarted(options, "agent-1", { prompt: "Look around" });
|
||||
await finished;
|
||||
yield taskNotification(options, "agent-1", {});
|
||||
yield result(options);
|
||||
yield idleState(options);
|
||||
});
|
||||
const client = v2Client();
|
||||
await client.app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
const { sessionId } = await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
await agent.request(v2.methods.agent.session.prompt, { sessionId, prompt: text("hi") });
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
client.sessionUpdates.some(
|
||||
(notification) =>
|
||||
notification.sessionId === "agent-1" &&
|
||||
notification.update.sessionUpdate === "state_update",
|
||||
),
|
||||
).toBe(true),
|
||||
);
|
||||
|
||||
await expect(
|
||||
agent.request(v2.methods.agent.session.status, { sessionId: "agent-1" }),
|
||||
).rejects.toMatchObject({
|
||||
code: -32602,
|
||||
message: expect.stringContaining("agent-1 is a subagent session"),
|
||||
});
|
||||
|
||||
finish();
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
client
|
||||
.updates("state_update")
|
||||
.some((update) => (update as { state: string }).state === "idle"),
|
||||
).toBe(true),
|
||||
);
|
||||
await client.authUpdate(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("sends a live child's configuration again after a replay's response, when the replay sent another", async () => {
|
||||
scriptTurns(async function* (options) {
|
||||
yield toolUseMessage(options, "toolu_agent-1", "Agent", {
|
||||
@@ -9355,6 +9676,98 @@ describe("ACP v2 auth", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("advertises auth.status, sends auth/status_update, and answers auth/status", async () => {
|
||||
const { app, authUpdate, acpAuthUpdates } = v2Client();
|
||||
const status = await app.connectWith(connectRouter(), async (agent) => {
|
||||
const response = await initializeV2(agent);
|
||||
expect(response.capabilities?.auth?.status).toEqual({});
|
||||
await authUpdate(1);
|
||||
return agent.request(v2.methods.agent.auth.status, {});
|
||||
});
|
||||
|
||||
expect(status).toMatchObject({
|
||||
state: "authenticated",
|
||||
accounts: [{ active: true, email: "user@example.com" }],
|
||||
});
|
||||
// The same payload as the notification, and the unchanged read of
|
||||
// `auth/status` sent no second one.
|
||||
expect(acpAuthUpdates).toEqual([status]);
|
||||
});
|
||||
|
||||
it.each(["max_5x", undefined, "future_tier"])(
|
||||
"keeps Ultra across session reads and refreshes the label for CLI tier %j",
|
||||
async (nextTier) => {
|
||||
let planTier: string | undefined = "max_20x";
|
||||
execFileSpy.mockImplementation((...invocation: unknown[]) => {
|
||||
const callback = invocation[invocation.length - 1] as (...a: unknown[]) => void;
|
||||
callback(null, {
|
||||
stdout: JSON.stringify({ ...JSON.parse(CLI_SUBSCRIPTION), planTier }),
|
||||
stderr: "",
|
||||
});
|
||||
});
|
||||
const account = {
|
||||
apiProvider: "firstParty",
|
||||
subscriptionType: "Claude Max",
|
||||
email: "user@example.com",
|
||||
};
|
||||
const initializationResult = sdk.initializationResult;
|
||||
sdk.initializationResult = async () => ({ ...(await initializationResult()), account });
|
||||
sdk.accountInfo = async () => account;
|
||||
const { app, authUpdate, acpAuthUpdates } = v2Client();
|
||||
await app.connectWith(connectRouter(), async (agent) => {
|
||||
await initializeV2(agent);
|
||||
expect((await authUpdate(1)).authStatus.label).toBe("Claude Ultra");
|
||||
await agent.request(v2.methods.agent.session.new, { cwd });
|
||||
expect(await agent.request(v2.methods.agent.auth.status, {})).toMatchObject({
|
||||
accounts: [{ label: "Claude Ultra" }],
|
||||
});
|
||||
expect(
|
||||
acpAuthUpdates.every((update) => update.accounts?.[0].label === "Claude Ultra"),
|
||||
).toBe(true);
|
||||
|
||||
planTier = nextTier;
|
||||
await agent.request(v2.methods.agent.auth.status, {});
|
||||
await vi.waitFor(() =>
|
||||
expect(acpAuthUpdates.at(-1)).toMatchObject({
|
||||
accounts: [{ label: "Claude Max", _meta: { claudeCode: { plan: "max" } } }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* In a remote environment with --hide-claude-auth, initialize offers the
|
||||
* console login as a terminal method and hides the TUI subscription login.
|
||||
*/
|
||||
it("offers console-login in a remote environment with hide-claude-auth", async () => {
|
||||
vi.stubGlobal("process", {
|
||||
...process,
|
||||
argv: ["--hide-claude-auth"],
|
||||
env: { ...process.env, SSH_CONNECTION: "192.168.1.1 12345 192.168.1.2 22" },
|
||||
});
|
||||
try {
|
||||
const { app } = v2Client();
|
||||
const response = await app.connectWith(connectRouter(), (agent) =>
|
||||
agent.request(v2.methods.agent.initialize, {
|
||||
protocolVersion: v2.PROTOCOL_VERSION,
|
||||
info: V2_CLIENT_INFO,
|
||||
capabilities: { auth: { terminal: {} } },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.authMethods).toEqual([
|
||||
expect.objectContaining({
|
||||
methodId: "console-login",
|
||||
type: "terminal",
|
||||
args: ["--cli", "auth", "login", "--console"],
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
});
|
||||
|
||||
it("logs in to a gateway with auth/login and out with auth/logout", async () => {
|
||||
const { app, authUpdate } = v2Client();
|
||||
await app.connectWith(connectRouter(), async (agent) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user