mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a1ac9b9d | ||
|
|
673b0507d7 | ||
|
|
ea00a4014a | ||
|
|
ebdd115b76 | ||
|
|
7c87fe7a8c |
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -32,3 +32,5 @@ jobs:
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
with:
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.16.1
|
||||
|
||||
- Update to @anthropic-ai/claude-agent-sdk@0.2.38
|
||||
- Fix incorrect paths for session/list
|
||||
- Fix available commands after loading a session
|
||||
- Make loading session more permissive for finding events
|
||||
- Fix overriding user-provided disallowedTools
|
||||
|
||||
## 0.16.0
|
||||
|
||||
- Update to @anthropic-ai/claude-agent-sdk@0.2.34
|
||||
|
||||
146
package-lock.json
generated
146
package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@zed-industries/claude-code-acp",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@zed-industries/claude-code-acp",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.14.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.34",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.38",
|
||||
"@modelcontextprotocol/sdk": "1.26.0",
|
||||
"diff": "8.0.3",
|
||||
"minimatch": "10.1.2"
|
||||
@@ -19,10 +19,10 @@
|
||||
"claude-code-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.73.0",
|
||||
"@types/node": "25.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@anthropic-ai/sdk": "0.74.0",
|
||||
"@types/node": "25.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.3.0",
|
||||
@@ -42,9 +42,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.2.34",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.34.tgz",
|
||||
"integrity": "sha512-QLHd3Nt7bGU7/YH71fXFaztM9fNxGGruzTMrTYJkbm5gYJl5ZyU2zGyoE5VpWC0e1QU0yYdNdBVgqSYDcJGufg==",
|
||||
"version": "0.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.38.tgz",
|
||||
"integrity": "sha512-U1vpf3rlSkw1qUlzC6CBibBA30ouQnla9JnuqYFLQ2zBb1U2NUCXIElrnV7RwWrI5e9ZKCHgR+1uaCwROONo7w==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -64,9 +64,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz",
|
||||
"integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==",
|
||||
"version": "0.74.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.74.0.tgz",
|
||||
"integrity": "sha512-srbJV7JKsc5cQ6eVuFzjZO7UR3xEPJqPamHFIe29bs38Ij2IripoAhC0S5NslNbaFUYqBKypmmpzMTpqfHEUDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1609,9 +1609,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz",
|
||||
"integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==",
|
||||
"version": "25.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz",
|
||||
"integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -1620,17 +1620,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
||||
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
|
||||
"integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/type-utils": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/type-utils": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -1643,23 +1643,23 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.55.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
|
||||
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1675,14 +1675,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
|
||||
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
|
||||
"integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.54.0",
|
||||
"@typescript-eslint/types": "^8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.55.0",
|
||||
"@typescript-eslint/types": "^8.55.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1697,14 +1697,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
|
||||
"integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1715,9 +1715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1732,15 +1732,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@@ -1757,9 +1757,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
|
||||
"integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1771,16 +1771,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
|
||||
"integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/project-service": "8.55.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1825,16 +1825,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
|
||||
"integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1849,13 +1849,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
|
||||
"integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3138,9 +3138,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz",
|
||||
"integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==",
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
|
||||
"integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
||||
12
package.json
12
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -61,16 +61,16 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.14.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.34",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.2.38",
|
||||
"@modelcontextprotocol/sdk": "1.26.0",
|
||||
"diff": "8.0.3",
|
||||
"minimatch": "10.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.73.0",
|
||||
"@types/node": "25.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@anthropic-ai/sdk": "0.74.0",
|
||||
"@types/node": "25.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.3.0",
|
||||
|
||||
173
src/acp-agent.ts
173
src/acp-agent.ts
@@ -79,26 +79,6 @@ import { fileURLToPath } from "node:url";
|
||||
export const CLAUDE_CONFIG_DIR =
|
||||
process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
||||
|
||||
/**
|
||||
* Decode a Claude project path encoding back to the original filesystem path.
|
||||
* Claude encodes paths by replacing path separators with dashes:
|
||||
* - Unix: "/Users/morse/project" -> "-Users-morse-project"
|
||||
* - Windows: "C:\Users\morse\project" -> "C-Users-morse-project"
|
||||
*/
|
||||
function decodeProjectPath(encodedPath: string): string {
|
||||
// Check if this looks like a Windows path (starts with drive letter pattern like "C-")
|
||||
const windowsDriveMatch = encodedPath.match(/^([A-Za-z])-/);
|
||||
if (windowsDriveMatch) {
|
||||
// Windows path: "C-Users-morse-project" -> "C:\Users\morse\project"
|
||||
const driveLetter = windowsDriveMatch[1];
|
||||
const rest = encodedPath.slice(2); // Skip "C-"
|
||||
return `${driveLetter}:\\${rest.replace(/-/g, "\\")}`;
|
||||
}
|
||||
|
||||
// Unix path: "-Users-morse-project" -> "/Users/morse/project"
|
||||
return encodedPath.replace(/-/g, "/");
|
||||
}
|
||||
|
||||
function sessionFilePath(cwd: string, sessionId: string): string {
|
||||
return path.join(CLAUDE_CONFIG_DIR, "projects", encodeProjectPath(cwd), `${sessionId}.jsonl`);
|
||||
}
|
||||
@@ -172,6 +152,7 @@ export type NewSessionMeta = {
|
||||
* Those parameters will be used and updated to work with ACP:
|
||||
* - hooks (merged with ACP's hooks)
|
||||
* - mcpServers (merged with ACP's mcpServers)
|
||||
* - disallowedTools (merged with ACP's disallowedTools)
|
||||
*/
|
||||
options?: Options;
|
||||
};
|
||||
@@ -278,14 +259,19 @@ export class ClaudeAcpAgent implements Agent {
|
||||
throw RequestError.authRequired();
|
||||
}
|
||||
|
||||
return await this.createSession(params, {
|
||||
const response = await this.createSession(params, {
|
||||
// Revisit these meta values once we support resume
|
||||
resume: (params._meta as NewSessionMeta | undefined)?.claudeCode?.options?.resume,
|
||||
});
|
||||
// Needs to happen after we return the session
|
||||
setTimeout(() => {
|
||||
this.sendAvailableCommandsUpdate(response.sessionId);
|
||||
}, 0);
|
||||
return response;
|
||||
}
|
||||
|
||||
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
||||
return await this.createSession(
|
||||
const response = await this.createSession(
|
||||
{
|
||||
cwd: params.cwd,
|
||||
mcpServers: params.mcpServers ?? [],
|
||||
@@ -296,6 +282,11 @@ export class ClaudeAcpAgent implements Agent {
|
||||
forkSession: true,
|
||||
},
|
||||
);
|
||||
// Needs to happen after we return the session
|
||||
setTimeout(() => {
|
||||
this.sendAvailableCommandsUpdate(response.sessionId);
|
||||
}, 0);
|
||||
return response;
|
||||
}
|
||||
|
||||
async unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
||||
@@ -309,14 +300,56 @@ export class ClaudeAcpAgent implements Agent {
|
||||
resume: params.sessionId,
|
||||
},
|
||||
);
|
||||
|
||||
// Needs to happen after we return the session
|
||||
setTimeout(() => {
|
||||
this.sendAvailableCommandsUpdate(response.sessionId);
|
||||
}, 0);
|
||||
return response;
|
||||
}
|
||||
|
||||
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
||||
/**
|
||||
* Find a session file by ID, first checking the given cwd's project directory,
|
||||
* then falling back to scanning all project directories.
|
||||
* Returns the absolute file path if found, or null if not found.
|
||||
*/
|
||||
private async findSessionFile(sessionId: string, cwd: string): Promise<string | null> {
|
||||
const fileName = `${sessionId}.jsonl`;
|
||||
|
||||
// Fast path: check the expected location based on cwd
|
||||
const expectedPath = sessionFilePath(cwd, sessionId);
|
||||
try {
|
||||
await fs.promises.access(sessionFilePath(params.cwd, params.sessionId));
|
||||
await fs.promises.access(expectedPath);
|
||||
return expectedPath;
|
||||
} catch {
|
||||
// Not found at expected path, scan all project directories
|
||||
}
|
||||
|
||||
const claudeDir = path.join(CLAUDE_CONFIG_DIR, "projects");
|
||||
try {
|
||||
const projectDirs = await fs.promises.readdir(claudeDir);
|
||||
for (const encodedPath of projectDirs) {
|
||||
const projectDir = path.join(claudeDir, encodedPath);
|
||||
const stat = await fs.promises.stat(projectDir);
|
||||
if (!stat.isDirectory()) continue;
|
||||
|
||||
const candidatePath = path.join(projectDir, fileName);
|
||||
try {
|
||||
await fs.promises.access(candidatePath);
|
||||
return candidatePath;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// projects directory doesn't exist or isn't readable
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
||||
const filePath = await this.findSessionFile(params.sessionId, params.cwd);
|
||||
if (!filePath) {
|
||||
throw new Error("Session not found");
|
||||
}
|
||||
|
||||
@@ -331,7 +364,12 @@ export class ClaudeAcpAgent implements Agent {
|
||||
},
|
||||
);
|
||||
|
||||
await this.replaySessionHistory(params.sessionId, params.cwd);
|
||||
await this.replaySessionHistory(params.sessionId, filePath);
|
||||
|
||||
// Send available commands after replay so it doesn't interleave with history
|
||||
setTimeout(() => {
|
||||
this.sendAvailableCommandsUpdate(params.sessionId);
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
modes: response.modes,
|
||||
@@ -358,6 +396,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
|
||||
// Collect all sessions across all project directories
|
||||
const allSessions: SessionInfo[] = [];
|
||||
const encodedCwdFilter = params.cwd ? encodeProjectPath(params.cwd) : null;
|
||||
|
||||
try {
|
||||
const projectDirs = await fs.promises.readdir(claudeDir);
|
||||
@@ -367,13 +406,9 @@ export class ClaudeAcpAgent implements Agent {
|
||||
const stat = await fs.promises.stat(projectDir);
|
||||
if (!stat.isDirectory()) continue;
|
||||
|
||||
// Decode the path based on platform:
|
||||
// - Unix: "-Users-morse-project" -> "/Users/morse/project"
|
||||
// - Windows: "C-Users-morse-project" -> "C:\Users\morse\project"
|
||||
const decodedCwd = decodeProjectPath(encodedPath);
|
||||
|
||||
// Skip if filtering by cwd and this doesn't match
|
||||
if (params.cwd && decodedCwd !== params.cwd) continue;
|
||||
// Path encoding is not always reversible (hyphens can be separators or literals),
|
||||
// so only use encoded value as a coarse pre-filter.
|
||||
if (encodedCwdFilter && encodedPath !== encodedCwdFilter) continue;
|
||||
|
||||
const files = await fs.promises.readdir(projectDir);
|
||||
// Filter to user session files only. Skip agent-*.jsonl files which contain
|
||||
@@ -386,23 +421,31 @@ export class ClaudeAcpAgent implements Agent {
|
||||
const content = await fs.promises.readFile(filePath, "utf-8");
|
||||
const lines = content.trim().split("\n").filter(Boolean);
|
||||
|
||||
const firstLine = lines[0];
|
||||
if (!firstLine) continue;
|
||||
|
||||
// Parse first line to get session info
|
||||
const firstEntry = JSON.parse(firstLine);
|
||||
const sessionId = firstEntry.sessionId || file.replace(".jsonl", "");
|
||||
const sessionId = file.replace(".jsonl", "");
|
||||
let parsedAnyEntry = false;
|
||||
let sessionCwd: string | undefined;
|
||||
|
||||
// Find first user message for title
|
||||
let title: string | undefined;
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const entry = JSON.parse(line);
|
||||
if (entry.type === "user" && entry.message?.content) {
|
||||
parsedAnyEntry = true;
|
||||
if (entry.isSidechain === true) {
|
||||
continue;
|
||||
}
|
||||
const entrySessionId =
|
||||
typeof entry.sessionId === "string" ? entry.sessionId : undefined;
|
||||
if (typeof entry.sessionId === "string" && entry.sessionId !== entrySessionId) {
|
||||
continue;
|
||||
}
|
||||
if (typeof entry.cwd === "string") {
|
||||
sessionCwd = entry.cwd;
|
||||
}
|
||||
if (!title && entry.type === "user" && entry.message?.content) {
|
||||
const msgContent = entry.message.content;
|
||||
if (typeof msgContent === "string") {
|
||||
title = sanitizeTitle(msgContent);
|
||||
break;
|
||||
}
|
||||
if (Array.isArray(msgContent) && msgContent.length > 0) {
|
||||
const first = msgContent[0];
|
||||
@@ -414,14 +457,29 @@ export class ClaudeAcpAgent implements Agent {
|
||||
: undefined;
|
||||
if (text) {
|
||||
title = sanitizeTitle(text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue scanning until we have both fields, since cwd can appear
|
||||
// in later entries even after the first user title-bearing message.
|
||||
if (title && sessionCwd) {
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed lines
|
||||
}
|
||||
}
|
||||
if (!parsedAnyEntry) continue;
|
||||
|
||||
// SessionInfo.cwd is currently required. For entries that do not
|
||||
// include an explicit cwd in the session JSONL (typically metadata-only files),
|
||||
// we skip them instead of decoding folder names because path encoding is lossy.
|
||||
if (!sessionCwd) continue;
|
||||
|
||||
// Even after encoded-path pre-filtering, verify per-entry cwd to disambiguate
|
||||
// collisions such as "/a-b" and "/a/b" that map to the same encoded folder name.
|
||||
if (params.cwd && sessionCwd !== params.cwd) continue;
|
||||
|
||||
// Get file modification time as updatedAt
|
||||
const fileStat = await fs.promises.stat(filePath);
|
||||
@@ -429,7 +487,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
|
||||
allSessions.push({
|
||||
sessionId,
|
||||
cwd: decodedCwd,
|
||||
cwd: sessionCwd,
|
||||
title: title ?? null,
|
||||
updatedAt,
|
||||
});
|
||||
@@ -709,8 +767,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
}
|
||||
}
|
||||
|
||||
private async replaySessionHistory(sessionId: string, cwd: string): Promise<void> {
|
||||
const filePath = sessionFilePath(cwd, sessionId);
|
||||
private async replaySessionHistory(sessionId: string, filePath: string): Promise<void> {
|
||||
const toolUseCache: ToolUseCache = {};
|
||||
const stream = fs.createReadStream(filePath, { encoding: "utf-8" });
|
||||
const reader = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
||||
@@ -912,6 +969,19 @@ export class ClaudeAcpAgent implements Agent {
|
||||
};
|
||||
}
|
||||
|
||||
private async sendAvailableCommandsUpdate(sessionId: string): Promise<void> {
|
||||
const session = this.sessions[sessionId];
|
||||
if (!session) return;
|
||||
const commands = await session.query.supportedCommands();
|
||||
await this.client.sessionUpdate({
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "available_commands_update",
|
||||
availableCommands: getAvailableSlashCommands(commands),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async createSession(
|
||||
params: NewSessionRequest,
|
||||
creationOpts: { resume?: string; forkSession?: boolean } = {},
|
||||
@@ -1105,7 +1175,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
options.allowedTools = allowedTools;
|
||||
}
|
||||
if (disallowedTools.length > 0) {
|
||||
options.disallowedTools = disallowedTools;
|
||||
options.disallowedTools = [...(options.disallowedTools || []), ...disallowedTools];
|
||||
}
|
||||
|
||||
// Handle abort controller from meta options
|
||||
@@ -1131,17 +1201,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
|
||||
const models = await getAvailableModels(q, initializationResult.models, settingsManager);
|
||||
|
||||
// Needs to happen after we return the session
|
||||
setTimeout(() => {
|
||||
this.client.sessionUpdate({
|
||||
sessionId,
|
||||
update: {
|
||||
sessionUpdate: "available_commands_update",
|
||||
availableCommands: getAvailableSlashCommands(initializationResult.commands),
|
||||
},
|
||||
});
|
||||
}, 0);
|
||||
|
||||
const availableModes = [
|
||||
{
|
||||
id: "default",
|
||||
|
||||
@@ -609,6 +609,7 @@ describe("tool conversions", () => {
|
||||
server_tool_use: null,
|
||||
inference_geo: null,
|
||||
iterations: null,
|
||||
speed: null,
|
||||
},
|
||||
context_management: null,
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ describe("unstable_listSessions", () => {
|
||||
mtime?: Date;
|
||||
malformed?: boolean;
|
||||
isAgentFile?: boolean;
|
||||
omitCwdFromEntries?: boolean;
|
||||
} = {},
|
||||
): void {
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
@@ -56,11 +57,25 @@ describe("unstable_listSessions", () => {
|
||||
const content = options.userMessageArray
|
||||
? [{ type: "text", text: options.userMessage }]
|
||||
: options.userMessage;
|
||||
lines.push(JSON.stringify({ type: "user", message: { content } }));
|
||||
lines.push(
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId,
|
||||
...(options.omitCwdFromEntries ? {} : { cwd }),
|
||||
message: { content },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Assistant response
|
||||
lines.push(JSON.stringify({ type: "assistant", message: { content: "Hello!" } }));
|
||||
lines.push(
|
||||
JSON.stringify({
|
||||
type: "assistant",
|
||||
sessionId,
|
||||
...(options.omitCwdFromEntries ? {} : { cwd }),
|
||||
message: { content: "Hello!" },
|
||||
}),
|
||||
);
|
||||
|
||||
fs.writeFileSync(filePath, lines.join("\n"));
|
||||
|
||||
@@ -187,6 +202,66 @@ describe("unstable_listSessions", () => {
|
||||
expect(result.sessions[0]!.sessionId).toBe("sess-a");
|
||||
});
|
||||
|
||||
it("uses per-entry cwd when filtering even if encoded folder collides", async () => {
|
||||
const encodedPath = encodeProjectPath("/Users/test/a-b");
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(projectDir, "sess-a-b.jsonl"),
|
||||
[
|
||||
JSON.stringify({ sessionId: "sess-a-b", type: "init" }),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "sess-a-b",
|
||||
cwd: "/Users/test/a-b",
|
||||
message: { content: "A-B" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(projectDir, "sess-a-slash-b.jsonl"),
|
||||
[
|
||||
JSON.stringify({ sessionId: "sess-a-slash-b", type: "init" }),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "sess-a-slash-b",
|
||||
cwd: "/Users/test/a/b",
|
||||
message: { content: "A/B" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({ cwd: "/Users/test/a-b" });
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("sess-a-b");
|
||||
expect(result.sessions[0]!.cwd).toBe("/Users/test/a-b");
|
||||
});
|
||||
|
||||
it("returns the true cwd for paths containing hyphens", async () => {
|
||||
const cwd = "/Users/test/claude-code-acp";
|
||||
writeSessionFile(cwd, "hyphen-session", { userMessage: "Hyphen test" });
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.cwd).toBe(cwd);
|
||||
expect(result.sessions[0]!.sessionId).toBe("hyphen-session");
|
||||
});
|
||||
|
||||
it("filters hyphenated paths by cwd correctly", async () => {
|
||||
writeSessionFile("/Users/test/claude-code-acp", "hyphen-a", { userMessage: "A" });
|
||||
writeSessionFile("/Users/test/other-project", "hyphen-b", { userMessage: "B" });
|
||||
|
||||
const result = await agent.unstable_listSessions({ cwd: "/Users/test/claude-code-acp" });
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("hyphen-a");
|
||||
expect(result.sessions[0]!.cwd).toBe("/Users/test/claude-code-acp");
|
||||
});
|
||||
|
||||
it("returns all sessions when cwd is not specified", async () => {
|
||||
writeSessionFile("/Users/test/projectone", "sess-a", { userMessage: "A" });
|
||||
writeSessionFile("/Users/test/projecttwo", "sess-b", { userMessage: "B" });
|
||||
@@ -241,15 +316,161 @@ describe("unstable_listSessions", () => {
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
// Write file without sessionId in content
|
||||
// Write file without sessionId in content (uses filename fallback), but with cwd.
|
||||
const filePath = path.join(projectDir, "fallback-id.jsonl");
|
||||
fs.writeFileSync(filePath, JSON.stringify({ type: "init" }) + "\n");
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({ type: "init" }),
|
||||
JSON.stringify({ type: "assistant", cwd, message: { content: "Hi" } }),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions[0]!.sessionId).toBe("fallback-id");
|
||||
});
|
||||
|
||||
it("keeps sessionId stable from filename when later entries have different sessionId", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(projectDir, "stable-id.jsonl");
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({ type: "init", sessionId: "stable-id" }),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "stable-id",
|
||||
cwd,
|
||||
message: { content: "Hello" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "assistant",
|
||||
sessionId: "other-id",
|
||||
cwd,
|
||||
message: { content: "Wrong session id from sidechain" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("stable-id");
|
||||
});
|
||||
|
||||
it("derives cwd from entries for the filename sessionId only", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(projectDir, "target-session.jsonl");
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "target-session",
|
||||
cwd,
|
||||
message: { content: "Correct cwd entry" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("target-session");
|
||||
expect(result.sessions[0]!.cwd).toBe(cwd);
|
||||
});
|
||||
|
||||
it("keeps scanning after title to find later cwd in same session", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(projectDir, "late-cwd.jsonl");
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({ type: "init", sessionId: "late-cwd" }),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "late-cwd",
|
||||
message: { content: "Title from first user message" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "assistant",
|
||||
sessionId: "late-cwd",
|
||||
cwd,
|
||||
message: { content: "Assistant entry carries cwd" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("late-cwd");
|
||||
expect(result.sessions[0]!.cwd).toBe(cwd);
|
||||
expect(result.sessions[0]!.title).toBe("Title from first user message");
|
||||
});
|
||||
|
||||
it("ignores sidechain entries when deriving cwd and title", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(projectDir, "sidechain-metadata.jsonl");
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({ type: "init", sessionId: "sidechain-metadata" }),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "sidechain-metadata",
|
||||
isSidechain: true,
|
||||
cwd: "/Users/test/wrong-project",
|
||||
message: { content: "Wrong sidechain title" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "user",
|
||||
sessionId: "sidechain-metadata",
|
||||
cwd,
|
||||
message: { content: "Correct primary title" },
|
||||
}),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await agent.unstable_listSessions({ cwd });
|
||||
|
||||
expect(result.sessions).toHaveLength(1);
|
||||
expect(result.sessions[0]!.sessionId).toBe("sidechain-metadata");
|
||||
expect(result.sessions[0]!.cwd).toBe(cwd);
|
||||
expect(result.sessions[0]!.title).toBe("Correct primary title");
|
||||
});
|
||||
|
||||
it("skips entries without cwd when cwd filter is not provided", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
const projectDir = path.join(tempDir, "projects", encodedPath);
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
||||
const filePath = path.join(projectDir, "no-cwd.jsonl");
|
||||
fs.writeFileSync(filePath, JSON.stringify({ sessionId: "no-cwd", type: "summary" }) + "\n");
|
||||
|
||||
const result = await agent.unstable_listSessions({});
|
||||
|
||||
expect(result.sessions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("returns null title when no user message exists", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const encodedPath = encodeProjectPath(cwd);
|
||||
@@ -262,7 +483,7 @@ describe("unstable_listSessions", () => {
|
||||
filePath,
|
||||
[
|
||||
JSON.stringify({ sessionId: "no-user", type: "init" }),
|
||||
JSON.stringify({ type: "assistant", message: { content: "Hi" } }),
|
||||
JSON.stringify({ type: "assistant", cwd, message: { content: "Hi" } }),
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
|
||||
@@ -230,6 +230,76 @@ describe("loadSession", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("finds session file when cwd differs from the original session cwd", async () => {
|
||||
const originalCwd = "/Users/test/original-project";
|
||||
const differentCwd = "/Users/test/different-project";
|
||||
const sessionId = "cross-cwd-session";
|
||||
|
||||
// Session was created in originalCwd
|
||||
writeSessionFile(originalCwd, sessionId, [
|
||||
{
|
||||
type: "user",
|
||||
sessionId,
|
||||
cwd: originalCwd,
|
||||
message: { role: "user", content: "Hello from original" },
|
||||
},
|
||||
]);
|
||||
|
||||
// Load with a different cwd
|
||||
await agent.loadSession({ cwd: differentCwd, sessionId, mcpServers: [] });
|
||||
|
||||
expect(sessionUpdates).toHaveLength(1);
|
||||
expect(sessionUpdates[0]?.update).toMatchObject({
|
||||
sessionUpdate: "user_message_chunk",
|
||||
content: { type: "text", text: "Hello from original" },
|
||||
});
|
||||
expect(createSessionSpy).toHaveBeenCalledWith(expect.objectContaining({ cwd: differentCwd }), {
|
||||
resume: sessionId,
|
||||
});
|
||||
});
|
||||
|
||||
it("sends available_commands_update after history replay, not during", async () => {
|
||||
const cwd = "/Users/test/project";
|
||||
const sessionId = "session-commands";
|
||||
|
||||
// Mock createSession to populate the sessions map with a mock query
|
||||
createSessionSpy.mockImplementationOnce(async () => {
|
||||
(agent as unknown as { sessions: Record<string, unknown> }).sessions[sessionId] = {
|
||||
query: {
|
||||
supportedCommands: async () => [{ name: "help", description: "Get help" }],
|
||||
},
|
||||
};
|
||||
return { modes: null, models: null };
|
||||
});
|
||||
|
||||
writeSessionFile(cwd, sessionId, [
|
||||
{
|
||||
type: "user",
|
||||
sessionId,
|
||||
message: { role: "user", content: "Hello" },
|
||||
},
|
||||
{
|
||||
type: "assistant",
|
||||
sessionId,
|
||||
message: { role: "assistant", content: [{ type: "text", text: "Hi" }] },
|
||||
},
|
||||
]);
|
||||
|
||||
await agent.loadSession({ cwd, sessionId, mcpServers: [] });
|
||||
|
||||
// Flush the setTimeout + async work inside sendAvailableCommandsUpdate
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const updateTypes = sessionUpdates.map((n) => n.update.sessionUpdate);
|
||||
|
||||
// available_commands_update should come AFTER all replay notifications
|
||||
expect(updateTypes).toEqual([
|
||||
"user_message_chunk",
|
||||
"agent_message_chunk",
|
||||
"available_commands_update",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles Windows-style paths", async () => {
|
||||
const cwd = "C:\\Users\\test\\project";
|
||||
const sessionId = "win-session";
|
||||
|
||||
Reference in New Issue
Block a user