mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
Compare commits
5 Commits
v0.95.1-pr
...
v0.96.1-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e369479f93 | ||
|
|
c26e640915 | ||
|
|
f6958db1ee | ||
|
|
2f3c6bb7b3 | ||
|
|
9a8bf71d45 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.95.0"
|
||||
".": "0.96.0"
|
||||
}
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [0.96.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.95.0...v0.96.0) (2026-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* report SendMessage delegations as linked session messages ([#1424](https://github.com/agentclientprotocol/claude-agent-acp/issues/1424)) ([739dde3](https://github.com/agentclientprotocol/claude-agent-acp/commit/739dde3ae5bb848bdb90e2c9150e2cc4687d480c))
|
||||
* report the model of each native subagent ([#1432](https://github.com/agentclientprotocol/claude-agent-acp/issues/1432)) ([c319e5e](https://github.com/agentclientprotocol/claude-agent-acp/commit/c319e5e542aabe353bb2c5e177e4c5853392b8c4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not hold an AIR load replay until the CLI starts ([#1433](https://github.com/agentclientprotocol/claude-agent-acp/issues/1433)) ([eb9d63e](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb9d63e5fde9b8fd8fe692dfb3280faba6626a42))
|
||||
* refine RFD child models after a load, and retry a failed model update ([#1434](https://github.com/agentclientprotocol/claude-agent-acp/issues/1434)) ([9a8bf71](https://github.com/agentclientprotocol/claude-agent-acp/commit/9a8bf71d454fa85d03f55d4101a77e3ea7a149d0))
|
||||
* send subagents' current state after a load response ([#1423](https://github.com/agentclientprotocol/claude-agent-acp/issues/1423)) ([17a967b](https://github.com/agentclientprotocol/claude-agent-acp/commit/17a967b4dab78bbb9cf9fedae60e6821578dc5cb))
|
||||
* update to Claude Code 2.1.310 ([#1435](https://github.com/agentclientprotocol/claude-agent-acp/issues/1435)) ([2f3c6bb](https://github.com/agentclientprotocol/claude-agent-acp/commit/2f3c6bb7b3451a00cd77d7bb31c09e690ca220b3))
|
||||
|
||||
## [0.95.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.94.0...v0.95.0) (2026-10-23)
|
||||
|
||||
|
||||
|
||||
104
package-lock.json
generated
104
package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.0.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.309",
|
||||
"@agentclientprotocol/sdk": "2.0.2",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.310",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
@@ -18,7 +18,7 @@
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "1.1.0",
|
||||
"@anthropic-ai/sdk": "1.3.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.5",
|
||||
@@ -38,31 +38,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.0.1.tgz",
|
||||
"integrity": "sha512-v4E/NcBTGFEKmorGso8ysAjJTkNh5UMJPP4aDznyMNmnP9t10g/aCCo/Djy7RYaYZy5ZROFK3G5cEj9tDgGOTQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-2.0.2.tgz",
|
||||
"integrity": "sha512-jLywRB42hZMRMc4aUgt0uRjkY9FQnMdybv4XgLl+ddi75QuJn8Vbl0+CHcVHJDC0+WDfi1KQXRWtHaR5vp6gHw==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.309.tgz",
|
||||
"integrity": "sha512-Tou0fXBkxKJDcgT09v3Iv/Jd7bNgapgfAiHxGPctAADPFQhidfhZbcv7at1cVL0Lon5sbs+OzP/pEOigw2TI0A==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.310.tgz",
|
||||
"integrity": "sha512-fo+sr+9Ks73P001f6XKRQlyzfIBAJMgXArLUkH5Tu98NikRDZeGtFQVj23d5/hj2ijpFgzffrr4fHywY9qUkoQ==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.309",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.309"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.310",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.310"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -71,9 +71,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.309.tgz",
|
||||
"integrity": "sha512-qC93iDGEioaXqWsYazcZzNv3hd8d4VHkCFgb+AzvbkCWOzZHnRu1EqF4Iz5kCTLDs4VdMvCa1NkzlUwtTedBVw==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.310.tgz",
|
||||
"integrity": "sha512-gpzMjDZdcdA2Webuqm9j76exG8yNK/755ZYWbS3MM4vVBlAsm+enEO+hhNt0Mx1VMil28RWetvTO4ZJ3YODVMA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -84,9 +84,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.309.tgz",
|
||||
"integrity": "sha512-RvP0UOcYxn42c1lgByoMufHaMOH86U2ThGGFupOxBKATY4Q6WJd6M1CukQ/qygfgJ8lcxvK8GOOd3SIny4kQ8Q==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.310.tgz",
|
||||
"integrity": "sha512-ZRQ4ZrzE2OOfR+Fx8h+CM4NKh0UiYqlh5TowP7HUhn5KUoYMjoSR/xIhix8ow42X2IDiBy0FjEVaA/Awhu2KAw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -97,9 +97,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.309.tgz",
|
||||
"integrity": "sha512-+8rQwzycuXTW2e/C+yotvP/SDiEDHxItdPvIriGAf2ixh7nPRjVuzHOj1tamgVO0J8nmpPvIAO4edzJ7VZCcAg==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.310.tgz",
|
||||
"integrity": "sha512-l6SPgUmJknjx+6/Pb7OR353fm3xBNAWqodFoC/Cflg0yMcLfz9iwNIc3Drgos2DqfHnid+ODqDzSO93AgQ7RmA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -113,9 +113,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.309.tgz",
|
||||
"integrity": "sha512-14ZD9hdUeobw+jr166YvCZyZwSYG3hDWi5S4yQxpSCrRuY/1z4Kl1AfymUlugiN3UT4Q1rif32K6GnjRJBvQdA==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.310.tgz",
|
||||
"integrity": "sha512-viXuvvtli3pfHzTEI+oeJzk3nldkfHvj757Xh/uAeximUT9ANZIq4SMZw2cSqbhASjni0qcO6B1x4BGx2LuQpw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -129,9 +129,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.309.tgz",
|
||||
"integrity": "sha512-96G8/uTArlvTTg0qGtiP36W76Ug/YT0gMBh8KX7J2RigdOTrk6QkHtUeCzrw6yff5hEIzecN7EWNw6uh9USo4A==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.310.tgz",
|
||||
"integrity": "sha512-jx7awHPoofXuE6cRRQSzLUFyW6aUXbYD9AtnNIDUHmmPPrgJ9nSnqjPaqcNhOdaZH7onUUujEAq26N2tx29fYg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -145,9 +145,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.309.tgz",
|
||||
"integrity": "sha512-9ViOt4MGMiE2h8V+JUPR5l9F0ZTFuhKC3jawIL/MrMfYY1m2Gt3gcMuP7h7YK914a4tmpXrmITWrpTOu1WOZQQ==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.310.tgz",
|
||||
"integrity": "sha512-/qFgN5LcofB8PayxBXoa+xDxkYPPQeo/eCyN0DsyyeI67ifH/l49pz9J+sH0+ctDe9und0vMCJPzng70bx2K+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -161,9 +161,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.309.tgz",
|
||||
"integrity": "sha512-lDsmw5hguDF7KNMG2OvvKlG0Zgs9bKlrX9Gs2Rv/NjLVl0PIy1TUsh7byOsUR4pXwoMU1toyU1CKqBc/Au8hHw==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.310.tgz",
|
||||
"integrity": "sha512-tRmDMhZpoWAIrZxFBTh6vyZB8W5gldU5/Rfo+cyq/OvBKTxxbim+/0tIa9QlJQncoVLXVN9eySYeeB9KMjio7w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -174,9 +174,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.309",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.309.tgz",
|
||||
"integrity": "sha512-otGHQimz6F++6SajGqSAKBclRPGTn60cx+8u3dnhMa5yovKZ5SgChFgstFsiDIWr/ZBqpJysvFemSgHX61mlmQ==",
|
||||
"version": "0.3.310",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.310.tgz",
|
||||
"integrity": "sha512-zvrIApxPccTJc6gCOtw5hj27QjWhhTi3z5x5iMmf9f0LZAKR+9mrgENVeFL7PP7zRGdYVhhX2SahbzxN83L/eA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -187,9 +187,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.1.0.tgz",
|
||||
"integrity": "sha512-Op/fKMsfVwFCfObc87leLOFlORE0RjOvLzRzPCWWLI7Ww6bobpKR6YmYC8ZYgulRh+m2a/3/61VUXoWHFtCXIw==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.3.0.tgz",
|
||||
"integrity": "sha512-k7z9BlLLPxfZH+ov7dS6EnaN/wiZeV8YNT1LZfrpAHIy7oY7pHr4UzirF9f2OlHtFEnRBOX+AZQ91Y5fK+TPTw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.4.1",
|
||||
@@ -1250,19 +1250,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
|
||||
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
|
||||
"integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^2.0.0",
|
||||
"content-type": "^2.1.0",
|
||||
"debug": "^4.4.3",
|
||||
"http-errors": "^2.0.1",
|
||||
"iconv-lite": "^0.7.2",
|
||||
"iconv-lite": "^0.7.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.15.2",
|
||||
"qs": "^6.16.1",
|
||||
"raw-body": "^3.0.2",
|
||||
"type-is": "^2.1.0"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -65,13 +65,13 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.0.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.309",
|
||||
"@agentclientprotocol/sdk": "2.0.2",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.310",
|
||||
"diff": "9.0.0",
|
||||
"zod": "4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "1.1.0",
|
||||
"@anthropic-ai/sdk": "1.3.0",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@tsconfig/node22": "22.0.6",
|
||||
"@types/node": "26.6.5",
|
||||
|
||||
@@ -3116,7 +3116,10 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
timing.phase("session-ready");
|
||||
const replayed = await replay;
|
||||
if (!replayed.ok) throw replayed.error;
|
||||
if (!replayed.ok) {
|
||||
this.replayedChildModels.delete(params.sessionId);
|
||||
throw replayed.error;
|
||||
}
|
||||
return {
|
||||
sessionId: response.sessionId,
|
||||
modes: response.modes,
|
||||
@@ -8197,6 +8200,7 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
|
||||
let content: typeof message.message.content;
|
||||
let toolUseResult = message.type === "user" ? message.tool_use_result : undefined;
|
||||
if (
|
||||
message.type === "assistant" &&
|
||||
(message.parent_tool_use_id === null || forwardsSubagentText())
|
||||
@@ -8217,6 +8221,20 @@ export class ClaudeAcpAgent {
|
||||
(item) => item.type !== "text" && item.type !== "thinking",
|
||||
);
|
||||
streamedBlocksOf(message.parent_tool_use_id).length = 0;
|
||||
} else if (
|
||||
message.parent_tool_use_id !== null &&
|
||||
Array.isArray(message.message.content)
|
||||
) {
|
||||
// A foreground fork inherits a placeholder result for its own
|
||||
// launch. Only the launching side can complete that Agent call
|
||||
// with the fork's actual report.
|
||||
const launchId = message.parent_tool_use_id;
|
||||
content = message.message.content.filter(
|
||||
(block) => block.type !== "tool_result" || block.tool_use_id !== launchId,
|
||||
);
|
||||
// The message-level sidecar cannot describe a remaining result
|
||||
// after its launch result was removed.
|
||||
if (content.length !== message.message.content.length) toolUseResult = undefined;
|
||||
} else {
|
||||
content = message.message.content;
|
||||
}
|
||||
@@ -8234,7 +8252,7 @@ export class ClaudeAcpAgent {
|
||||
);
|
||||
const backgroundBashTask = backgroundBashTaskFromToolResult(
|
||||
content,
|
||||
message.tool_use_result,
|
||||
toolUseResult,
|
||||
session.toolUseCache,
|
||||
);
|
||||
if (backgroundBashTask) await asyncTasks.taskBackgrounded(backgroundBashTask);
|
||||
@@ -8263,7 +8281,7 @@ export class ClaudeAcpAgent {
|
||||
asyncTasks,
|
||||
backgroundBashTask,
|
||||
);
|
||||
const resumedAgentId = resumedNativeSubagentId(message.tool_use_result);
|
||||
const resumedAgentId = resumedNativeSubagentId(toolUseResult);
|
||||
if (resumedAgentId) {
|
||||
const resume = sendMessageResume(
|
||||
session.toolUseCache,
|
||||
@@ -8304,7 +8322,7 @@ export class ClaudeAcpAgent {
|
||||
emittedToolCalls: session.emittedToolCalls,
|
||||
toolCallFields: toolCallFieldsOf(session),
|
||||
messageId: messageIdForGrouping(message),
|
||||
toolUseResult: message.type === "user" ? message.tool_use_result : undefined,
|
||||
toolUseResult,
|
||||
// On the wire since CLI 2.1.216 but not in SDKUserMessage's
|
||||
// type, hence the cast. Validated by parseToolResultMeta.
|
||||
toolResultMeta:
|
||||
@@ -9449,7 +9467,8 @@ export class ClaudeAcpAgent {
|
||||
? new RfdSubagentReplay(
|
||||
rfdHistory,
|
||||
(notification) => this.client.sessionUpdate(asSdkSessionNotification(notification)),
|
||||
replayModels,
|
||||
this.subagentModels(sessionId),
|
||||
!pending,
|
||||
)
|
||||
: undefined;
|
||||
rfdReplay?.scan(messages, sessionId);
|
||||
|
||||
@@ -90,6 +90,8 @@ export type SubagentModels = {
|
||||
type ChildConfiguration = {
|
||||
options: Map<string, SessionConfigOption>;
|
||||
published?: string;
|
||||
/** The `model` option value and name that the client last got. */
|
||||
publishedModel?: ModelEntry;
|
||||
publishing: Promise<void>;
|
||||
};
|
||||
|
||||
@@ -285,13 +287,13 @@ export class NativeSubagentRuntime {
|
||||
if (!entry) return;
|
||||
// Every response of a child reports its model: most repeat the model that
|
||||
// the client already has, and need no routing.
|
||||
// A publication that failed sent nothing, so only the published model counts.
|
||||
const child = this.childByParentToolUse.get(parentToolUseId!);
|
||||
const current = child?.announced
|
||||
? this.configBySession.get(child.sessionId)?.options.get(MODEL_CONFIG_ID)
|
||||
: undefined;
|
||||
if (current?.type === "select" && current.currentValue === entry.value) {
|
||||
const shown = current.options[0];
|
||||
if (shown && "name" in shown && shown.name === entry.name) return;
|
||||
if (
|
||||
child?.announced &&
|
||||
sameModelEntry(this.configBySession.get(child.sessionId)?.publishedModel, entry)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await deliver({
|
||||
sessionId: this.rootSessionId,
|
||||
@@ -384,6 +386,11 @@ export class NativeSubagentRuntime {
|
||||
},
|
||||
});
|
||||
config.published = snapshot;
|
||||
const model = configOptions.find((option) => option.id === MODEL_CONFIG_ID);
|
||||
const shown = model?.type === "select" ? model.options[0] : undefined;
|
||||
if (model && shown && "name" in shown && typeof model.currentValue === "string") {
|
||||
config.publishedModel = { value: model.currentValue, name: shown.name };
|
||||
}
|
||||
});
|
||||
// The caller receives the error; later observations must still be able to retry.
|
||||
config.publishing = publishing.catch(() => {});
|
||||
@@ -1418,12 +1425,15 @@ export class NativeSubagentRuntime {
|
||||
|
||||
private buffer(parentToolUseId: string, notification: AcpSessionNotification): void {
|
||||
const updates = this.pending.get(parentToolUseId);
|
||||
// A child reports its model with each response before its announcement.
|
||||
// Only the latest configuration waits, so they do not fill the buffer.
|
||||
if (notification.update.sessionUpdate === "config_option_update" && updates) {
|
||||
const index = updates.findIndex(
|
||||
({ update }) => update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
// A child reports its model with each response before its announcement
|
||||
// (see `reportModel`). Only the latest such report waits, so they do not
|
||||
// fill the buffer. Any other configuration update waits as it is.
|
||||
const modelOnly = ({ update }: AcpSessionNotification) =>
|
||||
update.sessionUpdate === "config_option_update" &&
|
||||
update.configOptions.length === 1 &&
|
||||
update.configOptions[0]?.id === MODEL_CONFIG_ID;
|
||||
if (updates && modelOnly(notification)) {
|
||||
const index = updates.findIndex(modelOnly);
|
||||
if (index >= 0) {
|
||||
updates[index] = notification;
|
||||
return;
|
||||
|
||||
@@ -112,6 +112,12 @@ export class RfdSubagentReplay {
|
||||
private readonly history: SubagentHistory,
|
||||
private readonly publish: Publish,
|
||||
private readonly models?: SubagentModels,
|
||||
/**
|
||||
* Whether the replay maps a model to the session's model list as it goes.
|
||||
* A load lacks the list until the CLI started, so it maps only in
|
||||
* {@link currentStates}, after the load response.
|
||||
*/
|
||||
private readonly mapsDuringReplay = true,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -189,7 +195,7 @@ export class RfdSubagentReplay {
|
||||
* whose model changes gets its `model` option again.
|
||||
*/
|
||||
async responseModel(owner: ReplayChild, model: string | undefined): Promise<void> {
|
||||
const entry = responseModelEntry(model, this.models);
|
||||
const entry = responseModelEntry(model, this.mapsDuringReplay ? this.models : undefined);
|
||||
if (!entry || !owner.announced) return;
|
||||
owner.responseModel = model;
|
||||
await this.sendModel(owner, entry);
|
||||
|
||||
@@ -3596,11 +3596,19 @@ describe("subagent transcript replay", () => {
|
||||
{ log: () => {}, error: () => {} },
|
||||
);
|
||||
await initializeClient(agent, AIR_NATIVE_SUBAGENTS);
|
||||
vi.mocked(getSessionMessages).mockResolvedValueOnce(replayHistory);
|
||||
// A load replays while the CLI starts: the session and its model list come later.
|
||||
await (
|
||||
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
|
||||
).replaySessionHistory("s1");
|
||||
agent as unknown as {
|
||||
replaySessionHistory(
|
||||
sessionId: string,
|
||||
messages: unknown[],
|
||||
pending: unknown,
|
||||
): Promise<void>;
|
||||
}
|
||||
).replaySessionHistory("s1", replayHistory, {
|
||||
state: { cwd: "/tmp", taskState: new Map(), messageIdToUuid: new Map() },
|
||||
stopped: false,
|
||||
});
|
||||
const child = "s1:replay-subagent:parent-agent-call";
|
||||
const models = () =>
|
||||
updates.flatMap(({ sessionId, update }) =>
|
||||
@@ -3877,7 +3885,14 @@ describe("subagent transcript replay", () => {
|
||||
/** The session's config options and model list, known after the response. */
|
||||
models?: { configOptions: SessionConfigOption[]; modelInfos: ModelInfo[] };
|
||||
},
|
||||
{ traceModels = false }: { traceModels?: boolean } = {},
|
||||
{
|
||||
traceModels = false,
|
||||
load = false,
|
||||
}: {
|
||||
traceModels?: boolean;
|
||||
/** Replay as a load does, while the CLI starts: without the session record. */
|
||||
load?: boolean;
|
||||
} = {},
|
||||
): Promise<string[]> {
|
||||
const configDir = await mkdtemp(path.join(os.tmpdir(), "claude-acp-rfd-replay-"));
|
||||
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
||||
@@ -3915,9 +3930,21 @@ describe("subagent transcript replay", () => {
|
||||
);
|
||||
await initializeClient(agent, capabilities);
|
||||
vi.mocked(getSessionInfo).mockResolvedValueOnce({ cwd: "/tmp/proj" } as any);
|
||||
const pending = load
|
||||
? {
|
||||
state: { cwd: "/tmp/proj", taskState: new Map(), messageIdToUuid: new Map() },
|
||||
stopped: false,
|
||||
}
|
||||
: undefined;
|
||||
await (
|
||||
agent as unknown as { replaySessionHistory(sessionId: string): Promise<void> }
|
||||
).replaySessionHistory(sessionId);
|
||||
agent as unknown as {
|
||||
replaySessionHistory(
|
||||
sessionId: string,
|
||||
messages?: unknown[],
|
||||
pending?: unknown,
|
||||
): Promise<void>;
|
||||
}
|
||||
).replaySessionHistory(sessionId, undefined, pending);
|
||||
const responseAt = updates.length;
|
||||
if (afterResponse) {
|
||||
// Stands in for the live session's runtime, which knows its running children.
|
||||
@@ -4268,7 +4295,7 @@ describe("subagent transcript replay", () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ traceModels: true },
|
||||
{ traceModels: true, load: true },
|
||||
);
|
||||
|
||||
const opus = JSON.stringify([{ value: "opus", name: "Opus 5" }]);
|
||||
@@ -15937,6 +15964,132 @@ describe("usage_update computation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("foreground fork tool results", () => {
|
||||
it.each([
|
||||
["plain", {}, null],
|
||||
["AIR", AIR_CLIENT_CAPABILITIES, null],
|
||||
["plain nested", {}, "toolu_outer"],
|
||||
["AIR nested", AIR_CLIENT_CAPABILITIES, "toolu_outer"],
|
||||
] as const)(
|
||||
"waits for the launching side's result for a %s client",
|
||||
async (_, capabilities, parent) => {
|
||||
const updates: SessionNotification[] = [];
|
||||
const logger = { log: vi.fn(), error: vi.fn() };
|
||||
const agent = new ClaudeAcpAgent(
|
||||
{
|
||||
sessionUpdate: async (notification: SessionNotification) => {
|
||||
updates.push(notification);
|
||||
},
|
||||
} as unknown as AcpClient,
|
||||
logger,
|
||||
);
|
||||
await initializeClient(agent, capabilities);
|
||||
const input = new Pushable<any>();
|
||||
const assistant = (content: unknown[], parentToolUseId: string | null) => ({
|
||||
type: "assistant",
|
||||
parent_tool_use_id: parentToolUseId,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
message: {
|
||||
id: randomUUID(),
|
||||
role: "assistant",
|
||||
model: "claude-sonnet-4-6",
|
||||
content,
|
||||
usage: successfulResultMessage().usage,
|
||||
},
|
||||
});
|
||||
const user = (content: unknown[], parentToolUseId: string | null) => ({
|
||||
type: "user",
|
||||
parent_tool_use_id: parentToolUseId,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
message: { role: "user", content },
|
||||
});
|
||||
const toolResult = (id: string, text: string) => ({
|
||||
type: "tool_result",
|
||||
tool_use_id: id,
|
||||
content: [{ type: "text", text }],
|
||||
});
|
||||
async function* messages() {
|
||||
const { value } = await input[Symbol.asyncIterator]().next();
|
||||
yield userEcho(value);
|
||||
yield assistant(
|
||||
[
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "toolu_fork",
|
||||
name: "Agent",
|
||||
input: {
|
||||
subagent_type: "fork",
|
||||
run_in_background: false,
|
||||
prompt: "Inspect the parser",
|
||||
},
|
||||
},
|
||||
],
|
||||
parent,
|
||||
);
|
||||
// The fork inherits a result for its own launch. It cannot complete
|
||||
// that call: the launching side sends the actual report later.
|
||||
yield user([toolResult("toolu_fork", "Fork started — that fork is you.")], "toolu_fork");
|
||||
yield assistant(
|
||||
[
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "toolu_child",
|
||||
name: "Agent",
|
||||
input: { subagent_type: "general-purpose", prompt: "Read the parser" },
|
||||
},
|
||||
],
|
||||
"toolu_fork",
|
||||
);
|
||||
// Keep results of tools the fork itself called, even in a message
|
||||
// that also carries the copied launch result.
|
||||
yield {
|
||||
...user(
|
||||
[
|
||||
toolResult("toolu_fork", "Fork started — processing in background"),
|
||||
toolResult("toolu_child", "child report"),
|
||||
],
|
||||
"toolu_fork",
|
||||
),
|
||||
// Filtering must not attribute the inherited launch sidecar to
|
||||
// the one result that remains in this message.
|
||||
tool_use_result: {
|
||||
status: "completed",
|
||||
content: [{ type: "text", text: "inherited launch report" }],
|
||||
},
|
||||
};
|
||||
yield user([toolResult("toolu_fork", "fork report")], parent);
|
||||
yield successfulResultMessage();
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
agent.sessions["test-session"] = mockSessionState({ query: wrapQuery(messages()), input });
|
||||
|
||||
await expect(
|
||||
agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "Inspect it" }] }),
|
||||
).resolves.toMatchObject({ stopReason: "end_turn" });
|
||||
|
||||
const completed = updates.flatMap(({ update }) =>
|
||||
update.sessionUpdate === "tool_call_update" && update.status === "completed"
|
||||
? [{ id: update.toolCallId, content: update.content, rawOutput: update.rawOutput }]
|
||||
: [],
|
||||
);
|
||||
expect(completed.map(({ id }) => id)).toEqual(["toolu_child", "toolu_fork"]);
|
||||
expect(completed.map(({ content }) => content)).toEqual([
|
||||
[{ type: "content", content: { type: "text", text: "child report" } }],
|
||||
[{ type: "content", content: { type: "text", text: "fork report" } }],
|
||||
]);
|
||||
if (!("_meta" in capabilities)) {
|
||||
expect(completed.map(({ rawOutput }) => rawOutput)).toEqual([
|
||||
[{ type: "text", text: "child report" }],
|
||||
[{ type: "text", text: "fork report" }],
|
||||
]);
|
||||
}
|
||||
expect(logger.error.mock.calls.flat().join("\n")).not.toContain("wasn't tracked");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("assembled assistant text fallback", () => {
|
||||
const ZERO_USAGE = {
|
||||
input_tokens: 0,
|
||||
|
||||
@@ -2678,6 +2678,44 @@ describe("NativeSubagentRuntime child models", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["rfd", "air"] as const)(
|
||||
"%s: sends a model again after its publication failed",
|
||||
async (form) => {
|
||||
const published: AcpSessionNotification[] = [];
|
||||
let failures = 1;
|
||||
const runtime = new NativeSubagentRuntime(
|
||||
form,
|
||||
"root",
|
||||
{},
|
||||
async (notification) => {
|
||||
if (notification.update.sessionUpdate === "config_option_update" && failures-- > 0) {
|
||||
throw new Error("client gone");
|
||||
}
|
||||
published.push(notification);
|
||||
},
|
||||
{ log: () => {} },
|
||||
undefined,
|
||||
undefined,
|
||||
models,
|
||||
);
|
||||
const deliver = async (notification: AcpSessionNotification) => {
|
||||
const routed = await runtime.route(notification, deliver);
|
||||
if (routed) published.push(routed);
|
||||
};
|
||||
await deliver(agentCall("launch-1", {}));
|
||||
await runtime.taskStarted(
|
||||
{ taskId: "worker-1", toolUseId: "launch-1", subagentType: "Explore" },
|
||||
deliver,
|
||||
);
|
||||
await expect(runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver)).rejects.toThrow(
|
||||
"client gone",
|
||||
);
|
||||
expect(modelUpdates(published)).toEqual([]);
|
||||
await runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver);
|
||||
expect(modelUpdates(published)).toEqual([["worker-1", "sonnet", "Sonnet 4.6"]]);
|
||||
},
|
||||
);
|
||||
|
||||
it("air: forgets the configuration of an ended child", async () => {
|
||||
const { runtime, published, deliver } = await spawn("air");
|
||||
await runtime.reportModel("launch-1", "claude-sonnet-4-6", deliver);
|
||||
|
||||
Reference in New Issue
Block a user