mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49d73b2ad3 | ||
|
|
cffea4be28 | ||
|
|
add7e31704 | ||
|
|
f1736a9371 | ||
|
|
895422c72d | ||
|
|
32175b83a0 | ||
|
|
9cda07ba9d | ||
|
|
51a370e4fe | ||
|
|
edde96ebaf | ||
|
|
3fb6db4e5e | ||
|
|
41e3771881 | ||
|
|
61ebda2c60 | ||
|
|
98b54a02c2 | ||
|
|
a172885c80 | ||
|
|
7ed1daf7b4 | ||
|
|
694221a412 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.37.0"
|
||||
".": "0.41.0"
|
||||
}
|
||||
|
||||
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## [0.41.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.40.0...v0.41.0) (2026-06-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** upgrade to @anthropic-ai/claude-agent-sdk@0.3.162 ([#740](https://github.com/agentclientprotocol/claude-agent-acp/issues/740)) ([add7e31](https://github.com/agentclientprotocol/claude-agent-acp/commit/add7e31704581aa70febb6341ca0e8cee5d93b43))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Force cancellation when SDK query hangs ([#742](https://github.com/agentclientprotocol/claude-agent-acp/issues/742)) ([cffea4b](https://github.com/agentclientprotocol/claude-agent-acp/commit/cffea4be28e6def91027110433a99e80b1be8a0c)), closes [#680](https://github.com/agentclientprotocol/claude-agent-acp/issues/680)
|
||||
|
||||
## [0.40.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.39.0...v0.40.0) (2026-06-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps-dev:** bump the minor group with 2 updates ([#726](https://github.com/agentclientprotocol/claude-agent-acp/issues/726)) ([51a370e](https://github.com/agentclientprotocol/claude-agent-acp/commit/51a370e4feb8a74279698b156e35b96631a74d74))
|
||||
* **deps:** bump actions/checkout from 6.0.2 to 6.0.3 ([#736](https://github.com/agentclientprotocol/claude-agent-acp/issues/736)) ([9cda07b](https://github.com/agentclientprotocol/claude-agent-acp/commit/9cda07ba9df27b48bd94332e365eed982e3f5613))
|
||||
* **deps:** bump the minor group with 50 updates ([#737](https://github.com/agentclientprotocol/claude-agent-acp/issues/737)) ([32175b8](https://github.com/agentclientprotocol/claude-agent-acp/commit/32175b83a0227d0baa1f7d7eedabe743b4b870e8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Optimize local command marker stripping ([#738](https://github.com/agentclientprotocol/claude-agent-acp/issues/738)) ([895422c](https://github.com/agentclientprotocol/claude-agent-acp/commit/895422c72d5f955903f227dc6887df823d2730e6)), closes [#727](https://github.com/agentclientprotocol/claude-agent-acp/issues/727)
|
||||
|
||||
## [0.39.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.38.0...v0.39.0) (2026-05-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** update Claude Agent SDK to 0.3.156 ([#722](https://github.com/agentclientprotocol/claude-agent-acp/issues/722)) ([3fb6db4](https://github.com/agentclientprotocol/claude-agent-acp/commit/3fb6db4e5ee5bd1f431cf5d2ce5331ce983f9597))
|
||||
|
||||
## [0.38.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.37.0...v0.38.0) (2026-05-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **deps:** bump to @anthropic-ai/claude-agent-sdk 0.3.154 (Opus 4.8) ([#716](https://github.com/agentclientprotocol/claude-agent-acp/issues/716)) ([a172885](https://github.com/agentclientprotocol/claude-agent-acp/commit/a172885c80fc32c108a8501268a543641088f543))
|
||||
* Support Opus 4.8 ([#718](https://github.com/agentclientprotocol/claude-agent-acp/issues/718)) ([98b54a0](https://github.com/agentclientprotocol/claude-agent-acp/commit/98b54a02c267cdef619becceccf6ef519fbf8f6f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Remove hide Claude auth flag handling ([#707](https://github.com/agentclientprotocol/claude-agent-acp/issues/707)) ([7ed1daf](https://github.com/agentclientprotocol/claude-agent-acp/commit/7ed1daf7b42088ad149e9bf742f764752b82d093))
|
||||
|
||||
## [0.37.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.36.1...v0.37.0) (2026-05-21)
|
||||
|
||||
|
||||
|
||||
502
package-lock.json
generated
502
package-lock.json
generated
@@ -1,62 +1,62 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.37.0",
|
||||
"version": "0.41.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.37.0",
|
||||
"version": "0.41.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.22.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.146",
|
||||
"@agentclientprotocol/sdk": "0.24.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.162",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"claude-agent-acp": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.97.1",
|
||||
"@anthropic-ai/sdk": "0.100.1",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "25.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.59.4",
|
||||
"@typescript-eslint/parser": "8.59.4",
|
||||
"eslint": "10.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.60.1",
|
||||
"@typescript-eslint/parser": "8.60.1",
|
||||
"eslint": "10.4.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.6.0",
|
||||
"prettier": "3.8.3",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.7"
|
||||
"vitest": "4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "0.22.1",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.22.1.tgz",
|
||||
"integrity": "sha512-DfqXtl/8gO9NImq094MTaCXEU2vkhh6v7q/kT+9UjZxUqj8hYaya2OjLVIqn16MzNHcXEpShTR2RIauLSYeDQQ==",
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.24.0.tgz",
|
||||
"integrity": "sha512-vvu9appvGvfYstBj19C6NCepV6SvUhY5VRv60KUZ4XzhTah/olOYul5Zo4C+x2enyshMSvgB2mm/OEmrsHaSmA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.146.tgz",
|
||||
"integrity": "sha512-hK9/Ng+hOyexUemTxdIUsSWJ9o2LFi2YNWzHwz8/YMCohUYOnFMZkBiENvUAb0WIc5hieOyBZrOIlg5OewuJMg==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.162.tgz",
|
||||
"integrity": "sha512-piAlpc1h6FUMCNU+bnYNNLX1lOgMlFnqZmIhn6Myv48MaNRaecFaZEuVLY+UxV0kjGiQFYHBLR4fk/rRwi2SAA==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.146",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.146"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.162",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.162"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -65,9 +65,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.146.tgz",
|
||||
"integrity": "sha512-0IIvlEaenq2CRSVx5Bo5BaCtHQXS87GancM35WKEYveGVLn6DI+5G7ikYuTE4AKRPkMnogFtY4BJt6LulWGj+A==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.162.tgz",
|
||||
"integrity": "sha512-hafbfEtDeYko1rYCgIBAQbYnFXzd/hHf3IcoaD8mmlCQOAQhKA8gT/RPdLuuzQHdOjEgqDGTNOU+IjwL+msYcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -78,9 +78,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.146.tgz",
|
||||
"integrity": "sha512-Dk5xJ03Ff1JXbMRP1t2wc/TyfY6xF/2Ysp31wMhFPjoNiKSPHMWaIg242+T3CHdxLWmJ8plWHL1HL5cyZ/LCkw==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.162.tgz",
|
||||
"integrity": "sha512-BNg2Mh/4zc2Jsgpq7Mj8+UH8iJ9xzHS/0CmusBMik0H2Bn7Hra5R/f+csAfZOzSflQl4CNQdGOB2bir7d2n8Ww==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -91,9 +91,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.146.tgz",
|
||||
"integrity": "sha512-mzBXDDWWBAC/vDtAYpO1G/dq5QvJtYSPXsqcb+sNdcDhiuf4IYnYp7ytRncYlsUNDkLmX6Gk2jkWAHUUA2Lozg==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.162.tgz",
|
||||
"integrity": "sha512-zCXYSimaXWQKZASfDJkoKXQr//toYDGIi16wKDh02Rqcr4mqFi9f5SBw/UCyimkGyYkNx3e+bmC+o/tFrLSTWw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -104,9 +104,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.146.tgz",
|
||||
"integrity": "sha512-QlCid0ucdrmhUAOewfQjaofN2wlokWcfFTxSFePTSj1umk35JO7TDFP700F7jU49r1fPWIdvJpPwWGyB0DeFPA==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.162.tgz",
|
||||
"integrity": "sha512-Jr4cyfqzb5V2+p/1PynIfGROOI0JNtV3vBMAgckAdtDzpIFk4mV2T8936tsZzgZr04y40YH1HlQpnJDr92I+Fw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -117,9 +117,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.146.tgz",
|
||||
"integrity": "sha512-B2baXU1tCBT5CVlD7jJMKjpC4xdO45NUIWpqImmwuOfKvlM/PITjyTXyTY662mGZf1dBmdqBBsqirwFH/jhi8Q==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.162.tgz",
|
||||
"integrity": "sha512-FO2+zDuSTsZ/5MqxIwExLxG0c2auA5wO6iICwZdUOWtboC6yU2AEgp4mzF0jbe1UOP0J27uODFpvz7uRpWipkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -130,9 +130,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.146.tgz",
|
||||
"integrity": "sha512-E3coK1ThQT08KIX80RLcsq7DWXFllCKOzoOe32it/bdtY56TBgPY9xemwXhIJ+cVBHTI9/MpBSIlKBcFCt+yQA==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.162.tgz",
|
||||
"integrity": "sha512-gW9Gpk7W3w3zGFHBDyY8uer/PE6T0pB+emN1aafZAomfseIH1ixJ6ya5Fw2cIS/K0/4oR2pvu4AprlbRBtr45Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -143,9 +143,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.146.tgz",
|
||||
"integrity": "sha512-CIwQxGX2r/yWpjCJ6ahB3smKXhghWgGTxL98+LGW52TUwqTiBnlNrH9DPqqgv1/+Hyquw6xfLrKU+StyfMgiLw==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.162.tgz",
|
||||
"integrity": "sha512-TZQifFDBdhzt1u6wbbpQ2AZcRkhNVYx1iZVfydAbs3L7ZMK264LjRPytBK4n4S413Is1XyLHbGMvEUNA3N+Tng==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -156,9 +156,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.146",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.146.tgz",
|
||||
"integrity": "sha512-qmxrsyaqA8s4HShqJls7ZCRjdoqN66Jo/hbjQNB3uHepD8tEO1iD19aPV4+osdLT7feMkhDBfLT07Q30R2NB5w==",
|
||||
"version": "0.3.162",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.162.tgz",
|
||||
"integrity": "sha512-8ZYDgNxkGp47xwpcEZ6/qUXd4BByA8hTnNf4fJD6+P1wWi7Ofxc/d5jwXSYwajYvBvbbktQDthmGzjtoK3lXUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -169,9 +169,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.97.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.97.1.tgz",
|
||||
"integrity": "sha512-wOf7AUeJPitcVpvKO4UMu63mWH5SaVipkGd7OOQJt/G6VYGlV8D2Gp9dLxOrttDJh/9gqPqdaBwDGcBevumeAg==",
|
||||
"version": "0.100.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.100.1.tgz",
|
||||
"integrity": "sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
@@ -190,9 +190,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -347,9 +347,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
|
||||
"integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
|
||||
"integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -528,9 +528,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.132.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz",
|
||||
"integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==",
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -538,9 +538,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -555,9 +555,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -572,9 +572,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -589,9 +589,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -606,9 +606,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz",
|
||||
"integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -623,9 +623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -640,9 +640,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz",
|
||||
"integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -657,9 +657,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -674,9 +674,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -691,9 +691,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -708,9 +708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz",
|
||||
"integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -725,9 +725,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -742,9 +742,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz",
|
||||
"integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -761,9 +761,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz",
|
||||
"integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -778,9 +778,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz",
|
||||
"integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -903,17 +903,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz",
|
||||
"integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
|
||||
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.59.4",
|
||||
"@typescript-eslint/type-utils": "8.59.4",
|
||||
"@typescript-eslint/utils": "8.59.4",
|
||||
"@typescript-eslint/visitor-keys": "8.59.4",
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/type-utils": "8.60.1",
|
||||
"@typescript-eslint/utils": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -926,22 +926,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "8.59.4",
|
||||
"@typescript-eslint/parser": "8.60.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz",
|
||||
"integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
|
||||
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.59.4",
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/typescript-estree": "8.59.4",
|
||||
"@typescript-eslint/visitor-keys": "8.59.4",
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -957,14 +957,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz",
|
||||
"integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
|
||||
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.59.4",
|
||||
"@typescript-eslint/types": "^8.59.4",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.60.1",
|
||||
"@typescript-eslint/types": "^8.60.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -979,14 +979,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz",
|
||||
"integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
|
||||
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/visitor-keys": "8.59.4"
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -997,9 +997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz",
|
||||
"integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
|
||||
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1014,15 +1014,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz",
|
||||
"integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
|
||||
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/typescript-estree": "8.59.4",
|
||||
"@typescript-eslint/utils": "8.59.4",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||
"@typescript-eslint/utils": "8.60.1",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1039,9 +1039,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz",
|
||||
"integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
|
||||
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1053,16 +1053,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz",
|
||||
"integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
|
||||
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.59.4",
|
||||
"@typescript-eslint/tsconfig-utils": "8.59.4",
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/visitor-keys": "8.59.4",
|
||||
"@typescript-eslint/project-service": "8.60.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1081,16 +1081,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz",
|
||||
"integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
|
||||
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.59.4",
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/typescript-estree": "8.59.4"
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1105,13 +1105,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.59.4",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz",
|
||||
"integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==",
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
|
||||
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.59.4",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1136,16 +1136,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
|
||||
"integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
|
||||
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -1154,13 +1154,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
|
||||
"integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
|
||||
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -1181,9 +1181,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
|
||||
"integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
|
||||
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1194,13 +1194,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
|
||||
"integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
|
||||
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -1208,14 +1208,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
|
||||
"integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
|
||||
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -1224,9 +1224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
|
||||
"integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
|
||||
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -1234,13 +1234,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
|
||||
"integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
|
||||
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -1653,9 +1653,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -1686,9 +1686,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz",
|
||||
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz",
|
||||
"integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1697,7 +1697,7 @@
|
||||
"@eslint/config-array": "^0.23.5",
|
||||
"@eslint/config-helpers": "^0.6.0",
|
||||
"@eslint/core": "^1.2.1",
|
||||
"@eslint/plugin-kit": "^0.7.1",
|
||||
"@eslint/plugin-kit": "^0.7.2",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
@@ -1947,9 +1947,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
|
||||
"integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
|
||||
"integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -2300,9 +2300,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -2313,9 +2313,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.21",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz",
|
||||
"integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==",
|
||||
"version": "4.12.23",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz",
|
||||
"integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -3246,13 +3246,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
|
||||
"integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.132.0",
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -3262,21 +3262,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.2",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.2",
|
||||
"@rolldown/binding-darwin-x64": "1.0.2",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.2",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.2",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.2",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.2",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.2",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.2",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.2",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.2",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.2",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.2",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.2",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.2"
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
@@ -3304,9 +3304,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -3526,9 +3526,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
|
||||
"integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3536,9 +3536,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3748,17 +3748,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.14",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",
|
||||
"integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==",
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.2",
|
||||
"tinyglobby": "^0.2.16"
|
||||
"rolldown": "1.0.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -3826,19 +3826,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
|
||||
"integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
|
||||
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.7",
|
||||
"@vitest/mocker": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/runner": "4.1.7",
|
||||
"@vitest/snapshot": "4.1.7",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/expect": "4.1.8",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/runner": "4.1.8",
|
||||
"@vitest/snapshot": "4.1.8",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -3866,12 +3866,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.7",
|
||||
"@vitest/browser-preview": "4.1.7",
|
||||
"@vitest/browser-webdriverio": "4.1.7",
|
||||
"@vitest/coverage-istanbul": "4.1.7",
|
||||
"@vitest/coverage-v8": "4.1.7",
|
||||
"@vitest/ui": "4.1.7",
|
||||
"@vitest/browser-playwright": "4.1.8",
|
||||
"@vitest/browser-preview": "4.1.8",
|
||||
"@vitest/browser-webdriverio": "4.1.8",
|
||||
"@vitest/coverage-istanbul": "4.1.8",
|
||||
"@vitest/coverage-v8": "4.1.8",
|
||||
"@vitest/ui": "4.1.8",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
||||
16
package.json
16
package.json
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.37.0",
|
||||
"version": "0.41.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
@@ -60,22 +60,22 @@
|
||||
"author": "Zed Industries",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.22.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.146",
|
||||
"@agentclientprotocol/sdk": "0.24.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.162",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.97.1",
|
||||
"@anthropic-ai/sdk": "0.100.1",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@types/node": "25.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.59.4",
|
||||
"@typescript-eslint/parser": "8.59.4",
|
||||
"eslint": "10.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.60.1",
|
||||
"@typescript-eslint/parser": "8.60.1",
|
||||
"eslint": "10.4.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.6.0",
|
||||
"prettier": "3.8.3",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.7"
|
||||
"vitest": "4.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
391
src/acp-agent.ts
391
src/acp-agent.ts
@@ -26,13 +26,10 @@ import {
|
||||
ResumeSessionRequest,
|
||||
ResumeSessionResponse,
|
||||
SessionConfigOption,
|
||||
SessionModelState,
|
||||
SessionModeState,
|
||||
SessionNotification,
|
||||
SetSessionConfigOptionRequest,
|
||||
SetSessionConfigOptionResponse,
|
||||
SetSessionModelRequest,
|
||||
SetSessionModelResponse,
|
||||
SetSessionModeRequest,
|
||||
SetSessionModeResponse,
|
||||
CloseSessionRequest,
|
||||
@@ -60,6 +57,7 @@ import {
|
||||
query,
|
||||
Settings,
|
||||
SDKAssistantMessageError,
|
||||
SDKMessage,
|
||||
SDKMessageOrigin,
|
||||
SDKPartialAssistantMessage,
|
||||
SDKUserMessage,
|
||||
@@ -137,6 +135,26 @@ const ZERO_USAGE = Object.freeze({
|
||||
|
||||
const DEFAULT_CONTEXT_WINDOW = 200000;
|
||||
|
||||
/** Floor after `session/cancel` before the adapter forces the active prompt
|
||||
* loop to return "cancelled". `query.interrupt()` normally makes the SDK
|
||||
* yield a trailing idle within milliseconds, and the loop returns through its
|
||||
* usual path — so this timer is armed and cleared, never fired, on healthy
|
||||
* cancels. It only trips when the SDK is genuinely wedged (e.g. a
|
||||
* `TaskOutput { block: true }` poll against a hung background task — issue
|
||||
* #680) and never yields. The value is deliberately loose: it's an
|
||||
* "obviously stuck" ceiling, not a guess at interrupt latency, so it can't
|
||||
* pre-empt a slow-but-healthy interrupt. */
|
||||
const DEFAULT_FORCE_CANCEL_GRACE_MS = 30_000;
|
||||
|
||||
/** Internal model-selection state. Mirrors the shape the ACP SDK exposed as
|
||||
* `SessionModelState` before model selection moved entirely into
|
||||
* `SessionConfigOption` (category "model"). Retained internally to track the
|
||||
* current model and build the "model" config option. */
|
||||
type SessionModelState = {
|
||||
availableModels: Array<{ modelId: string; name: string; description?: string }>;
|
||||
currentModelId: string;
|
||||
};
|
||||
|
||||
type Session = {
|
||||
query: Query;
|
||||
input: Pushable<SDKUserMessage>;
|
||||
@@ -155,6 +173,17 @@ type Session = {
|
||||
pendingMessages: Map<string, { resolve: (cancelled: boolean) => void; order: number }>;
|
||||
nextPendingOrder: number;
|
||||
abortController: AbortController;
|
||||
/** Per-turn signal the active prompt loop races `query.next()` against.
|
||||
* Aborted by cancel() (after a grace period) to force the loop to return
|
||||
* "cancelled" when the SDK is wedged and `query.next()` never yields again
|
||||
* (issue #680). Distinct from `abortController`: this only wakes the loop;
|
||||
* it does NOT touch the SDK query/subprocess. Undefined when no prompt is
|
||||
* actively consuming the query. */
|
||||
cancelController?: AbortController;
|
||||
/** Pending grace-period timer that aborts `cancelController`. Cleared when
|
||||
* the loop returns normally so the backstop never fires after a clean
|
||||
* cancel. */
|
||||
forceCancelTimer?: ReturnType<typeof setTimeout>;
|
||||
emitRawSDKMessages: boolean | SDKMessageFilter[];
|
||||
/** Context window size of the last top-level assistant model, carried across
|
||||
* prompts so mid-stream usage_update notifications report a correct `size`
|
||||
@@ -165,29 +194,6 @@ type Session = {
|
||||
/** Accumulated task list for the session, keyed by task ID. Task IDs are
|
||||
* per-session, so this state must not be shared across sessions. */
|
||||
taskState: TaskState;
|
||||
/** Assistant message ids whose text/thinking content was already emitted as
|
||||
* `agent_message_chunk` / `agent_thought_chunk` via `stream_event` partial
|
||||
* messages. Used by the top-level `assistant` case to decide whether to
|
||||
* skip text/thinking blocks (the default, when streaming worked) or fall
|
||||
* back to emitting the assembled blocks (when the underlying SDK / model
|
||||
* proxy never produced `content_block_delta` for them). */
|
||||
textStreamedMessageIds: Set<string>;
|
||||
/** Keys of assembled text/thinking blocks the fallback path has already
|
||||
* forwarded this turn, used to dedup re-delivered blocks without dropping
|
||||
* genuinely new ones. A single assistant message id is frequently
|
||||
* delivered as several `assistant` messages, each carrying a *different*
|
||||
* subset of content blocks (e.g. an empty `thinking` block, then the real
|
||||
* `text` block); deduping at message-id granularity would emit the first
|
||||
* block, flag the id, then silently drop the later text. Keying on the
|
||||
* block content instead lets distinct blocks that share an id all through
|
||||
* while still collapsing identical re-deliveries (incremental + consolidated
|
||||
* messages, or the same message delivered twice). */
|
||||
fallbackEmittedBlockKeys: Set<string>;
|
||||
/** Id of the assistant message currently being streamed via `stream_event`
|
||||
* partial messages, captured at `message_start` so subsequent
|
||||
* `content_block_delta` events can record their parent into
|
||||
* `textStreamedMessageIds`. */
|
||||
currentStreamingMessageId?: string;
|
||||
};
|
||||
|
||||
/** Compute a stable fingerprint of the session-defining params so we can
|
||||
@@ -370,11 +376,42 @@ const LOCAL_ONLY_COMMANDS = new Set(["/context", "/heapdump", "/extra-usage"]);
|
||||
// payload in these XML-like markers that the CLI uses for its own display.
|
||||
// The live prompt loop drops them; replay must strip them too or they leak
|
||||
// into the UI on session/load.
|
||||
const LOCAL_COMMAND_TAG_PATTERN =
|
||||
/<(command-name|command-message|command-args|local-command-stdout|local-command-stderr)>[\s\S]*?<\/\1>/g;
|
||||
const LOCAL_COMMAND_MARKERS = [
|
||||
"command-name",
|
||||
"command-message",
|
||||
"command-args",
|
||||
"local-command-stdout",
|
||||
"local-command-stderr",
|
||||
].map((tag) => ({ open: `<${tag}>`, close: `</${tag}>` }));
|
||||
|
||||
// Single-pass scanner that removes each `<tag>…</tag>` marker (matching the
|
||||
// nearest closing tag of the same name, like a lazy regex would).
|
||||
function stripMarkerTags(text: string): string {
|
||||
return text.replace(LOCAL_COMMAND_TAG_PATTERN, "");
|
||||
const dead = new Set<string>();
|
||||
let result = "";
|
||||
let copiedUpTo = 0;
|
||||
let i = 0;
|
||||
while (i < text.length) {
|
||||
if (text[i] === "<") {
|
||||
const marker = LOCAL_COMMAND_MARKERS.find(
|
||||
(m) => !dead.has(m.open) && text.startsWith(m.open, i),
|
||||
);
|
||||
if (marker) {
|
||||
const end = text.indexOf(marker.close, i + marker.open.length);
|
||||
if (end !== -1) {
|
||||
result += text.slice(copiedUpTo, i);
|
||||
i = copiedUpTo = end + marker.close.length;
|
||||
continue;
|
||||
}
|
||||
// No closing marker remains anywhere ahead, and `indexOf` only ever
|
||||
// searches forward from here on, so stop treating this tag as an
|
||||
// opener — that avoids rescanning the tail for it on every match.
|
||||
dead.add(marker.open);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return result + text.slice(copiedUpTo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,6 +552,10 @@ export class ClaudeAcpAgent implements Agent {
|
||||
clientCapabilities?: ClientCapabilities;
|
||||
logger: Logger;
|
||||
gatewayAuthRequest?: GatewayAuthRequest;
|
||||
/** Grace period before a `session/cancel` forces a wedged prompt loop to
|
||||
* return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
|
||||
* tests can shrink it. */
|
||||
forceCancelGraceMs: number = DEFAULT_FORCE_CANCEL_GRACE_MS;
|
||||
|
||||
constructor(client: AgentSideConnection, logger?: Logger) {
|
||||
this.sessions = {};
|
||||
@@ -766,14 +807,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
cachedWriteTokens: 0,
|
||||
};
|
||||
|
||||
// Assistant message ids and fallback block keys are turn-scoped — clear
|
||||
// before a new turn so the sets don't grow unboundedly over a long-lived
|
||||
// session, and so that an id reused across turns (uncommon but possible)
|
||||
// is re-evaluated.
|
||||
session.textStreamedMessageIds.clear();
|
||||
session.fallbackEmittedBlockKeys.clear();
|
||||
session.currentStreamingMessageId = undefined;
|
||||
|
||||
let lastAssistantTotalUsage: number | null = null;
|
||||
let lastAssistantUsage: UsageSnapshot | null = null;
|
||||
let lastAssistantModel: string | null = null;
|
||||
@@ -784,6 +817,19 @@ export class ClaudeAcpAgent implements Agent {
|
||||
// forward it to clients as structured `data`, sparing them from
|
||||
// pattern-matching on the human-readable message text.
|
||||
let lastAssistantError: SDKAssistantMessageError | undefined;
|
||||
// When a streaming classifier refuses a turn, the assistant message carries
|
||||
// stop_reason "refusal" and structured stop_details. We capture the
|
||||
// human-readable explanation here so the terminal `result` can surface it
|
||||
// to the user (the refused assistant message itself usually has no content)
|
||||
// and report ACP's dedicated `refusal` stop reason.
|
||||
let lastRefusalExplanation: string | null = null;
|
||||
// Tracks whether we're inside a compaction. The SDK emits the terminal
|
||||
// `status` (compact_result success/failed) twice for a single failed
|
||||
// compaction, and the two messages are indistinguishable — so we report the
|
||||
// outcome only while a compaction is in progress, then clear this. A fresh
|
||||
// `compacting` status sets it again, so every distinct compaction (e.g.
|
||||
// repeated auto-compactions in a long turn) is still shown.
|
||||
let compactionInProgress = false;
|
||||
|
||||
const userMessage = promptToClaude(params);
|
||||
|
||||
@@ -815,9 +861,27 @@ export class ClaudeAcpAgent implements Agent {
|
||||
let errored = false;
|
||||
let stopReason: StopReason = "end_turn";
|
||||
|
||||
// Wake-up channel so cancel() can force this loop to return "cancelled"
|
||||
// even when query.next() is wedged and never yields again (issue #680).
|
||||
const cancelController = new AbortController();
|
||||
session.cancelController = cancelController;
|
||||
const cancelled = new Promise<void>((resolve) => {
|
||||
cancelController.signal.addEventListener("abort", () => resolve(), { once: true });
|
||||
});
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { value: message, done } = await session.query.next();
|
||||
const nextMessage = session.query.next();
|
||||
const next = await Promise.race([nextMessage, cancelled]);
|
||||
if (cancelController.signal.aborted) {
|
||||
// The SDK never yielded after interrupt() (e.g. a wedged TaskOutput
|
||||
// block). Abandon the in-flight next() — swallowing any later
|
||||
// rejection so it can't surface as an unhandled rejection — and
|
||||
// honor the cancel per the ACP contract.
|
||||
void nextMessage.catch(() => {});
|
||||
return { stopReason: "cancelled" };
|
||||
}
|
||||
const { value: message, done } = next as IteratorResult<SDKMessage, void>;
|
||||
|
||||
if (done || !message) {
|
||||
if (session.cancelled) {
|
||||
@@ -843,6 +907,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
break;
|
||||
case "status": {
|
||||
if (message.status === "compacting") {
|
||||
compactionInProgress = true;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
@@ -850,6 +915,28 @@ export class ClaudeAcpAgent implements Agent {
|
||||
content: { type: "text", text: "Compacting..." },
|
||||
},
|
||||
});
|
||||
} else if (message.compact_result === "success" && compactionInProgress) {
|
||||
// The SDK signals manual `/compact` completion with a status
|
||||
// message carrying `compact_result`, not the `compact_boundary`
|
||||
// message (which only fires when there's content to compact).
|
||||
compactionInProgress = false;
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "\n\nCompacting completed." },
|
||||
},
|
||||
});
|
||||
} else if (message.compact_result === "failed" && compactionInProgress) {
|
||||
compactionInProgress = false;
|
||||
const reason = message.compact_error ? `: ${message.compact_error}` : ".";
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: `\n\nCompacting failed${reason}` },
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -865,6 +952,10 @@ export class ClaudeAcpAgent implements Agent {
|
||||
// The alternative (no update) leaves the client showing e.g.
|
||||
// "944k/1m" right after the user sees "Compacting completed",
|
||||
// which is confusing and wrong.
|
||||
//
|
||||
// The "Compacting completed." text is emitted from the `status`
|
||||
// handler (keyed on `compact_result`), not here, so the failure
|
||||
// path gets a message too.
|
||||
lastAssistantTotalUsage = 0;
|
||||
lastAssistantUsage = null;
|
||||
await this.client.sessionUpdate({
|
||||
@@ -875,13 +966,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
size: session.contextWindowSize,
|
||||
},
|
||||
});
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "\n\nCompacting completed." },
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "local_command_output": {
|
||||
@@ -943,6 +1027,22 @@ export class ClaudeAcpAgent implements Agent {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "commands_changed": {
|
||||
// Push the full slash-command list after a mid-session change
|
||||
// (e.g. skills discovered dynamically as the agent works in a
|
||||
// subdirectory). The client should REPLACE its cached command
|
||||
// list with this payload: supportedCommands() is captured once
|
||||
// at initialize and never reflects mid-session changes, so we
|
||||
// forward message.commands directly rather than re-querying.
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: message.session_id,
|
||||
update: {
|
||||
sessionUpdate: "available_commands_update",
|
||||
availableCommands: getAvailableSlashCommands(message.commands),
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "hook_started":
|
||||
case "hook_progress":
|
||||
case "hook_response":
|
||||
@@ -957,6 +1057,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
case "api_retry":
|
||||
case "mirror_error":
|
||||
case "permission_denied":
|
||||
case "thinking_tokens":
|
||||
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
|
||||
break;
|
||||
default:
|
||||
@@ -1014,6 +1115,26 @@ export class ClaudeAcpAgent implements Agent {
|
||||
break;
|
||||
}
|
||||
|
||||
// A refusal can arrive on any result subtype (and may even set
|
||||
// is_error), so handle it before the subtype switch — otherwise the
|
||||
// is_error throw below would surface it as an internal error. The
|
||||
// refused assistant message carries no visible content, so surface
|
||||
// the classifier's explanation (when available) and report ACP's
|
||||
// dedicated `refusal` stop reason.
|
||||
if (message.stop_reason === "refusal" && !isTaskNotification) {
|
||||
if (lastRefusalExplanation) {
|
||||
await this.client.sessionUpdate({
|
||||
sessionId: params.sessionId,
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: lastRefusalExplanation },
|
||||
},
|
||||
});
|
||||
}
|
||||
stopReason = "refusal";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (message.subtype) {
|
||||
case "success": {
|
||||
if (message.result.includes("Please run /login")) {
|
||||
@@ -1087,31 +1208,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
break;
|
||||
}
|
||||
case "stream_event": {
|
||||
// Track which assistant message ids have produced streamed text /
|
||||
// thinking deltas so the `assistant` case below can fall back to
|
||||
// emitting the assembled content when streaming didn't (e.g. when
|
||||
// the SDK is talking to a model proxy that never produces
|
||||
// `content_block_delta` events for the second model call in a
|
||||
// tool-use loop).
|
||||
//
|
||||
// This runs for every stream_event (not gated on
|
||||
// `parent_tool_use_id === null` like the usage block below) because
|
||||
// subagent assistant messages flow through the same content filter.
|
||||
// `currentStreamingMessageId` is a single field, which is safe
|
||||
// because the SDK serializes the event stream and `message_start`
|
||||
// always precedes its own `content_block_delta`s, so the captured
|
||||
// id is always the parent of the deltas that follow.
|
||||
if (message.event.type === "message_start") {
|
||||
session.currentStreamingMessageId = message.event.message.id;
|
||||
} else if (
|
||||
message.event.type === "content_block_delta" &&
|
||||
(message.event.delta.type === "text_delta" ||
|
||||
message.event.delta.type === "thinking_delta") &&
|
||||
session.currentStreamingMessageId
|
||||
) {
|
||||
session.textStreamedMessageIds.add(session.currentStreamingMessageId);
|
||||
}
|
||||
|
||||
if (
|
||||
message.parent_tool_use_id === null &&
|
||||
(message.event.type === "message_start" || message.event.type === "message_delta")
|
||||
@@ -1219,6 +1315,9 @@ export class ClaudeAcpAgent implements Agent {
|
||||
if (message.error) {
|
||||
lastAssistantError = message.error;
|
||||
}
|
||||
if (message.message.stop_reason === "refusal") {
|
||||
lastRefusalExplanation = message.message.stop_details?.explanation ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip <command-*>/<local-command-stdout> markers and render any
|
||||
@@ -1227,6 +1326,7 @@ export class ClaudeAcpAgent implements Agent {
|
||||
// payloads (e.g. /compact's malformed output) strip to null and are
|
||||
// skipped. Mirrors the replay path at replaySessionHistory.
|
||||
if (
|
||||
message.message.role !== "system" &&
|
||||
typeof message.message.content === "string" &&
|
||||
message.message.content.includes("<local-command-stdout>")
|
||||
) {
|
||||
@@ -1271,6 +1371,9 @@ export class ClaudeAcpAgent implements Agent {
|
||||
) {
|
||||
break;
|
||||
}
|
||||
if (message.message.role === "system") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
message.type === "assistant" &&
|
||||
@@ -1283,77 +1386,14 @@ export class ClaudeAcpAgent implements Agent {
|
||||
throw RequestError.authRequired();
|
||||
}
|
||||
|
||||
// Text and thinking blocks are normally emitted from the
|
||||
// `stream_event` case above as `content_block_delta` arrives,
|
||||
// so the assembled message replays them here. We skip them in
|
||||
// that case to avoid duplication. However, if streaming never
|
||||
// produced any text/thinking delta for this assistant message
|
||||
// (e.g. because the underlying model proxy returned the response
|
||||
// as a single non-streamed block, which is common with OpenAI-
|
||||
// compatible gateways translating to the Anthropic protocol), the
|
||||
// delta path emitted nothing — filtering the assembled blocks out
|
||||
// would drop the assistant's final text on the floor while still
|
||||
// returning `stopReason: "end_turn"`. Detect that case via
|
||||
// `textStreamedMessageIds` and emit the assembled blocks as a
|
||||
// fallback.
|
||||
//
|
||||
// The fallback dedup must be per content block, not per message
|
||||
// id: such gateways frequently deliver one assistant response as
|
||||
// several `assistant` messages that *share a message id* but each
|
||||
// carry a *different* block (e.g. an empty `thinking` block, then
|
||||
// the real `text` block). Flagging the whole id after the first
|
||||
// block would emit that block, then silently drop the real text
|
||||
// arriving under the same id. So we key the dedup on the block
|
||||
// contents, which lets distinct blocks through while still
|
||||
// collapsing identical re-deliveries (an incremental block
|
||||
// followed by a consolidated message, or the same message
|
||||
// delivered twice).
|
||||
const assistantMessageId =
|
||||
message.type === "assistant" ? message.message.id : undefined;
|
||||
const liveStreamed =
|
||||
!!assistantMessageId && session.textStreamedMessageIds.has(assistantMessageId);
|
||||
|
||||
let emittedFallbackBlock = false;
|
||||
const content =
|
||||
message.type === "assistant"
|
||||
? message.message.content.filter((item) => {
|
||||
if (item.type !== "text" && item.type !== "thinking") {
|
||||
return true;
|
||||
}
|
||||
// Already delivered live via streaming deltas — drop the
|
||||
// assembled copy to avoid duplication. Note this is
|
||||
// all-or-nothing per message id: a single delta of either
|
||||
// kind flags the id, so if a gateway streamed (say)
|
||||
// thinking deltas but delivered text only in the assembled
|
||||
// message under that same id, the assembled text is
|
||||
// dropped here. That mixed mode hasn't been observed —
|
||||
// gateways stream either everything or nothing — and it is
|
||||
// not a regression (the old code dropped it unconditionally).
|
||||
if (liveStreamed) {
|
||||
return false;
|
||||
}
|
||||
// Fallback path: emit each assembled block once, keyed on
|
||||
// its contents so re-delivered blocks dedup but distinct
|
||||
// blocks sharing a message id are all forwarded.
|
||||
const blockText = item.type === "text" ? item.text : item.thinking;
|
||||
const key = `${assistantMessageId ?? "<unknown>"}\u0000${item.type}\u0000${blockText}`;
|
||||
if (session.fallbackEmittedBlockKeys.has(key)) {
|
||||
return false;
|
||||
}
|
||||
session.fallbackEmittedBlockKeys.add(key);
|
||||
emittedFallbackBlock = true;
|
||||
return true;
|
||||
})
|
||||
? // Handled by stream events above
|
||||
message.message.content.filter(
|
||||
(item) => !["text", "thinking"].includes(item.type),
|
||||
)
|
||||
: message.message.content;
|
||||
|
||||
if (emittedFallbackBlock) {
|
||||
this.logger.log(
|
||||
`[claude-agent-acp] No streamed text/thinking deltas seen for ` +
|
||||
`assistant message ${assistantMessageId ?? "<unknown>"}; ` +
|
||||
`emitting assembled blocks as fallback.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const notification of toAcpNotifications(
|
||||
content,
|
||||
message.message.role,
|
||||
@@ -1435,6 +1475,16 @@ export class ClaudeAcpAgent implements Agent {
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
// The loop is returning — interrupt() succeeded or the prompt finished
|
||||
// — so disarm the force-cancel backstop and release the wake-up channel
|
||||
// (only if we still own it; a handoff installs the next prompt's).
|
||||
if (session.forceCancelTimer) {
|
||||
clearTimeout(session.forceCancelTimer);
|
||||
session.forceCancelTimer = undefined;
|
||||
}
|
||||
if (session.cancelController === cancelController) {
|
||||
session.cancelController = undefined;
|
||||
}
|
||||
if (!handedOff) {
|
||||
session.promptRunning = false;
|
||||
if (errored) {
|
||||
@@ -1472,6 +1522,33 @@ export class ClaudeAcpAgent implements Agent {
|
||||
pending.resolve(true);
|
||||
}
|
||||
session.pendingMessages.clear();
|
||||
|
||||
// Arm a backstop before interrupting: if a prompt is actively consuming
|
||||
// the query and interrupt() doesn't make the SDK yield (e.g. a wedged
|
||||
// TaskOutput block — issue #680), force the loop to return "cancelled"
|
||||
// after the floor elapses so the pending session/prompt still resolves per
|
||||
// the ACP cancellation contract instead of hanging forever. The loop's
|
||||
// `finally` clears this timer when interrupt() works and it returns through
|
||||
// the normal idle path, so on healthy cancels it is armed but never fires.
|
||||
//
|
||||
// Arm at most once per turn: the floor is an absolute ceiling from the
|
||||
// first cancel, so a client that re-sends cancel (each call still retries
|
||||
// interrupt() below) can't keep pushing the deadline out.
|
||||
if (
|
||||
session.promptRunning &&
|
||||
session.cancelController &&
|
||||
!session.cancelController.signal.aborted &&
|
||||
!session.forceCancelTimer
|
||||
) {
|
||||
const cancelController = session.cancelController;
|
||||
session.forceCancelTimer = setTimeout(() => {
|
||||
this.logger.error(
|
||||
`Session ${params.sessionId}: cancel floor elapsed without the SDK yielding; forcing "cancelled". The underlying query may still be wedged — a new session may be required.`,
|
||||
);
|
||||
cancelController.abort();
|
||||
}, this.forceCancelGraceMs);
|
||||
}
|
||||
|
||||
await session.query.interrupt();
|
||||
}
|
||||
|
||||
@@ -1483,6 +1560,18 @@ export class ClaudeAcpAgent implements Agent {
|
||||
return;
|
||||
}
|
||||
await this.cancel({ sessionId });
|
||||
// cancel() arms the force-cancel floor and interrupts gracefully, but a
|
||||
// wedged prompt loop only wakes when `cancelController` aborts — closing
|
||||
// the query/abortController below doesn't touch it. Since we're tearing the
|
||||
// session down anyway, wake the loop now so the in-flight prompt() resolves
|
||||
// immediately instead of after the floor, and clear the timer so it can't
|
||||
// outlive the deleted session (it isn't unref'd and would otherwise keep
|
||||
// the event loop alive until it fires).
|
||||
if (session.forceCancelTimer) {
|
||||
clearTimeout(session.forceCancelTimer);
|
||||
session.forceCancelTimer = undefined;
|
||||
}
|
||||
session.cancelController?.abort();
|
||||
session.settingsManager.dispose();
|
||||
session.abortController.abort();
|
||||
session.query.close();
|
||||
@@ -1512,22 +1601,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
return {};
|
||||
}
|
||||
|
||||
async unstable_setSessionModel(
|
||||
params: SetSessionModelRequest,
|
||||
): Promise<SetSessionModelResponse | void> {
|
||||
const session = this.sessions[params.sessionId];
|
||||
if (!session) {
|
||||
throw new Error("Session not found");
|
||||
}
|
||||
// Resolve aliases (e.g. "opus", "opus[1m]") to canonical model IDs so
|
||||
// downstream lookups in modelInfos succeed and the effort option isn't
|
||||
// silently dropped.
|
||||
const resolved = resolveModelPreference(session.modelInfos, params.modelId);
|
||||
const modelId = resolved?.value ?? params.modelId;
|
||||
await session.query.setModel(modelId);
|
||||
await this.updateConfigOption(params.sessionId, "model", modelId);
|
||||
}
|
||||
|
||||
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
|
||||
if (!this.sessions[params.sessionId]) {
|
||||
throw new Error("Session not found");
|
||||
@@ -1986,7 +2059,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
return {
|
||||
sessionId: params.sessionId,
|
||||
modes: existingSession.modes,
|
||||
models: existingSession.models,
|
||||
configOptions: existingSession.configOptions,
|
||||
};
|
||||
}
|
||||
@@ -2012,7 +2084,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
return {
|
||||
sessionId: response.sessionId,
|
||||
modes: response.modes,
|
||||
models: response.models,
|
||||
configOptions: response.configOptions,
|
||||
};
|
||||
}
|
||||
@@ -2377,13 +2448,10 @@ export class ClaudeAcpAgent implements Agent {
|
||||
contextWindowSize:
|
||||
inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
|
||||
taskState,
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
models,
|
||||
modes,
|
||||
configOptions,
|
||||
};
|
||||
@@ -3261,6 +3329,7 @@ export function toAcpNotifications(
|
||||
case "compaction":
|
||||
case "compaction_delta":
|
||||
case "advisor_tool_result":
|
||||
case "mid_conv_system":
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -3328,7 +3397,11 @@ export function streamEventToAcpNotifications(
|
||||
taskState: options?.taskState,
|
||||
},
|
||||
);
|
||||
// No content
|
||||
// No content. `ping` is a Messages-API keep-alive event that the SDK's
|
||||
// `BetaRawMessageStreamEvent` union doesn't include even though the
|
||||
// wire format emits it; the `as never` cast lets us no-op it here
|
||||
// instead of letting it fall through to `unreachable`.
|
||||
case "ping" as never:
|
||||
case "message_start":
|
||||
case "message_delta":
|
||||
case "message_stop":
|
||||
|
||||
@@ -3,4 +3,4 @@ description: Say hello
|
||||
argument-hint: name
|
||||
---
|
||||
|
||||
Respond with "Hello $1" and nothing else.
|
||||
Respond with "Hello $ARGUMENTS" and nothing else.
|
||||
|
||||
@@ -225,7 +225,9 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
// No setModel call is needed because no override was applied — the SDK is
|
||||
// already on its own default.
|
||||
expect(setModelSpy).not.toHaveBeenCalled();
|
||||
expect(response.models.currentModelId).toBe("claude-sonnet-4-6");
|
||||
expect(response.configOptions?.find((o: any) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-sonnet-4-6",
|
||||
);
|
||||
});
|
||||
|
||||
describe("auto mode availability per model", () => {
|
||||
@@ -578,7 +580,9 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
});
|
||||
|
||||
expect(setModelSpy).toHaveBeenCalledWith("claude-haiku-4-5");
|
||||
expect(response.models.currentModelId).toBe("claude-haiku-4-5");
|
||||
expect(response.configOptions?.find((o: any) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-haiku-4-5",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not inherit display info across mismatched model family versions", async () => {
|
||||
@@ -687,7 +691,9 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
});
|
||||
|
||||
expect(setModelSpy).toHaveBeenCalledWith("claude-opus-4-6-1m");
|
||||
expect(response.models.currentModelId).toBe("claude-opus-4-6-1m");
|
||||
expect(response.configOptions?.find((o: any) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-opus-4-6-1m",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips the initial setModel when the resolved value matches the SDK's model list verbatim", async () => {
|
||||
@@ -727,7 +733,9 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
});
|
||||
|
||||
expect(setModelSpy).not.toHaveBeenCalled();
|
||||
expect(response.models.currentModelId).toBe("claude-opus-4-6");
|
||||
expect(response.configOptions?.find((o: any) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-opus-4-6",
|
||||
);
|
||||
} finally {
|
||||
if (originalEnv === undefined) {
|
||||
delete process.env.ANTHROPIC_MODEL;
|
||||
@@ -777,6 +785,8 @@ describe("ClaudeAcpAgent settings", () => {
|
||||
});
|
||||
|
||||
expect(setModelSpy).toHaveBeenCalledWith("claude-haiku-4-5");
|
||||
expect(response.models.currentModelId).toBe("claude-haiku-4-5");
|
||||
expect(response.configOptions?.find((o: any) => o.id === "model")?.currentValue).toBe(
|
||||
"claude-haiku-4-5",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ClaudeAcpAgent,
|
||||
claudeCliPath,
|
||||
describeAlwaysAllow,
|
||||
streamEventToAcpNotifications,
|
||||
type SDKMessageFilter,
|
||||
} from "../acp-agent.js";
|
||||
import { Pushable } from "../utils.js";
|
||||
@@ -238,13 +239,15 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
name: "compact",
|
||||
});
|
||||
|
||||
// Send something
|
||||
await connection.prompt({
|
||||
prompt: [{ type: "text", text: "Hi" }],
|
||||
sessionId: newSessionResponse.sessionId,
|
||||
});
|
||||
// Clear response
|
||||
client.takeReceivedText();
|
||||
// Build up enough conversation that there's something to compact. The SDK
|
||||
// refuses to compact a conversation with too few message groups.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await connection.prompt({
|
||||
prompt: [{ type: "text", text: `Reply with just the number ${i}.` }],
|
||||
sessionId: newSessionResponse.sessionId,
|
||||
});
|
||||
client.takeReceivedText();
|
||||
}
|
||||
|
||||
await connection.prompt({
|
||||
prompt: [
|
||||
@@ -257,7 +260,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
});
|
||||
|
||||
expect(client.takeReceivedText()).toContain("Compacting...\n\nCompacting completed.");
|
||||
}, 30000);
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
describe("tool conversions", () => {
|
||||
@@ -662,6 +665,7 @@ describe("tool conversions", () => {
|
||||
server_tool_use: null,
|
||||
inference_geo: null,
|
||||
iterations: null,
|
||||
output_tokens_details: null,
|
||||
speed: null,
|
||||
},
|
||||
context_management: null,
|
||||
@@ -1544,8 +1548,6 @@ describe("stop reason propagation", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1690,8 +1692,6 @@ describe("stop reason propagation", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
|
||||
const response = await agent.prompt({
|
||||
@@ -1810,409 +1810,6 @@ describe("stop reason propagation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("assistant text fallback when streaming yields no text deltas", () => {
|
||||
function createCapturingAgent() {
|
||||
const notifications: SessionNotification[] = [];
|
||||
const mockClient = {
|
||||
sessionUpdate: async (n: SessionNotification) => {
|
||||
notifications.push(n);
|
||||
},
|
||||
extNotification: async () => {},
|
||||
} as unknown as AgentSideConnection;
|
||||
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
return { agent, notifications };
|
||||
}
|
||||
|
||||
function injectSession(agent: ClaudeAcpAgent, messages: any[]) {
|
||||
const input = new Pushable<any>();
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const { value: userMessage, done } = await iter.next();
|
||||
if (!done && userMessage) {
|
||||
yield {
|
||||
type: "user",
|
||||
message: userMessage.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: userMessage.uuid,
|
||||
session_id: "test-session",
|
||||
isReplay: true,
|
||||
};
|
||||
}
|
||||
yield* messages;
|
||||
}
|
||||
agent.sessions["test-session"] = {
|
||||
query: messageGenerator() as any,
|
||||
input,
|
||||
cancelled: false,
|
||||
cwd: "/test",
|
||||
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
|
||||
modes: { currentModeId: "default", availableModes: [] },
|
||||
models: { currentModelId: "default", availableModels: [] },
|
||||
modelInfos: [],
|
||||
settingsManager: { dispose: vi.fn() } as any,
|
||||
accumulatedUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cachedReadTokens: 0,
|
||||
cachedWriteTokens: 0,
|
||||
},
|
||||
configOptions: [],
|
||||
promptRunning: false,
|
||||
pendingMessages: new Map(),
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
function makeResult() {
|
||||
return {
|
||||
type: "result" as const,
|
||||
subtype: "success" as const,
|
||||
stop_reason: null,
|
||||
is_error: false,
|
||||
result: "",
|
||||
errors: [],
|
||||
duration_ms: 0,
|
||||
duration_api_ms: 0,
|
||||
num_turns: 1,
|
||||
total_cost_usd: 0,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
modelUsage: {},
|
||||
permission_denials: [],
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
};
|
||||
}
|
||||
|
||||
function makeAssistantMessage(messageId: string, text: string) {
|
||||
return makeAssistantMessageWithBlocks(messageId, [{ type: "text", text }]);
|
||||
}
|
||||
|
||||
// Same as makeAssistantMessage but with arbitrary content blocks, so tests
|
||||
// can reproduce gateways that split a single response (one message id) into
|
||||
// several `assistant` messages each carrying a different block.
|
||||
function makeAssistantMessageWithBlocks(messageId: string, content: any[]) {
|
||||
return {
|
||||
type: "assistant" as const,
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
message: {
|
||||
id: messageId,
|
||||
type: "message" as const,
|
||||
role: "assistant" as const,
|
||||
model: "claude-test",
|
||||
content,
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("emits the assembled text when no content_block_delta was streamed", async () => {
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "msg_no_stream";
|
||||
const finalText = "final answer never streamed";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessage(messageId, finalText),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const textChunks = notifications
|
||||
.map((n) => n.update)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === finalText,
|
||||
);
|
||||
expect(textChunks.length).toBe(1);
|
||||
});
|
||||
|
||||
it("does not re-emit text already streamed via content_block_delta", async () => {
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "msg_streamed";
|
||||
const streamedText = "streamed answer";
|
||||
injectSession(agent, [
|
||||
{
|
||||
type: "stream_event",
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: {
|
||||
type: "message_start",
|
||||
message: {
|
||||
id: messageId,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
model: "claude-test",
|
||||
content: [],
|
||||
stop_reason: null,
|
||||
stop_sequence: null,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "stream_event",
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
event: {
|
||||
type: "content_block_delta",
|
||||
index: 0,
|
||||
delta: { type: "text_delta", text: streamedText },
|
||||
},
|
||||
},
|
||||
makeAssistantMessage(messageId, streamedText),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const textChunks = notifications
|
||||
.map((n) => n.update)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === streamedText,
|
||||
);
|
||||
// Only the streamed content_block_delta should produce a chunk; the
|
||||
// assembled message must not duplicate it.
|
||||
expect(textChunks.length).toBe(1);
|
||||
});
|
||||
|
||||
it("fallback emits exactly once when the same assistant message is delivered twice", async () => {
|
||||
// Regression test: the SDK occasionally delivers the same assembled
|
||||
// assistant message twice (observed in production logs). Without
|
||||
// recording the id into `textStreamedMessageIds` after the first
|
||||
// fallback emit, the second delivery would re-fire the fallback and
|
||||
// ACP clients would see duplicate text chunks. After the dedupe fix
|
||||
// the second delivery must be a no-op.
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "msg_delivered_twice";
|
||||
const finalText = "final answer delivered twice";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessage(messageId, finalText),
|
||||
makeAssistantMessage(messageId, finalText),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const textChunks = notifications
|
||||
.map((n) => n.update)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === finalText,
|
||||
);
|
||||
expect(textChunks.length).toBe(1);
|
||||
});
|
||||
|
||||
it("textStreamedMessageIds is cleared at the start of each new turn", async () => {
|
||||
// Regression test: assistant message ids are turn-scoped. Without
|
||||
// per-turn clear, the set would accumulate over the session's lifetime
|
||||
// and a (rare) reuse of an id across turns would be misclassified as
|
||||
// "already streamed", silently dropping the fallback emit. The two
|
||||
// turns below use the same messageId; both should fallback-emit
|
||||
// exactly once because the set is reset on each `prompt()` entry.
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "msg_reused_across_turns";
|
||||
const finalText = "answer for both turns";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessage(messageId, finalText),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
makeAssistantMessage(messageId, finalText),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const first = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "turn 1" }],
|
||||
});
|
||||
const second = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "turn 2" }],
|
||||
});
|
||||
|
||||
expect(first.stopReason).toBe("end_turn");
|
||||
expect(second.stopReason).toBe("end_turn");
|
||||
const textChunks = notifications
|
||||
.map((n) => n.update)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === finalText,
|
||||
);
|
||||
// Two turns each fallback-emit once.
|
||||
expect(textChunks.length).toBe(2);
|
||||
});
|
||||
|
||||
it("emits the text block when it arrives in a later same-id message than the thinking block", async () => {
|
||||
// Production regression (OpenAI-compatible gpt-5 gateway): a single
|
||||
// assistant response (one message id) is delivered as *separate*
|
||||
// `assistant` messages — an empty `thinking` block first, then the real
|
||||
// `text` block — both sharing the same id and neither streamed via
|
||||
// content_block_delta. Deduping at message-id granularity flagged the id
|
||||
// on the thinking block and then dropped the text, so the client saw the
|
||||
// fallback fire but received an empty answer. The fallback must key on the
|
||||
// block contents so the later text block is still emitted.
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "resp_split_blocks";
|
||||
const finalText = "=== Ticket Review Summary === approved";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessageWithBlocks(messageId, [
|
||||
{ type: "thinking", thinking: "", signature: "sig" },
|
||||
]),
|
||||
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: finalText }]),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const textChunks = notifications
|
||||
.map((n) => n.update)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === finalText,
|
||||
);
|
||||
expect(textChunks.length).toBe(1);
|
||||
});
|
||||
|
||||
it("emits distinct text blocks that share a message id", async () => {
|
||||
// A gateway may split a multi-block answer (thinking + text + a second
|
||||
// text) across same-id messages. Every distinct text block must reach the
|
||||
// client exactly once.
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "resp_multi_text";
|
||||
const firstText = "我先直接执行审批并返回工单结果。";
|
||||
const secondText = "Ticket approved.";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessageWithBlocks(messageId, [
|
||||
{ type: "thinking", thinking: "considering options", signature: "sig" },
|
||||
]),
|
||||
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: firstText }]),
|
||||
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: secondText }]),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const updates = notifications.map((n) => n.update);
|
||||
const firstChunks = updates.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === firstText,
|
||||
);
|
||||
const secondChunks = updates.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === secondText,
|
||||
);
|
||||
expect(firstChunks.length).toBe(1);
|
||||
expect(secondChunks.length).toBe(1);
|
||||
});
|
||||
|
||||
it("does not duplicate text when an incremental block is followed by a consolidated message", async () => {
|
||||
// Some gateways deliver an incremental per-block `assistant` message and
|
||||
// then a consolidated `assistant` message (same id) that repeats that
|
||||
// block alongside others. The repeated block must not be emitted twice.
|
||||
const { agent, notifications } = createCapturingAgent();
|
||||
const messageId = "resp_consolidated";
|
||||
const thinking = "step-by-step reasoning";
|
||||
const finalText = "the answer";
|
||||
injectSession(agent, [
|
||||
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: finalText }]),
|
||||
makeAssistantMessageWithBlocks(messageId, [
|
||||
{ type: "thinking", thinking, signature: "sig" },
|
||||
{ type: "text", text: finalText },
|
||||
]),
|
||||
makeResult(),
|
||||
{ type: "system", subtype: "session_state_changed", state: "idle" },
|
||||
]);
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "go" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
const updates = notifications.map((n) => n.update);
|
||||
const textChunks = updates.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_message_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === finalText,
|
||||
);
|
||||
const thoughtChunks = updates.filter(
|
||||
(u) =>
|
||||
u.sessionUpdate === "agent_thought_chunk" &&
|
||||
u.content.type === "text" &&
|
||||
u.content.text === thinking,
|
||||
);
|
||||
expect(textChunks.length).toBe(1);
|
||||
expect(thoughtChunks.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/close", () => {
|
||||
function createMockAgent() {
|
||||
const mockClient = {
|
||||
@@ -2254,8 +1851,6 @@ describe("session/close", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
@@ -2340,8 +1935,6 @@ describe("session/delete", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
@@ -2443,8 +2036,6 @@ describe("getOrCreateSession param change detection", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
return agent.sessions[sessionId]!;
|
||||
}
|
||||
@@ -2680,8 +2271,6 @@ describe("usage_update computation", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3582,8 +3171,6 @@ describe("emitRawSDKMessages", () => {
|
||||
emitRawSDKMessages,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3812,8 +3399,6 @@ describe("result origin handling", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3989,8 +3574,6 @@ describe("memory_recall handling", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4221,8 +3804,6 @@ describe("post-error recovery", () => {
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
textStreamedMessageIds: new Set<string>(),
|
||||
fallbackEmittedBlockKeys: new Set<string>(),
|
||||
};
|
||||
return { interrupt };
|
||||
}
|
||||
@@ -4295,3 +3876,216 @@ describe("post-error recovery", () => {
|
||||
expect(session.pendingMessages.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session/cancel wedge recovery (issue #680)", () => {
|
||||
function createMockAgent() {
|
||||
const mockClient = {
|
||||
sessionUpdate: async () => {},
|
||||
} as unknown as AgentSideConnection;
|
||||
return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
|
||||
}
|
||||
|
||||
// Generator that replays the prompt's user message and then blocks forever,
|
||||
// simulating the SDK wedged in a `TaskOutput { block: true }` poll against a
|
||||
// hung background task. `interrupt()` is a no-op — it does NOT unblock the
|
||||
// generator, matching the SDK behavior described in the issue.
|
||||
function injectWedgedSession(agent: ClaudeAcpAgent, opts: { interruptUnblocks?: boolean } = {}) {
|
||||
const input = new Pushable<any>();
|
||||
const interrupt = vi.fn(async () => {});
|
||||
const close = vi.fn();
|
||||
// A promise the wedged poll awaits. When `interruptUnblocks` is set, the
|
||||
// mocked interrupt() resolves it so the generator yields a trailing idle —
|
||||
// the normal, healthy interrupt path.
|
||||
let releaseBlock!: () => void;
|
||||
const blocked = new Promise<void>((resolve) => {
|
||||
releaseBlock = resolve;
|
||||
});
|
||||
if (opts.interruptUnblocks) {
|
||||
interrupt.mockImplementation(async () => {
|
||||
releaseBlock();
|
||||
});
|
||||
}
|
||||
|
||||
async function* messageGenerator() {
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const first = await iter.next();
|
||||
if (!first.done && first.value) {
|
||||
yield {
|
||||
type: "user",
|
||||
message: first.value.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: first.value.uuid,
|
||||
session_id: "test-session",
|
||||
isReplay: true,
|
||||
};
|
||||
}
|
||||
// Wedge: never yield again unless interrupt() releases us.
|
||||
await blocked;
|
||||
yield { type: "system", subtype: "session_state_changed", state: "idle" };
|
||||
}
|
||||
|
||||
const gen = Object.assign(messageGenerator(), { interrupt, close });
|
||||
agent.sessions["test-session"] = {
|
||||
query: gen as any,
|
||||
input,
|
||||
cancelled: false,
|
||||
cwd: "/test",
|
||||
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
|
||||
modes: { currentModeId: "default", availableModes: [] },
|
||||
models: { currentModelId: "default", availableModels: [] },
|
||||
modelInfos: [],
|
||||
settingsManager: { dispose: vi.fn() } as any,
|
||||
accumulatedUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cachedReadTokens: 0,
|
||||
cachedWriteTokens: 0,
|
||||
},
|
||||
configOptions: [],
|
||||
promptRunning: false,
|
||||
pendingMessages: new Map(),
|
||||
nextPendingOrder: 0,
|
||||
abortController: new AbortController(),
|
||||
emitRawSDKMessages: false,
|
||||
contextWindowSize: 200000,
|
||||
taskState: new Map(),
|
||||
};
|
||||
return { interrupt };
|
||||
}
|
||||
|
||||
it("resolves the pending prompt with cancelled when the SDK never yields after interrupt", async () => {
|
||||
const agent = createMockAgent();
|
||||
// Shrink the grace period so the test doesn't wait the production default.
|
||||
agent.forceCancelGraceMs = 20;
|
||||
const { interrupt } = injectWedgedSession(agent);
|
||||
|
||||
const promptPromise = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "run cargo test" }],
|
||||
});
|
||||
|
||||
// Let the loop consume the replay and block on the wedged query.next().
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
|
||||
const response = await promptPromise;
|
||||
expect(response.stopReason).toBe("cancelled");
|
||||
expect(interrupt).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns cancelled through the normal idle path without waiting the grace period when interrupt works", async () => {
|
||||
const agent = createMockAgent();
|
||||
// Large grace so that if the test ever falls through to the backstop it
|
||||
// would hang past the test timeout instead of passing by accident.
|
||||
agent.forceCancelGraceMs = 60_000;
|
||||
const { interrupt } = injectWedgedSession(agent, { interruptUnblocks: true });
|
||||
|
||||
const promptPromise = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "run cargo test" }],
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
|
||||
const response = await promptPromise;
|
||||
expect(response.stopReason).toBe("cancelled");
|
||||
expect(interrupt).toHaveBeenCalled();
|
||||
// Backstop timer must have been cleared so it can't fire later.
|
||||
expect(agent.sessions["test-session"].forceCancelTimer).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not arm the backstop when no prompt is running", async () => {
|
||||
const agent = createMockAgent();
|
||||
injectWedgedSession(agent);
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
|
||||
const session = agent.sessions["test-session"];
|
||||
expect(session.cancelled).toBe(true);
|
||||
expect(session.forceCancelTimer).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not reset the force-cancel floor on repeated cancels", async () => {
|
||||
const agent = createMockAgent();
|
||||
// Long floor so the timer handle stays observable across both cancels.
|
||||
agent.forceCancelGraceMs = 60_000;
|
||||
injectWedgedSession(agent);
|
||||
|
||||
const promptPromise = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "run cargo test" }],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
const firstTimer = agent.sessions["test-session"].forceCancelTimer;
|
||||
expect(firstTimer).toBeDefined();
|
||||
|
||||
await agent.cancel({ sessionId: "test-session" });
|
||||
// Same handle: the second cancel did not clear-and-rearm (which would push
|
||||
// the floor out). The deadline stays anchored to the first cancel.
|
||||
expect(agent.sessions["test-session"].forceCancelTimer).toBe(firstTimer);
|
||||
|
||||
// Clean up the wedged prompt + long timer.
|
||||
await agent.closeSession({ sessionId: "test-session" });
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "cancelled" });
|
||||
});
|
||||
|
||||
it("resolves an in-flight wedged prompt immediately when the session is closed", async () => {
|
||||
const agent = createMockAgent();
|
||||
// Large floor: if closeSession relied on the force-cancel timer this would
|
||||
// hang past the test timeout. Teardown must wake the loop via
|
||||
// cancelController instead.
|
||||
agent.forceCancelGraceMs = 60_000;
|
||||
injectWedgedSession(agent);
|
||||
|
||||
const promptPromise = agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "run cargo test" }],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
|
||||
await agent.closeSession({ sessionId: "test-session" });
|
||||
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "cancelled" });
|
||||
expect(agent.sessions["test-session"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("streamEventToAcpNotifications", () => {
|
||||
it("treats `ping` keep-alive events as no-ops without logging to stderr", () => {
|
||||
const errors: unknown[][] = [];
|
||||
const logger = {
|
||||
log: () => {},
|
||||
error: (...args: unknown[]) => {
|
||||
errors.push(args);
|
||||
},
|
||||
};
|
||||
const pingMessage = {
|
||||
type: "stream_event",
|
||||
parent_tool_use_id: null,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
// The SDK's typed `BetaRawMessageStreamEvent` union doesn't include
|
||||
// `ping`, but the API emits it on the wire and the SDK passes it
|
||||
// through. Cast through `unknown` to feed the realistic runtime shape.
|
||||
event: { type: "ping" } as unknown,
|
||||
} as Parameters<typeof streamEventToAcpNotifications>[0];
|
||||
|
||||
const result = streamEventToAcpNotifications(
|
||||
pingMessage,
|
||||
"test-session",
|
||||
{},
|
||||
{ sessionUpdate: async () => {} } as unknown as Parameters<
|
||||
typeof streamEventToAcpNotifications
|
||||
>[3],
|
||||
logger,
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,10 @@ const mockQuery = vi.hoisted(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", () => ({
|
||||
vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({
|
||||
...(await vi.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
|
||||
"@anthropic-ai/claude-agent-sdk",
|
||||
)),
|
||||
query: mockQuery,
|
||||
}));
|
||||
|
||||
|
||||
@@ -377,33 +377,30 @@ describe("session config options", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("unstable_setSessionModel sends config_option_update", () => {
|
||||
describe("setSessionConfigOption(model) returns updated configOptions", () => {
|
||||
beforeEach(() => {
|
||||
populateSession();
|
||||
});
|
||||
|
||||
it("sends config_option_update when model is changed via setSessionModel", async () => {
|
||||
await agent.unstable_setSessionModel({
|
||||
it("returns configOptions with the new model when changed", async () => {
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
expect(configUpdate).toBeDefined();
|
||||
expect(configUpdate?.update).toMatchObject({
|
||||
sessionUpdate: "config_option_update",
|
||||
configOptions: expect.arrayContaining([
|
||||
expect(response.configOptions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: "model", currentValue: "claude-sonnet-4-6" }),
|
||||
]),
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
it("updates stored configOptions currentValue when model changes", async () => {
|
||||
await agent.unstable_setSessionModel({
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const session = (
|
||||
@@ -415,7 +412,7 @@ describe("session config options", () => {
|
||||
expect(modelOption?.currentValue).toBe("claude-sonnet-4-6");
|
||||
});
|
||||
|
||||
it("includes updated effort in config_option_update when model drops effort support", async () => {
|
||||
it("drops effort from returned configOptions when model drops effort support", async () => {
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
session.modelInfos = [
|
||||
{
|
||||
@@ -433,23 +430,18 @@ describe("session config options", () => {
|
||||
},
|
||||
];
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
expect(configUpdate).toBeDefined();
|
||||
const effortOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "effort",
|
||||
);
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption).toBeUndefined();
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("clamps effort in config_option_update when new model has different supported levels", async () => {
|
||||
it("clamps effort in returned configOptions when new model has different supported levels", async () => {
|
||||
// Set current effort to "max" which the new model won't support
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
@@ -472,39 +464,31 @@ describe("session config options", () => {
|
||||
},
|
||||
];
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
const effortOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "effort",
|
||||
);
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption).toBeDefined();
|
||||
expect(effortOption.currentValue).toBe("default");
|
||||
expect(effortOption?.currentValue).toBe("default");
|
||||
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
|
||||
});
|
||||
|
||||
it("preserves effort in config_option_update when new model supports same level", async () => {
|
||||
it("preserves effort in returned configOptions when new model supports same level", async () => {
|
||||
// Set effort to "low" first
|
||||
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
|
||||
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
|
||||
if (effortOpt) effortOpt.currentValue = "low";
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
const effortOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "effort",
|
||||
);
|
||||
const effortOption = response.configOptions.find((o) => o.id === "effort");
|
||||
expect(effortOption?.currentValue).toBe("low");
|
||||
// Effort didn't change, so applyFlagSettings should NOT be called
|
||||
expect(applyFlagSettingsSpy).not.toHaveBeenCalled();
|
||||
@@ -821,10 +805,11 @@ describe("session config options", () => {
|
||||
expect(session.configOptions.find((o) => o.id === "mode")?.currentValue).toBe("plan");
|
||||
});
|
||||
|
||||
it("setSessionModel also syncs configOptions", async () => {
|
||||
await agent.unstable_setSessionModel({
|
||||
it("setSessionConfigOption(model) also syncs configOptions", async () => {
|
||||
await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-sonnet-4-6",
|
||||
configId: "model",
|
||||
value: "claude-sonnet-4-6",
|
||||
});
|
||||
|
||||
const session = (
|
||||
@@ -947,58 +932,21 @@ describe("session config options", () => {
|
||||
it("drops `auto` from available modes when switching to Haiku", async () => {
|
||||
setupHaikuOpusSession("default");
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-haiku-4-5",
|
||||
configId: "model",
|
||||
value: "claude-haiku-4-5",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
expect(configUpdate).toBeDefined();
|
||||
const modeOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "mode",
|
||||
);
|
||||
const modeOption = response.configOptions.find((o) => o.id === "mode");
|
||||
expect(modeOption).toBeDefined();
|
||||
const modeValues = modeOption.options.map((o: any) => o.value);
|
||||
const modeValues = (modeOption as any).options.map((o: any) => o.value);
|
||||
expect(modeValues).not.toContain("auto");
|
||||
expect(modeValues).toEqual(
|
||||
expect.arrayContaining(["default", "acceptEdits", "plan", "dontAsk"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("clamps to `default` and emits current_mode_update when Opus(auto) → Haiku", async () => {
|
||||
setupHaikuOpusSession("auto");
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-haiku-4-5",
|
||||
});
|
||||
|
||||
// SDK was synced to "default".
|
||||
expect(setPermissionModeSpy).toHaveBeenCalledWith("default");
|
||||
|
||||
// current_mode_update was emitted before config_option_update so a
|
||||
// client applying notifications in order observes the mode change
|
||||
// before re-rendering the config-option list.
|
||||
const modeUpdateIdx = sessionUpdates.findIndex(
|
||||
(n) => n.update.sessionUpdate === "current_mode_update",
|
||||
);
|
||||
const configUpdateIdx = sessionUpdates.findIndex(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
expect(modeUpdateIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(configUpdateIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(modeUpdateIdx).toBeLessThan(configUpdateIdx);
|
||||
expect((sessionUpdates[modeUpdateIdx].update as any).currentModeId).toBe("default");
|
||||
|
||||
// configOptions reflect the clamped mode.
|
||||
const modeOption = (sessionUpdates[configUpdateIdx].update as any).configOptions.find(
|
||||
(o: any) => o.id === "mode",
|
||||
);
|
||||
expect(modeOption.currentValue).toBe("default");
|
||||
});
|
||||
|
||||
it("re-adds `auto` when switching from Haiku back to Opus", async () => {
|
||||
const session = setupHaikuOpusSession("default");
|
||||
// Pretend Haiku is the current model with no `auto`.
|
||||
@@ -1013,19 +961,15 @@ describe("session config options", () => {
|
||||
description: m.description,
|
||||
}));
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-opus-4-5",
|
||||
configId: "model",
|
||||
value: "claude-opus-4-5",
|
||||
});
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
expect(configUpdate).toBeDefined();
|
||||
const modeOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "mode",
|
||||
);
|
||||
const modeValues = modeOption.options.map((o: any) => o.value);
|
||||
const modeOption = response.configOptions.find((o) => o.id === "mode");
|
||||
expect(modeOption).toBeDefined();
|
||||
const modeValues = (modeOption as any).options.map((o: any) => o.value);
|
||||
expect(modeValues).toContain("auto");
|
||||
|
||||
// The current mode ("default") is still valid on Opus, so no
|
||||
@@ -1039,9 +983,10 @@ describe("session config options", () => {
|
||||
it("preserves the current mode when it remains valid after a model switch", async () => {
|
||||
setupHaikuOpusSession("plan");
|
||||
|
||||
await agent.unstable_setSessionModel({
|
||||
const response = await agent.setSessionConfigOption({
|
||||
sessionId: SESSION_ID,
|
||||
modelId: "claude-haiku-4-5",
|
||||
configId: "model",
|
||||
value: "claude-haiku-4-5",
|
||||
});
|
||||
|
||||
// `plan` is in availableModes for both Opus and Haiku, so no clamp.
|
||||
@@ -1052,20 +997,15 @@ describe("session config options", () => {
|
||||
);
|
||||
expect(modeUpdates).toHaveLength(0);
|
||||
|
||||
const configUpdate = sessionUpdates.find(
|
||||
(n) => n.update.sessionUpdate === "config_option_update",
|
||||
);
|
||||
const modeOption = (configUpdate?.update as any).configOptions.find(
|
||||
(o: any) => o.id === "mode",
|
||||
);
|
||||
expect(modeOption.currentValue).toBe("plan");
|
||||
const modeOption = response.configOptions.find((o) => o.id === "mode");
|
||||
expect((modeOption as any).currentValue).toBe("plan");
|
||||
});
|
||||
|
||||
it("clamps mode and emits current_mode_update via setSessionConfigOption(model)", async () => {
|
||||
// Mirrors the unstable_setSessionModel(auto → Haiku) test, but goes
|
||||
// through the request/response API. The `current_mode_update` side
|
||||
// effect must still fire so clients learn about the clamp regardless of
|
||||
// which entry point triggered the model switch.
|
||||
// Switching Opus(auto) → Haiku clamps the mode to "default". The
|
||||
// `current_mode_update` side effect must fire so clients learn about the
|
||||
// clamp even though the request/response API returns the new
|
||||
// configOptions rather than emitting a config_option_update.
|
||||
setupHaikuOpusSession("auto");
|
||||
|
||||
const response = await agent.setSessionConfigOption({
|
||||
|
||||
Reference in New Issue
Block a user