Compare commits

...

9 Commits

Author SHA1 Message Date
acp-release-bot[bot]
3500ef736f chore(main): release 0.58.1 (#856)
🤖 I have created a release *beep* *boop*
---


##
[0.58.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.58.0...v0.58.1)
(2026-07-09)


### Bug Fixes

* Use valid npm version for publish
([#855](https://github.com/agentclientprotocol/claude-agent-acp/issues/855))
([8b366d8](8b366d8e9a))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-07-09 14:19:30 +02:00
Ben Brandt
8b366d8e9a fix: Use valid npm version for publish (#855) 2026-07-09 11:57:22 +00:00
acp-release-bot[bot]
5144aabc52 chore(main): release 0.58.0 (#847)
🤖 I have created a release *beep* *boop*
---


##
[0.58.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.57.0...v0.58.0)
(2026-07-09)


### Features

* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.205
([#854](https://github.com/agentclientprotocol/claude-agent-acp/issues/854))
([f664ced](f664ced76d))


### Bug Fixes

* Preserve live model on resumed sessions
([#848](https://github.com/agentclientprotocol/claude-agent-acp/issues/848))
([f3d8ae3](f3d8ae3eb3)),
closes
[#845](https://github.com/agentclientprotocol/claude-agent-acp/issues/845)
* Report usage for cancelled active turns
([#846](https://github.com/agentclientprotocol/claude-agent-acp/issues/846))
([b03318f](b03318f59c)),
closes
[#844](https://github.com/agentclientprotocol/claude-agent-acp/issues/844)
* tolerate missing text in streamed thinking chunks
([#852](https://github.com/agentclientprotocol/claude-agent-acp/issues/852))
([e944ced](e944ceddea))
* Use SDK guards for elicitation validation
([#850](https://github.com/agentclientprotocol/claude-agent-acp/issues/850))
([32b9350](32b93501d7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: acp-release-bot[bot] <246668977+acp-release-bot[bot]@users.noreply.github.com>
2026-07-09 13:40:22 +02:00
dependabot[bot]
f664ced76d feat(deps): update to @anthropic-ai/claude-agent-sdk 0.3.205 (#854)
Bumps the minor group with 12 updates:

| Package | From | To |
| --- | --- | --- |
|
[@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript)
| `0.3.202` | `0.3.204` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `26.1.0` | `26.1.1` |
| @anthropic-ai/claude-agent-sdk-darwin-arm64 | `0.3.202` | `0.3.204` |
| @anthropic-ai/claude-agent-sdk-darwin-x64 | `0.3.202` | `0.3.204` |
| @anthropic-ai/claude-agent-sdk-linux-arm64 | `0.3.202` | `0.3.204` |
| @anthropic-ai/claude-agent-sdk-linux-arm64-musl | `0.3.202` |
`0.3.204` |
| @anthropic-ai/claude-agent-sdk-linux-x64 | `0.3.202` | `0.3.204` |
| @anthropic-ai/claude-agent-sdk-linux-x64-musl | `0.3.202` | `0.3.204`
|
| @anthropic-ai/claude-agent-sdk-win32-arm64 | `0.3.202` | `0.3.204` |
| @anthropic-ai/claude-agent-sdk-win32-x64 | `0.3.202` | `0.3.204` |
|
[rolldown](https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown)
| `1.1.4` | `1.1.5` |
| [std-env](https://github.com/unjs/std-env) | `4.1.0` | `4.2.0` |

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.202 to 0.3.204
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/claude-agent-sdk-typescript/releases">@​anthropic-ai/claude-agent-sdk's
releases</a>.</em></p>
<blockquote>
<h2>v0.3.204</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.204</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.204
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.204
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.204
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.204
</code></pre>
<h2>v0.3.203</h2>
<h2>What's changed</h2>
<ul>
<li>Added a <code>background_tasks_changed</code> system message with
the full set of live background tasks on every membership change, so
consumers can track background activity as a level instead of pairing
<code>task_started</code>/<code>task_notification</code> edges</li>
<li>Fixed stable releases shipping a <code>sdk.d.ts</code> with
unresolved type references that broke consumer typechecking with
<code>skipLibCheck</code> disabled</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.203
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.203
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.203
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.203
</code></pre>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md">@​anthropic-ai/claude-agent-sdk's
changelog</a>.</em></p>
<blockquote>
<h2>0.3.204</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.204</li>
</ul>
<h2>0.3.203</h2>
<ul>
<li>Added a <code>background_tasks_changed</code> system message with
the full set of live background tasks on every membership change, so
consumers can track background activity as a level instead of pairing
<code>task_started</code>/<code>task_notification</code> edges</li>
<li>Fixed stable releases shipping a <code>sdk.d.ts</code> with
unresolved type references that broke consumer typechecking with
<code>skipLibCheck</code> disabled</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7feb85d829"><code>7feb85d</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="eb18b2a6de"><code>eb18b2a</code></a>
chore: Update CHANGELOG.md</li>
<li>See full diff in <a
href="https://github.com/anthropics/claude-agent-sdk-typescript/compare/v0.3.202...v0.3.204">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 26.1.0 to 26.1.1
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.3.202
to 0.3.204

Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.3.202 to
0.3.204

Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.3.202 to
0.3.204

Updates `rolldown` from 1.1.4 to 1.1.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">rolldown's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.5</h2>
<h2>[1.1.5] - 2026-07-08</h2>
<h3>🚀 Features</h3>
<ul>
<li>detect top-level import-binding reads as execution-order sensitive
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10180">#10180</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>sourcemap_filenames: add a sourcemapFileNames option (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9271">#9271</a>)
by <a
href="https://github.com/V1OL3TF0X"><code>@​V1OL3TF0X</code></a></li>
<li>binding: record plugin hook result kind in tracing spans (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10154">#10154</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: skip side-effect-free modules in per-entry reachability (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10111">#10111</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>improve error message for unresolved virtual imports (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10156">#10156</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>add descriptive metadata to plugin API (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10106">#10106</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>add <code>--configLoader=native</code> option (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10118">#10118</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>improve invalid annotation warnings (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10185">#10185</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>keep deduplicated asset filenames stable once they can be observed
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10191">#10191</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>sourcemap_filenames: use public option name in pattern errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10188">#10188</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>sourcemap_filenames: hash prepared sourcemap content (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10178">#10178</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>tree-shake unused circular declarators exported via export list (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10166">#10166</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: don't panic when an HMR rebuild hits an unresolved import (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10162">#10162</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>propagate errors from output.globals function (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9880">#9880</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev: revert cache mutations when a partial scan fails (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10110">#10110</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev: update importer relationships of cached modules in incremental
build (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10107">#10107</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>hmr: fall back to full reload when a changed module is not
registered as executed (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10132">#10132</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>chunk-optimizer: follow entry facade edges in runtime placement
cycle check (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10101">#10101</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev: ignore watcher events after close (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10113">#10113</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>emit async wrapper for TLA modules under onDemandWrapping (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10086">#10086</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>gate sideEffects:false modules' side effects on body demand (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10080">#10080</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>rolldown_plugin_vite_resolve: return empty object for <code>browser:
false</code> mapped modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10082">#10082</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>reset the word-boundary state on newline in Hires::Boundary
sourcemaps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10025">#10025</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>trim an emptied chunk's outro/intro instead of skipping past it (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10029">#10029</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>test each edited chunk's own start against indent exclude ranges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10026">#10026</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>preserve sourcemap mappings for indented lines when a CJS module
shares the chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10074">#10074</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>separate tree-shaking side effects from execution order sensitivity
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10168">#10168</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>type construct_vite_preload_call to take an ObjectPattern (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10135">#10135</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>treeshake: single-source the own-export classification shared with
the lazy-barrel loader (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10098">#10098</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: reuse Vite's bundledDev server (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10081">#10081</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>clippy: ban std HashMap/HashSet in favour of FxHashMap/FxHashSet (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10108">#10108</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>treeshake: make body demand a second module bit instead of a stmt
multimap (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10097">#10097</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>seal used_symbol_refs by construction after its last writer (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10091">#10091</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>treeshake: replace inclusion mutual recursion with a worklist engine
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10096">#10096</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: split include_statements.rs into focused modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10095">#10095</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>drop redundant is_user_defined filter on partitioned entries (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10050">#10050</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>project the retained export interface out of used_symbol_refs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10089">#10089</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>track used external symbols separately from used_symbol_refs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10088">#10088</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>make module namespace inclusion an explicit linking metadata field
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10087">#10087</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/blob/main/CHANGELOG.md">rolldown's
changelog</a>.</em></p>
<blockquote>
<h2>[1.1.5] - 2026-07-08</h2>
<h3>🚀 Features</h3>
<ul>
<li>detect top-level import-binding reads as execution-order sensitive
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10180">#10180</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>sourcemap_filenames: add a sourcemapFileNames option (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9271">#9271</a>)
by <a
href="https://github.com/V1OL3TF0X"><code>@​V1OL3TF0X</code></a></li>
<li>binding: record plugin hook result kind in tracing spans (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10154">#10154</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: skip side-effect-free modules in per-entry reachability (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10111">#10111</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>improve error message for unresolved virtual imports (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10156">#10156</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>add descriptive metadata to plugin API (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10106">#10106</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>add <code>--configLoader=native</code> option (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10118">#10118</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>improve invalid annotation warnings (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10185">#10185</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>keep deduplicated asset filenames stable once they can be observed
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10191">#10191</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>sourcemap_filenames: use public option name in pattern errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10188">#10188</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>sourcemap_filenames: hash prepared sourcemap content (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10178">#10178</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>tree-shake unused circular declarators exported via export list (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10166">#10166</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: don't panic when an HMR rebuild hits an unresolved import (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10162">#10162</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>propagate errors from output.globals function (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9880">#9880</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev: revert cache mutations when a partial scan fails (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10110">#10110</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev: update importer relationships of cached modules in incremental
build (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10107">#10107</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>hmr: fall back to full reload when a changed module is not
registered as executed (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10132">#10132</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>chunk-optimizer: follow entry facade edges in runtime placement
cycle check (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10101">#10101</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev: ignore watcher events after close (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10113">#10113</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>emit async wrapper for TLA modules under onDemandWrapping (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10086">#10086</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>gate sideEffects:false modules' side effects on body demand (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10080">#10080</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>rolldown_plugin_vite_resolve: return empty object for <code>browser:
false</code> mapped modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10082">#10082</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>reset the word-boundary state on newline in Hires::Boundary
sourcemaps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10025">#10025</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>trim an emptied chunk's outro/intro instead of skipping past it (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10029">#10029</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>test each edited chunk's own start against indent exclude ranges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10026">#10026</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>preserve sourcemap mappings for indented lines when a CJS module
shares the chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10074">#10074</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>separate tree-shaking side effects from execution order sensitivity
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10168">#10168</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>type construct_vite_preload_call to take an ObjectPattern (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10135">#10135</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>treeshake: single-source the own-export classification shared with
the lazy-barrel loader (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10098">#10098</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: reuse Vite's bundledDev server (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10081">#10081</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>clippy: ban std HashMap/HashSet in favour of FxHashMap/FxHashSet (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10108">#10108</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>treeshake: make body demand a second module bit instead of a stmt
multimap (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10097">#10097</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>seal used_symbol_refs by construction after its last writer (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10091">#10091</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>treeshake: replace inclusion mutual recursion with a worklist engine
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10096">#10096</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: split include_statements.rs into focused modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10095">#10095</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>drop redundant is_user_defined filter on partitioned entries (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10050">#10050</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>project the retained export interface out of used_symbol_refs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10089">#10089</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>track used external symbols separately from used_symbol_refs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10088">#10088</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>make module namespace inclusion an explicit linking metadata field
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10087">#10087</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>rename statement evaluation metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10078">#10078</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f09947ab01"><code>f09947a</code></a>
release: v1.1.5 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10194">#10194</a>)</li>
<li><a
href="bb66570a26"><code>bb66570</code></a>
fix: improve invalid annotation warnings (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10185">#10185</a>)</li>
<li><a
href="fb1d3b40d6"><code>fb1d3b4</code></a>
fix(sourcemap_filenames): use public option name in pattern errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10188">#10188</a>)</li>
<li><a
href="cccee78311"><code>cccee78</code></a>
test: move string_wizard replace unit tests to the JS magic-string suite
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10">#10</a>...</li>
<li><a
href="4b018b35c8"><code>4b018b3</code></a>
fix(sourcemap_filenames): hash prepared sourcemap content (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10178">#10178</a>)</li>
<li><a
href="72ce2b99c8"><code>72ce2b9</code></a>
feat(sourcemap_filenames): add a sourcemapFileNames option (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9271">#9271</a>)</li>
<li><a
href="7fe2c4d611"><code>7fe2c4d</code></a>
chore(deps): update oxc to 0.139.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10161">#10161</a>)</li>
<li><a
href="f730cac127"><code>f730cac</code></a>
fix: propagate errors from output.globals function (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9880">#9880</a>)</li>
<li><a
href="5b4a4080ff"><code>5b4a408</code></a>
chore(rolldown_plugin_utils): remove dead asset-url and css scaffolding
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10131">#10131</a>)</li>
<li><a
href="460900aeda"><code>460900a</code></a>
docs(cli): clarify disabling boolean/object flags like codeSplitting (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/10153">#10153</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/rolldown/rolldown/commits/v1.1.5/packages/rolldown">compare
view</a></li>
</ul>
</details>
<br />

Updates `std-env` from 4.1.0 to 4.2.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/unjs/std-env/releases">std-env's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<h3>🚀 Enhancements</h3>
<ul>
<li>Add Junie agent support (<a
href="https://redirect.github.com/unjs/std-env/pull/195">#195</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>agents:</strong> Don't detect kiro IDE integrated terminal
as an agent (<a
href="https://redirect.github.com/unjs/std-env/pull/196">#196</a>)</li>
</ul>
<h3>❤️Contributors</h3>
<ul>
<li>Pooya Parsa (<a
href="https://github.com/pi0"><code>@​pi0</code></a>)</li>
<li>Gauthier POGAM (<a
href="https://github.com/GauthierPLM"><code>@​GauthierPLM</code></a>])</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/unjs/std-env/blob/main/CHANGELOG.md">std-env's
changelog</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<p><a
href="https://github.com/unjs/std-env/compare/v4.1.0...v4.2.0">compare
changes</a></p>
<h3>🚀 Enhancements</h3>
<ul>
<li>Add Junie agent support (<a
href="https://redirect.github.com/unjs/std-env/pull/195">#195</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>agents:</strong> Don't detect kiro IDE integrated terminal
as an agent (<a
href="https://redirect.github.com/unjs/std-env/pull/196">#196</a>)</li>
</ul>
<h3>🏡 Chore</h3>
<ul>
<li>Update deps (<a
href="https://github.com/unjs/std-env/commit/aaeddcd">aaeddcd</a>)</li>
<li>Update tsconfig (<a
href="https://github.com/unjs/std-env/commit/9e44e17">9e44e17</a>)</li>
<li>Update tsconfig (<a
href="https://github.com/unjs/std-env/commit/1b2503d">1b2503d</a>)</li>
</ul>
<h3>❤️ Contributors</h3>
<ul>
<li>Pi0x <a href="mailto:x@pi0.io">x@pi0.io</a></li>
<li>Pooya Parsa (<a
href="https://github.com/pi0"><code>@​pi0</code></a>)</li>
<li>Gauthier POGAM--LE MONTAGNER (<a
href="https://github.com/GauthierPLM"><code>@​GauthierPLM</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ddd5e9e076"><code>ddd5e9e</code></a>
chore(release): v4.2.0</li>
<li><a
href="09d5e7805f"><code>09d5e78</code></a>
fix(agents): don't detect kiro IDE integrated terminal as an agent (<a
href="https://redirect.github.com/unjs/std-env/issues/196">#196</a>)</li>
<li><a
href="c5b59ac814"><code>c5b59ac</code></a>
chore(deps): update github-actions to v7 (<a
href="https://redirect.github.com/unjs/std-env/issues/187">#187</a>)</li>
<li><a
href="1b2503d788"><code>1b2503d</code></a>
chore: update tsconfig</li>
<li><a
href="9e44e17f8e"><code>9e44e17</code></a>
chore: update tsconfig</li>
<li><a
href="aaeddcdb8f"><code>aaeddcd</code></a>
chore: update deps</li>
<li><a
href="56aade8cc2"><code>56aade8</code></a>
feat: add Junie agent support (<a
href="https://redirect.github.com/unjs/std-env/issues/195">#195</a>)</li>
<li>See full diff in <a
href="https://github.com/unjs/std-env/compare/v4.1.0...v4.2.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-07-09 13:07:01 +02:00
Jazzen Chen
e944ceddea fix: tolerate missing text in streamed thinking chunks (#852)
## Summary

This makes streamed text/thinking chunk handling defensive when gateways
emit empty or malformed delta payloads.

- Guard `text_delta.text` and `thinking_delta.thinking` before reading
`.length`
- Skip missing/non-string text or thinking content the same way empty
chunks are already skipped
- Add regression coverage for a missing `thinking` delta before a real
text delta
- Add direct `toAcpNotifications` coverage for missing/non-string text
and thinking chunks

## Why

The adapter already skips empty thinking chunks, but some
Anthropic-compatible gateways can emit signature-only or omitted
thinking deltas where `thinking` is absent rather than an empty string.
In that case the adapter currently throws:

```text
Cannot read properties of undefined (reading 'length')
```

That aborts the ACP session even though the following text chunks are
valid.

This change keeps the existing behavior for valid chunks, while treating
missing/non-string chunk text as empty content.

## Test Plan

- `npm run check`
- `npm test -- --run`
- `npm run build`

Also verified manually through a VibeAround Web Chat flow using Claude
Code ACP with an Anthropic-compatible custom endpoint; the session
completed and returned a normal assistant response.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-07-08 11:23:13 +00:00
Ben Brandt
32b93501d7 fix: Use SDK guards for elicitation validation (#850) 2026-07-07 15:52:37 +00:00
Ben Brandt
f08c4521ec Fix hinted model alias resolution (#849) 2026-07-07 15:44:54 +00:00
Ben Brandt
f3d8ae3eb3 fix: Preserve live model on resumed sessions (#848)
Closes #845
2026-07-07 17:37:21 +02:00
Ben Brandt
b03318f59c fix: Report usage for cancelled active turns (#846)
Closes #844
2026-07-07 12:05:19 +00:00
12 changed files with 984 additions and 271 deletions

View File

@@ -50,8 +50,6 @@ jobs:
with:
node-version: "lts/*"
registry-url: "https://registry.npmjs.org"
- name: Update npm
run: npm install -g npm@latest
- run: npm ci
- run: npm run build
- run: npm publish

View File

@@ -1,3 +1,3 @@
{
".": "0.57.0"
".": "0.58.1"
}

View File

@@ -1,5 +1,27 @@
# Changelog
## [0.58.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.58.0...v0.58.1) (2026-07-09)
### Bug Fixes
* Use valid npm version for publish ([#855](https://github.com/agentclientprotocol/claude-agent-acp/issues/855)) ([8b366d8](https://github.com/agentclientprotocol/claude-agent-acp/commit/8b366d8e9ad55e9237bf2d618cfc99b57fac570d))
## [0.58.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.57.0...v0.58.0) (2026-07-09)
### Features
* **deps:** update to @anthropic-ai/claude-agent-sdk 0.3.205 ([#854](https://github.com/agentclientprotocol/claude-agent-acp/issues/854)) ([f664ced](https://github.com/agentclientprotocol/claude-agent-acp/commit/f664ced76df00674d88aedd018a4261da3bc3f3a))
### Bug Fixes
* Preserve live model on resumed sessions ([#848](https://github.com/agentclientprotocol/claude-agent-acp/issues/848)) ([f3d8ae3](https://github.com/agentclientprotocol/claude-agent-acp/commit/f3d8ae3eb389ee9367f48c8762562a55280ade0a)), closes [#845](https://github.com/agentclientprotocol/claude-agent-acp/issues/845)
* Report usage for cancelled active turns ([#846](https://github.com/agentclientprotocol/claude-agent-acp/issues/846)) ([b03318f](https://github.com/agentclientprotocol/claude-agent-acp/commit/b03318f59c6165146c8d8e036227f35c3abd0ff4)), closes [#844](https://github.com/agentclientprotocol/claude-agent-acp/issues/844)
* tolerate missing text in streamed thinking chunks ([#852](https://github.com/agentclientprotocol/claude-agent-acp/issues/852)) ([e944ced](https://github.com/agentclientprotocol/claude-agent-acp/commit/e944ceddea79950d6bfb6339266862507c4b964f))
* Use SDK guards for elicitation validation ([#850](https://github.com/agentclientprotocol/claude-agent-acp/issues/850)) ([32b9350](https://github.com/agentclientprotocol/claude-agent-acp/commit/32b93501d7a35a3b405b5022b4caaad492eed11c))
## [0.57.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.56.0...v0.57.0) (2026-07-07)

242
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.57.0",
"version": "0.58.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@agentclientprotocol/claude-agent-acp",
"version": "0.57.0",
"version": "0.58.1",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "1.2.0",
"@anthropic-ai/claude-agent-sdk": "0.3.202",
"@agentclientprotocol/sdk": "1.2.1",
"@anthropic-ai/claude-agent-sdk": "0.3.205",
"zod": "^3.25.0 || ^4.0.0"
},
"bin": {
@@ -20,7 +20,7 @@
"@anthropic-ai/sdk": "0.110.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.5",
"@types/node": "26.1.0",
"@types/node": "26.1.1",
"@typescript-eslint/eslint-plugin": "8.63.0",
"@typescript-eslint/parser": "8.63.0",
"eslint": "10.6.0",
@@ -36,31 +36,31 @@
}
},
"node_modules/@agentclientprotocol/sdk": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.0.tgz",
"integrity": "sha512-rJ3766M2eouZsNCaK21JbPD++JLBZ0PWq38dzj1sA4/i0njaQEkiC2F5v7MlizFW6akX+qu5Ks8R00Xw+YMHKA==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.1.tgz",
"integrity": "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA==",
"license": "Apache-2.0",
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.202.tgz",
"integrity": "sha512-LnaLxDtsZP7J6g++xRSnnpTX7CHNe4v+cvBRIlD2ar+N+xi0aqY2YDaCsxPsl+haVUB9kqlUMd0zosmwsfTGjQ==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.205.tgz",
"integrity": "sha512-ft6iBw9kXudsusiXNpeybIPBJ07Z3tqp1ROSg5cEJqgA+9i+JJj2sRfQth+QD+lyenbbAU8yPieLxIimvfBhtw==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.202",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.202",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.202",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.202",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.202",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.202",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.202",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.202"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.205",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.205",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.205",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.205",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.205",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.205",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.205",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.205"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
@@ -69,9 +69,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.202.tgz",
"integrity": "sha512-ujR3zDthDPkZs+AxW95iHpqLT5cuwGImsS3mVxLt1DlDij4qeTnihLX8+EpQTK+oNW9jjvFA86yKwa84fa1KYA==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.205.tgz",
"integrity": "sha512-lrfJ4eVtzfPkCpbSkBOGSMQCBbvmW6nbPzgHE4IwMN3scZlpuFMUFqh2aaJa/X2SAcWD9H2S0t2WWvSRgM7BjA==",
"cpu": [
"arm64"
],
@@ -82,9 +82,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.202.tgz",
"integrity": "sha512-s/RVSGgkVmIMfyt1ndR8braLLu82bARoijmt1kk8d4IptUZ0Sc+zNUWKoFXwR9XqDBu6rBbBF9RIzD02raT57w==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.205.tgz",
"integrity": "sha512-G6ETPmL5mNzJ2DFsWxG3jmsmrXgZX1N2ZCJvxaGUUpjTsKZJ4Tup1cWYvcd/m7o5fYZmx9REmgzTwsAIc1fdPQ==",
"cpu": [
"x64"
],
@@ -95,9 +95,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.202.tgz",
"integrity": "sha512-a4YtRkgGYt3ogePJDW8Ts6bNW690jb9LHyZaiWXsi+zT53xCNqJB2zKPyRc7hXWOqzIk4nCfwJpjmhLzMu3WIg==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.205.tgz",
"integrity": "sha512-CXzySK3PV3EizCRPXnxPqeaAtgrBFDnMFOVpMe36oC3U16yDb1b1tAJGqZi/7uFrVvAiaXvnSFxhUWnDDSaO+A==",
"cpu": [
"arm64"
],
@@ -111,9 +111,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.202.tgz",
"integrity": "sha512-abSb3Gah45kUNyOeKjmQ/dd1KZ4CaQz5JAr9YQxRDXoOwx8wJVx6huBIpDxjms9wyS9X5Rqxn0Lx7zFP+wV2zQ==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.205.tgz",
"integrity": "sha512-91fgdG4aTnQ29sKOcUqgH4+tKCW2ut6PWGRSYmXNDbROasJm1rAlPdzC5brdu/e4c0CDSNV6TWyE5JCjaS/jlQ==",
"cpu": [
"arm64"
],
@@ -127,9 +127,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.202.tgz",
"integrity": "sha512-XIvhdCWAAT4OdOA82fOJII+WH0Tf8pFckckEbJMMmOgQBKOnHT+609Pd3Ehw6zGcA9iFrhG5mY8Ncuckeo1aMw==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.205.tgz",
"integrity": "sha512-siS+1iNqBSlGFZZvJY6+mhzZ/6/ec/TbX9GMuwmTF0E6fxGhIIp797jJxR1q8r6FAq7d39mEoRNhC0Ffo60uNQ==",
"cpu": [
"x64"
],
@@ -143,9 +143,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.202.tgz",
"integrity": "sha512-fze5nAQL1ErcMCQNB10ILaWdM0QbJSaTQzBz8NVAy0FGW8ZL0t4Wf/VgFkfzXbfkaxmPuM1C27Dn5HiU7UDEHQ==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.205.tgz",
"integrity": "sha512-vvsb7GlnA8CTSVvvTkrXjcSeRKqxSM7p/tU3Od9ICAZeWHglptekEyzLEApzLuLbI5ewfFF/F0q3NwOBbo18dg==",
"cpu": [
"x64"
],
@@ -159,9 +159,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.202.tgz",
"integrity": "sha512-N1J0HRvC+8a69bqNY7+ENIYQzR0i7s+rOIGH5XtuLxvLqOnZO8LHxWEZOe8ezabGq5eZqphSCgL6vQnQQpNh+A==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.205.tgz",
"integrity": "sha512-SpP5zF68weFez/6pKrGzq/UVAJDMDNphWqmkLfOpWTDBL5xy6XlIZw5Bl4EXoVnfi2VLFkwuffNeFe+9SdX7kw==",
"cpu": [
"arm64"
],
@@ -172,9 +172,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.3.202",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.202.tgz",
"integrity": "sha512-ytLGEC1fjTSiVSoXukS+j9G+06Mi20NSzxxzlG6uE75SEB0+17tHdWUaHqd8PhH/6GPzcYx81czxWQl1MVbq4Q==",
"version": "0.3.205",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.205.tgz",
"integrity": "sha512-kg2kkXyeSoFLruO3Ic2IruLxzBR0xCUtmlJHdWi3SYW7JhAKNJg4fcrdJsWcardmEw23Y2UDGDJbRyxqSVx6wg==",
"cpu": [
"x64"
],
@@ -544,9 +544,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.138.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
"version": "0.139.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -554,9 +554,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
"cpu": [
"arm64"
],
@@ -571,9 +571,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
"cpu": [
"arm64"
],
@@ -588,9 +588,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
"cpu": [
"x64"
],
@@ -605,9 +605,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
"cpu": [
"x64"
],
@@ -622,9 +622,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
"cpu": [
"arm"
],
@@ -639,9 +639,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
"cpu": [
"arm64"
],
@@ -659,9 +659,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
"cpu": [
"arm64"
],
@@ -679,9 +679,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
"cpu": [
"ppc64"
],
@@ -699,9 +699,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
"cpu": [
"s390x"
],
@@ -719,9 +719,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
"cpu": [
"x64"
],
@@ -739,9 +739,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
"cpu": [
"x64"
],
@@ -759,9 +759,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
"cpu": [
"arm64"
],
@@ -776,9 +776,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
"cpu": [
"wasm32"
],
@@ -795,9 +795,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
"cpu": [
"arm64"
],
@@ -812,9 +812,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
"cpu": [
"x64"
],
@@ -934,9 +934,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz",
"integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==",
"version": "26.1.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3324,13 +3324,13 @@
}
},
"node_modules/rolldown": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.138.0",
"@oxc-project/types": "=0.139.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -3340,21 +3340,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.1.4",
"@rolldown/binding-darwin-arm64": "1.1.4",
"@rolldown/binding-darwin-x64": "1.1.4",
"@rolldown/binding-freebsd-x64": "1.1.4",
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
"@rolldown/binding-linux-arm64-musl": "1.1.4",
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
"@rolldown/binding-linux-x64-gnu": "1.1.4",
"@rolldown/binding-linux-x64-musl": "1.1.4",
"@rolldown/binding-openharmony-arm64": "1.1.4",
"@rolldown/binding-wasm32-wasi": "1.1.4",
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
"@rolldown/binding-win32-x64-msvc": "1.1.4"
"@rolldown/binding-android-arm64": "1.1.5",
"@rolldown/binding-darwin-arm64": "1.1.5",
"@rolldown/binding-darwin-x64": "1.1.5",
"@rolldown/binding-freebsd-x64": "1.1.5",
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
"@rolldown/binding-linux-arm64-musl": "1.1.5",
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
"@rolldown/binding-linux-x64-gnu": "1.1.5",
"@rolldown/binding-linux-x64-musl": "1.1.5",
"@rolldown/binding-openharmony-arm64": "1.1.5",
"@rolldown/binding-wasm32-wasi": "1.1.5",
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
"@rolldown/binding-win32-x64-msvc": "1.1.5"
}
},
"node_modules/router": {
@@ -3590,9 +3590,9 @@
}
},
"node_modules/std-env": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
"integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
"dev": true,
"license": "MIT"
},
@@ -3826,16 +3826,16 @@
}
},
"node_modules/vite": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
"version": "8.1.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
"integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"picomatch": "^4.0.5",
"postcss": "^8.5.16",
"rolldown": "~1.1.3",
"rolldown": "~1.1.4",
"tinyglobby": "^0.2.17"
},
"bin": {

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.57.0",
"version": "0.58.1",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -63,15 +63,15 @@
"node": ">=22"
},
"dependencies": {
"@agentclientprotocol/sdk": "1.2.0",
"@anthropic-ai/claude-agent-sdk": "0.3.202",
"@agentclientprotocol/sdk": "1.2.1",
"@anthropic-ai/claude-agent-sdk": "0.3.205",
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.110.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.5",
"@types/node": "26.1.0",
"@types/node": "26.1.1",
"@typescript-eslint/eslint-plugin": "8.63.0",
"@typescript-eslint/parser": "8.63.0",
"eslint": "10.6.0",

View File

@@ -229,8 +229,11 @@ type Session = {
* the SDK still runs it and emits a result with no uuid we can match. Because
* the SDK processes input FIFO, those orphan results arrive (in submission
* order) before the next live turn's, so skipping exactly this many leaves
* the genuine head untouched. Reset to 0 on every activation as a backstop
* against an SDK that drops queued input on interrupt (no orphan emitted). */
* the genuine head untouched. On CLIs with the interrupt receipt, orphans
* the interrupt dropped (absent from `still_queued`) are uncounted as soon
* as the receipt arrives (see cancel()). Reset to 0 on every activation as
* a backstop against a dropped queued input this can't see (older CLIs, a
* receipt lost to a failed control round-trip). */
pendingOrphanResults?: number;
/** The long-lived consumer task. Lazily started on the first `prompt()` and
* kept alive for the session so between-turn/background messages are still
@@ -1340,7 +1343,7 @@ export class ClaudeAcpAgent {
if (session.activeTurn && !session.activeTurn.settled) {
session.pendingOrphanResults = (session.pendingOrphanResults ?? 0) + 1;
}
settleActive({ stopReason: "cancelled" });
settleActive({ stopReason: "cancelled", usage: sessionUsage(session) });
// If the session is being torn down, abandon the in-flight next()
// (swallowing any later rejection so it can't surface as unhandled)
// and stop; otherwise re-arm and keep consuming — `pendingNext`
@@ -1368,11 +1371,10 @@ export class ClaudeAcpAgent {
//
// Settle the turn that was in flight so its prompt() doesn't hang:
// cancelled if a cancel is pending, otherwise the accumulated outcome.
settleActive(
session.cancelled
? { stopReason: "cancelled" }
: { stopReason, usage: sessionUsage(session) },
);
settleActive({
stopReason: session.cancelled ? "cancelled" : stopReason,
usage: sessionUsage(session),
});
// Queued turns the SDK never started never ran, so reject them rather
// than reporting a success (end_turn) — or a misleading "cancelled" —
// for a prompt that produced no output. (A cancel already settled the
@@ -1517,8 +1519,14 @@ export class ClaudeAcpAgent {
// the turn NOW so its session/prompt gets a terminal
// response, instead of leaving it hanging until the next
// prompt drains the wreckage.
// A cancelled turn still consumed tokens: its dropped result
// already fed the accumulator (the usage tally at the result
// handler runs before the `session.cancelled` guard), so
// report it — clients metering spend would otherwise lose
// the interrupted turn's tokens entirely (issue #844). Zero
// when the cancel pre-empted the result (wedge/force-cancel).
if (session.cancelled && session.activeTurn && !session.activeTurn.settled) {
settleActive({ stopReason: "cancelled" });
settleActive({ stopReason: "cancelled", usage: sessionUsage(session) });
} else if (owedTrailingIdles > 0) {
// Absorb a settled turn's trailing idle. Also covers a
// cancel that landed between a turn's counted result and
@@ -1772,6 +1780,15 @@ export class ClaudeAcpAgent {
lastRefusalExplanation = message.api_refusal_explanation ?? message.content;
}
break;
// `control_request_progress` only reports on side_question
// control requests, which this adapter never issues.
// `background_tasks_changed` is a level signal (the full live
// background-task set on every membership change) for surfaces
// that render a background-activity indicator; turn lifecycle
// here is driven by results/idle, so there is nothing to track.
case "control_request_progress":
case "background_tasks_changed":
break;
default:
unreachable(message, this.logger);
break;
@@ -2024,7 +2041,7 @@ export class ClaudeAcpAgent {
// create a block the consolidated handler's `text.length > 0`
// guard can never consume, stalling the diff cursor and
// re-emitting the next block as a duplicate.
if (chunk && chunk.text.length > 0) {
if (chunk?.text) {
const index = message.event.index;
const last = streamedBlocks[streamedBlocks.length - 1];
if (last && last.index === index && last.type === chunk.type) {
@@ -2142,7 +2159,9 @@ export class ClaudeAcpAgent {
// as the freshly-activated turn ending without a result
// (which would false-fail a healthy turn — issue #825).
owedTrailingIdles++;
settleActive({ stopReason: "cancelled" });
// Before activateTurn resets the accumulator, so the
// usage still belongs to the cancelled turn.
settleActive({ stopReason: "cancelled", usage: sessionUsage(session) });
} else {
settleActive({ stopReason: "end_turn", usage: sessionUsage(session) });
}
@@ -2381,13 +2400,10 @@ export class ClaudeAcpAgent {
// `conversation_reset` (from `/clear`, plan-mode exit, fresh-session
// flows) is safe to drop: turn lifecycle here is driven by
// results/idle, and the client owns its own transcript view.
// `control_request_progress` only reports on side_question control
// requests, which this adapter never issues.
case "tool_use_summary":
case "auth_status":
case "prompt_suggestion":
case "conversation_reset":
case "control_request_progress":
break;
default:
unreachable(message);
@@ -2449,20 +2465,22 @@ export class ClaudeAcpAgent {
// they have no in-flight SDK work to interrupt. The active turn is settled
// by the consumer when it observes the interrupt's trailing idle (or via the
// backstop below). Mirrors the old pendingMessages cancellation.
const orphanedUuids: string[] = [];
if (session.turnQueue) {
let orphaned = 0;
for (const turn of session.turnQueue) {
if (turn !== session.activeTurn && !turn.settled) {
turn.settled = true;
// Deliberately no `usage`: a queued turn never ran, so the session
// accumulator (the active turn's tally) is not its spend.
turn.resolve({ stopReason: "cancelled" });
orphaned++;
orphanedUuids.push(turn.promptUuid);
}
}
// Each removed queued turn's user message was already pushed to the SDK,
// which processes input FIFO and will still emit a result for it with no
// uuid to match. Count those so the consumer skips them (see
// ensureActiveTurn) rather than misattributing them to the head.
session.pendingOrphanResults = (session.pendingOrphanResults ?? 0) + orphaned;
session.pendingOrphanResults = (session.pendingOrphanResults ?? 0) + orphanedUuids.length;
session.turnQueue = session.turnQueue.filter(
(turn) => turn === session.activeTurn && !turn.settled,
);
@@ -2494,7 +2512,29 @@ export class ClaudeAcpAgent {
}, this.forceCancelGraceMs);
}
await session.query.interrupt();
const receipt = await session.query.interrupt();
// On CLIs advertising `interrupt_receipt_v1`, the receipt's `still_queued`
// lists exactly which queued messages survive the interrupt and will still
// run. An orphaned turn whose uuid is absent was dropped by the interrupt
// and will never emit a result — uncount it now instead of leaving a stale
// skip that activateTurn's reset only clears once a later live ECHO
// arrives: an echo-less result in between (a local-only command like
// `/context`) would be wrongly swallowed by the leftover count. Subtracting
// a count (rather than tracking uuids) stays race-safe against the
// consumer draining concurrently: dropped uuids produce no results, so the
// consumer's decrements only ever consume the still-queued share. Unknown
// uuids in the receipt (internally-enqueued messages) are ignored, per its
// contract. Older CLIs resolve `undefined` (guard the FIELD, not just the
// receipt, so a bare `{}` success from a gateway can't read as "everything
// was dropped") — keep the count-everything behavior and its
// activation-time self-heal.
if (Array.isArray(receipt?.still_queued) && orphanedUuids.length > 0) {
const stillQueued = new Set(receipt.still_queued);
const dropped = orphanedUuids.filter((uuid) => !stillQueued.has(uuid)).length;
if (dropped > 0) {
session.pendingOrphanResults = Math.max(0, (session.pendingOrphanResults ?? 0) - dropped);
}
}
}
/** Mark a session's SDK query stream as permanently ended and release the
@@ -2634,18 +2674,38 @@ export class ClaudeAcpAgent {
: [];
let validValue = allValues.find((o) => o.value === params.value);
// The option's reported currentValue is always a valid target, even when
// it has no options entry: a session running an out-of-picker model
// (resumed onto an allowlist-excluded model, or a refusal fallback)
// reports a currentValue that isn't selectable, and a client
// round-tripping it must not get "Invalid value". It flows through the
// normal apply path below — re-asserting an already-current value is
// harmless and can repair SDK drift.
if (!validValue && option.currentValue === params.value) {
validValue = { value: params.value, name: params.value };
}
// For model options, fall back to resolveModelPreference when the exact
// value doesn't match. This lets callers use human-friendly aliases like
// "opus" or "sonnet" instead of full model IDs like "claude-opus-4-6".
// Resolve against session.modelInfos first: those entries carry
// `resolvedModel`, so a full model id (in either hint spelling) lands on
// the right row via the exact tier instead of a fuzzier one picking a
// same-family sibling from a different context lane. The options-derived
// list (which never carries `resolvedModel`) remains as a fallback for
// resolutions that don't map back onto a selectable option (e.g. a fuzzy
// hit on an out-of-picker verbatim entry).
if (!validValue && params.configId === MODEL_CONFIG_ID) {
const modelInfos: ModelInfo[] = allValues.map((o) => ({
value: o.value,
displayName: o.name,
description: o.description ?? "",
}));
const resolved = resolveModelPreference(modelInfos, params.value);
if (resolved) {
validValue = allValues.find((o) => o.value === resolved.value);
const toOptionValue = (resolved: ModelInfo | null) =>
resolved ? allValues.find((o) => o.value === resolved.value) : undefined;
validValue = toOptionValue(resolveModelPreference(session.modelInfos, params.value));
if (!validValue) {
const optionInfos: ModelInfo[] = allValues.map((o) => ({
value: o.value,
displayName: o.name,
description: o.description ?? "",
}));
validValue = toOptionValue(resolveModelPreference(optionInfos, params.value));
}
}
@@ -3793,11 +3853,44 @@ export class ClaudeAcpAgent {
initializationResult.models,
settingsManager,
this.logger,
creationOpts.resume !== undefined,
);
// Gate `auto` (and future model-specific modes) on the resolved model's
// `ModelInfo`. See `buildAvailableModes` for the canonical SDK signal.
const currentModelInfo = allowedModels.find((m) => m.value === models.currentModelId);
// A resumed session can be running a model outside the `availableModels`
// allowlist (currentModelId is then the verbatim live id, see
// `matchResumedModel`); its capabilities are still known to the SDK's
// unfiltered list, so fall back to that before treating the model as
// unknown — otherwise auto mode would be spuriously clamped and the
// Fast-mode/Effort options hidden for a model that supports them.
const allowlistedModelInfo = allowedModels.find((m) => m.value === models.currentModelId);
const fallbackModelInfo = allowlistedModelInfo
? undefined
: (resolveModelPreference(initializationResult.models, models.currentModelId) ?? undefined);
const currentModelInfo = allowlistedModelInfo ?? fallbackModelInfo;
// Register the fallback-resolved capabilities under the verbatim live id
// so every modelInfos consumer (buildConfigOptions' effort lookup, later
// rebuilds via session.modelInfos) agrees with the gating below. The
// picker options themselves come from `models.availableModels`, so this
// adds no selectable entry. The spread keeps every capability flag
// (current and future); the identity fields are overridden because the
// fuzzy-matched sibling's resolvedModel/displayName/description can
// describe a different context lane and would poison later resolvedModel
// matching (syncModelAfterRefusalFallback) and context-window inference
// (applyConfigOptionValue) if they traveled under this id.
const modelInfos = fallbackModelInfo
? [
...allowedModels,
{
...fallbackModelInfo,
value: models.currentModelId,
displayName: models.currentModelId,
description: "",
resolvedModel: undefined,
},
]
: allowedModels;
const availableModes = buildAvailableModes(currentModelInfo);
// Clamp `permissionMode` if the resolved session does not offer it. The
@@ -3863,7 +3956,7 @@ export class ClaudeAcpAgent {
const configOptions = buildConfigOptions(
modes,
models,
allowedModels,
modelInfos,
settingsManager.getSettings().effortLevel,
agents,
currentAgent,
@@ -3896,7 +3989,7 @@ export class ClaudeAcpAgent {
},
modes,
models,
modelInfos: allowedModels,
modelInfos,
configOptions,
agents,
currentAgent,
@@ -3904,10 +3997,15 @@ export class ClaudeAcpAgent {
abortController,
emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
contextWindowSize:
// Deliberately keyed to the allowlisted entry: a fallback-resolved
// sibling's displayName/description can describe a different context
// lane than the verbatim live id (e.g. an "opus[1m]" row matched for
// a bare 200k id), so on the fallback path only the id itself is a
// trustworthy window signal.
inferContextWindowFromModel(
models.currentModelId,
currentModelInfo?.displayName,
currentModelInfo?.description,
allowlistedModelInfo?.displayName,
allowlistedModelInfo?.description,
) ?? DEFAULT_CONTEXT_WINDOW,
taskState,
toolUseCache: {},
@@ -4325,15 +4423,37 @@ export function buildConfigOptions(
// but the SDK model list uses IDs like "claude-opus-4-6-1m".
const MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
// The id-suffix spelling of a context hint ("-1m" in "claude-opus-4-6-1m");
// shared by the strip and canonicalize helpers below so the two can't drift.
const CONTEXT_HINT_SUFFIX_PATTERN = /-(\d+m)$/i;
/** Remove context-window hints — the display form "[1m]" and the SDK id
* suffix form "-1m" — from a model string. Those digits describe context
* size, not model identity or generation version. */
function stripContextHints(s: string): string {
return s.replace(/\[\d+m\]/gi, "").replace(CONTEXT_HINT_SUFFIX_PATTERN, "");
}
/** Canonicalize a model id for exact comparison: trimmed, lowercased, with
* the id-suffix hint spelling unified to the bracket form ("-1m" → "[1m]").
* The hint itself is kept — bare and 1M ids must stay distinct. */
function canonicalizeModelId(s: string): string {
return s.trim().toLowerCase().replace(CONTEXT_HINT_SUFFIX_PATTERN, "[$1]");
}
/** The context hint a model string carries ("1m" for either spelling), or
* null for a bare id. */
function contextHintOf(s: string): string | null {
return canonicalizeModelId(s).match(MODEL_CONTEXT_HINT_PATTERN)?.[1] ?? null;
}
// Captures a model family version: `4-6`/`4.7` for dated generations, or a
// bare `5` for single-number ones like "Sonnet 5". Used to keep a pinned
// `claude-opus-4-6` from matching the `opus` alias once it points at 4.7.
const MODEL_FAMILY_VERSION_PATTERN = /\b(\d+)(?:[-.](\d+))?\b/;
function extractModelFamilyVersion(s: string): string | null {
// Strip "[1m]"-style context hints first — that digit is context window
// size, not a model generation version.
const match = s.replace(/\[\d+m\]/gi, "").match(MODEL_FAMILY_VERSION_PATTERN);
const match = stripContextHints(s).match(MODEL_FAMILY_VERSION_PATTERN);
if (!match) return null;
return match[2] ? `${match[1]}.${match[2]}` : match[1];
}
@@ -4387,11 +4507,13 @@ export function resolveModelPreference(models: ModelInfo[], preference: string):
const lower = trimmed.toLowerCase();
// Exact match on value or display name
// Exact match on value or display name. Values compare on the canonical
// hint spelling so "opus-1m" hits an "opus[1m]" row (and vice versa).
const canonicalPreference = canonicalizeModelId(trimmed);
const directMatch = models.find(
(model) =>
model.value === trimmed ||
model.value.toLowerCase() === lower ||
canonicalizeModelId(model.value) === canonicalPreference ||
model.displayName.toLowerCase() === lower,
);
if (directMatch) return directMatch;
@@ -4399,18 +4521,27 @@ export function resolveModelPreference(models: ModelInfo[], preference: string):
// Exact match on the alias's canonical resolved id (e.g. a pinned
// "claude-sonnet-5" against the "sonnet" row's `resolvedModel`). SDK-
// reported and unambiguous, so it's tried before the fuzzier tiers below.
// "default" is skipped first since it shares a resolvedModel with
// whichever alias the CLI currently recommends — a specific pin should
// land on that named alias, not "default".
// Compared on the canonical hint spelling so a "-1m"-suffix pin matches a
// "[1m]"-spelled resolvedModel instead of falling into the substring tier
// (which would land on the bare 200k sibling). "default" is skipped first
// since it shares a resolvedModel with whichever alias the CLI currently
// recommends — a specific pin should land on that named alias, not
// "default".
const matchesResolved = (model: ModelInfo) =>
model.resolvedModel != null && canonicalizeModelId(model.resolvedModel) === canonicalPreference;
const resolvedMatch =
models.find(
(model) => model.value !== "default" && model.resolvedModel?.toLowerCase() === lower,
) ?? models.find((model) => model.resolvedModel?.toLowerCase() === lower);
models.find((model) => model.value !== "default" && matchesResolved(model)) ??
models.find(matchesResolved);
if (resolvedMatch) return resolvedMatch;
// Substring match
// Substring match. Skips candidates whose context hint disagrees with the
// preference's — a bare row must not absorb a 1M-hinted preference (nor
// vice versa); such pairs fall through to the tokenized tier, which
// weighs hints in its scoring and still finds the best same-family row.
const preferenceHint = contextHintOf(trimmed);
const includesMatch = models.find((model) => {
if (!modelVersionsCompatible(trimmed, model)) return false;
if (contextHintOf(model.value) !== preferenceHint) return false;
const value = model.value.toLowerCase();
const display = model.displayName.toLowerCase();
return value.includes(lower) || display.includes(lower) || lower.includes(value);
@@ -4435,6 +4566,61 @@ export function resolveModelPreference(models: ModelInfo[], preference: string):
return bestMatch;
}
/** Map the live model reported by a resumed session onto the picker's model
* list. The CLI restores a resumed session's model from the transcript's
* last assistant message, which records the concrete API id (e.g.
* "claude-opus-4-6") with any "[1m]" context hint dropped. Tiers, in order:
* 1. Exact match with the Default entry's resolution — when a named alias
* shares Default's resolvedModel verbatim, the live id can't tell the
* two apart, and a never-customized session should stay on Default.
* 2. Exact resolvedModel match on a named row. Checked before the
* hint-stripped Default comparison so a live "claude-sonnet-5[1m]" lands
* on the "sonnet[1m]" row rather than a Default that resolves to the
* bare "claude-sonnet-5" — the two rows differ in context window, which
* drives `contextWindowSize` and capability gating downstream.
* 3. Hint-stripped match with Default's resolution — a session that never
* left the default resumes as the bare transcript id, and shouldn't show
* a concrete picker entry.
* 4. `resolveModelPreference` over the picker entries.
* 5. A model with no picker counterpart (e.g. excluded by an
* `availableModels` allowlist) is tracked verbatim, mirroring
* `syncModelAfterRefusalFallback`: the picker shows no selection, but the
* model-dependent bookkeeping stays truthful to what the SDK is running. */
export function matchResumedModel(models: ModelInfo[], liveModel: string): ModelInfo {
const live = canonicalizeModelId(liveModel);
const defaultEntry = models.find((m) => m.value === "default");
const defaultResolved = defaultEntry?.resolvedModel
? canonicalizeModelId(defaultEntry.resolvedModel)
: undefined;
if (defaultEntry && defaultResolved === live) {
return defaultEntry;
}
// No default-row exclusion needed: a default row matching `live` exactly
// already returned at the tier above.
const exactMatch = models.find(
(m) => m.resolvedModel && canonicalizeModelId(m.resolvedModel) === live,
);
if (exactMatch) return exactMatch;
if (
defaultEntry &&
defaultResolved &&
stripContextHints(defaultResolved) === stripContextHints(live)
) {
return defaultEntry;
}
return (
resolveModelPreference(models, liveModel) ?? {
value: liveModel,
displayName: liveModel,
description: "",
}
);
}
function resolveSettingsModel(
models: ModelInfo[],
settingsModel: unknown,
@@ -4527,12 +4713,32 @@ export function applyAvailableModelsAllowlist(
return result;
}
/** Read the model a resumed session is actually running (via the
* `getContextUsage` control request — the same source `/context` prints) and
* map it onto the picker. Best-effort: a control-request failure is logged
* and returns null so callers keep their current choice; failing the whole
* session/load over an unreadable report would be worse. */
async function readResumedLiveModel(
query: Query,
models: ModelInfo[],
logger: Logger,
): Promise<ModelInfo | null> {
try {
const liveModel = (await query.getContextUsage()).model;
return liveModel ? matchResumedModel(models, liveModel) : null;
} catch (error) {
logger.error("Failed to read the resumed session's live model:", error);
return null;
}
}
async function getAvailableModels(
query: Query,
models: ModelInfo[],
sdkModels: ModelInfo[],
settingsManager: SettingsManager,
logger: Logger,
isResumedSession: boolean,
): Promise<SessionModelState> {
const settings = settingsManager.getSettings();
@@ -4542,7 +4748,8 @@ async function getAvailableModels(
// Model priority (highest to lowest):
// 1. ANTHROPIC_MODEL environment variable
// 2. settings.model (user configuration)
// 3. models[0] (default first model)
// 3. the resumed session's live model (resumed sessions only)
// 4. models[0] (default first model)
if (process.env.ANTHROPIC_MODEL) {
const match = resolveModelPreference(models, process.env.ANTHROPIC_MODEL);
if (match) {
@@ -4557,22 +4764,49 @@ async function getAvailableModels(
}
}
// A resumed session restores the model it was previously running (the CLI
// re-reads it from the transcript), so without an env/settings override the
// freshly-computed default above can disagree with what the session actually
// runs — session/load then reports a model the session isn't using (issue
// #845). Ask the CLI for the live model and reflect it. No `setModel` here:
// the SDK is already running this model, and pushing a picker alias back
// (e.g. "opus[1m]") could change the live model rather than describe it.
if (resolvedFromInput === undefined && isResumedSession) {
currentModel = (await readResumedLiveModel(query, models, logger)) ?? currentModel;
}
// Skip the setModel round-trip when we can prove the SDK has already landed
// on the same model. Two cases qualify:
// (a) No override applied — currentModel stayed at models[0]; the SDK is on
// its own default and we have nothing to sync.
// (a) No override applied — currentModel is the SDK's own default (or, on
// resume, the live model read back from the SDK above); nothing to sync.
// (b) The resolver returned the user's input verbatim AND that value exists
// in the SDK's original model list — meaning no fuzzy match or
// allowlist rewrite was involved, and the SDK (which reads the same
// ANTHROPIC_MODEL / settings.json) will have arrived at the same entry.
// This only holds for fresh sessions: a resumed session lands on the
// transcript's model regardless of env/settings, so the override must
// be re-asserted to keep the reported model truthful.
// Anything else (fuzzy match, allowlist-synthesized value, alias) gets a
// setModel call so we don't drift from the user's intended pin.
const sdkSawSameValue = sdkModels.some((m) => m.value === currentModel.value);
const skipSetModel =
resolvedFromInput === undefined ||
(currentModel.value === resolvedFromInput && sdkSawSameValue);
(!isResumedSession && currentModel.value === resolvedFromInput && sdkSawSameValue);
if (!skipSetModel) {
await query.setModel(currentModel.value);
try {
await query.setModel(currentModel.value);
} catch (error) {
// On a fresh session the pin is a defining option — fail loudly. A
// resumed session already runs fine on the transcript's model, so
// failing the whole session/load over the re-assert would be worse
// than loading with the pin unapplied (mirrors the setPermissionMode
// containment in createSession). The SDK then stayed on the
// transcript's model, so read that back rather than reporting the
// pin the session isn't running.
if (!isResumedSession) throw error;
logger.error(`Failed to re-assert model "${currentModel.value}" on resume:`, error);
currentModel = (await readResumedLiveModel(query, models, logger)) ?? currentModel;
}
}
return {
@@ -4888,8 +5122,8 @@ export function toAcpNotifications(
let update: SessionNotification["update"] | null = null;
switch (chunk.type) {
case "text":
case "text_delta":
if (chunk.text.length > 0) {
case "text_delta": {
if (chunk.text) {
update = {
sessionUpdate: role === "assistant" ? "agent_message_chunk" : "user_message_chunk",
content: {
@@ -4899,6 +5133,7 @@ export function toAcpNotifications(
};
}
break;
}
case "image":
update = {
sessionUpdate: role === "assistant" ? "agent_message_chunk" : "user_message_chunk",
@@ -4911,10 +5146,10 @@ export function toAcpNotifications(
};
break;
case "thinking":
case "thinking_delta":
case "thinking_delta": {
// Recent models default `thinking.display` to "omitted", which streams
// signature-only thinking blocks whose text is empty.
if (chunk.thinking.length > 0) {
if (chunk.thinking) {
update = {
sessionUpdate: "agent_thought_chunk",
content: {
@@ -4924,6 +5159,7 @@ export function toAcpNotifications(
};
}
break;
}
case "tool_use":
case "server_tool_use":
case "mcp_tool_use": {

View File

@@ -1,9 +1,8 @@
import { randomUUID } from "node:crypto";
import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
import type {
CreateElicitationRequest,
CreateElicitationResponse,
ElicitationAcceptAction,
ElicitationContentValue,
ElicitationPropertySchema,
ElicitationSchema,
EnumOption,
@@ -70,43 +69,18 @@ export function mcpElicitationToCreateRequest(
};
}
/** A wire value allowed in elicitation form content. */
function isElicitationContentValue(value: unknown): value is ElicitationContentValue {
return (
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean" ||
(Array.isArray(value) && value.every((item) => typeof item === "string"))
);
}
/**
* Content of an accepted elicitation response.
*
* The response union includes a custom/future-action variant whose `action` is
* plain `string`, so a positive `=== "accept"` check can never narrow it away
* and `content` reads as `unknown`. Rather than asserting which union member
* this is, validate the values: the SDK's own wire validation has the same
* blind spot (an accept response with malformed content parses via the
* catch-all variant), so ill-typed values are dropped here either way.
* Uses the SDK's validating guard rather than an `action === "accept"` check:
* the guard both narrows past the union's custom/future variant and validates
* the payload, so a malformed accept (right tag, ill-typed content) yields
* empty content — the same classification the SDK's wire validators apply.
*/
function acceptedElicitationContent(
response: CreateElicitationResponse,
): NonNullable<ElicitationAcceptAction["content"]> {
if (response.action !== "accept") {
return {};
}
const { content } = response;
if (!content || typeof content !== "object") {
return {};
}
const validated: NonNullable<ElicitationAcceptAction["content"]> = {};
for (const [key, value] of Object.entries(content)) {
if (isElicitationContentValue(value)) {
validated[key] = value;
}
}
return validated;
return CreateElicitationResponse.isAccept(response) ? (response.content ?? {}) : {};
}
/**

38
src/sdk-shims.d.ts vendored
View File

@@ -1,38 +0,0 @@
/**
* Shims for types the `@anthropic-ai/claude-agent-sdk` 0.3.198 `sdk.d.ts`
* references in its `SDKMessage` union but never declares
* (`SDKControlRequestProgressMessage`, `SDKConversationResetMessage`). With
* `skipLibCheck` the dangling references silently become error-`any`, which
* poisons narrowing on the whole union — every exhaustive switch over
* `SDKMessage` stops compiling.
*
* These must stay GLOBAL declarations (no top-level import/export in this
* file): unqualified names inside the SDK's module declaration fall back to
* the global scope, which is the only way a consumer can supply them. Shapes
* are transcribed from the zod schemas embedded in the bundled CLI binary.
* Delete this file once the SDK ships the declarations itself — a proper
* module-scoped declaration will shadow these.
*/
/** Progress for a long-running client-originated control request (currently
* only `side_question`), correlated by `request_id`. */
declare type SDKControlRequestProgressMessage = {
type: "control_request_progress";
request_id: string;
status: "started" | "api_retry";
attempt?: number;
max_retries?: number;
retry_delay_ms?: number;
error_status?: number | null;
uuid: import("crypto").UUID;
session_id: string;
};
/** Emitted by `/clear`, plan-mode exit, and fresh-session flows; the surface
* should mount a fresh transcript under `new_conversation_id`. */
declare type SDKConversationResetMessage = {
type: "conversation_reset";
new_conversation_id: import("crypto").UUID;
uuid: import("crypto").UUID;
session_id: string;
};

View File

@@ -82,6 +82,17 @@ function userEcho(u: any) {
};
}
/** The `usage` a cancelled active turn settles with when the cancel pre-empted
* its result: all zeros, since only a turn's terminal result feeds the
* accumulator (issue #844). */
const cancelledTurnUsage = {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
totalTokens: 0,
};
/** Wrap a mock async generator with the `Query` methods the agent calls outside
* of iteration — `close()` (teardown/closeQueryStream), `interrupt()` (cancel),
* and `setModel()` — so a bare generator doesn't trip "x is not a function". */
@@ -204,9 +215,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
params: CreateElicitationRequest,
): Promise<CreateElicitationResponse> {
this.elicitations.push(params);
// The `in` check also excludes the custom/future-mode variant, whose
// `mode: string` would otherwise survive the literal comparison.
if (params.mode !== "form" || !("requestedSchema" in params)) {
if (!CreateElicitationRequest.isForm(params)) {
return { action: "decline" };
}
// Accept the first option of every choice field (skip the free-text one).
@@ -437,10 +446,9 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
// ...built by our converter (indexed field key + free-text "Other" field),
// which confirms our interception path produced it rather than some other
// mechanism.
const properties =
elicitation.mode === "form" && "requestedSchema" in elicitation
? Object.keys(elicitation.requestedSchema.properties ?? {})
: [];
const properties = CreateElicitationRequest.isForm(elicitation)
? Object.keys(elicitation.requestedSchema.properties ?? {})
: [];
expect(properties).toContain("question_0");
expect(properties).toContain("question_0_custom");
@@ -4808,6 +4816,22 @@ describe("assembled assistant text fallback", () => {
expect(messageChunkTexts(updates)).toEqual(["real answer"]);
});
it("dedupes a streamed text block even when a thinking delta omits text", async () => {
const { agent, updates } = createMockAgentWithCapture();
injectSession(agent, [
messageStart("msg-missing-thinking"),
thinkingDelta(undefined as any),
textDelta("real answer"),
assistantMessage("msg-missing-thinking", [{ type: "text", text: "real answer" }]),
result(),
idle,
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
expect(messageChunkTexts(updates)).toEqual(["real answer"]);
});
it("does not re-emit the next turn's text after a turn is cancelled mid-stream", async () => {
// Regression: streamedBlocks is reset inside the consolidated-assistant
// branch, but a cancelled turn `break`s out before reaching it (the
@@ -4863,7 +4887,10 @@ describe("assembled assistant text fallback", () => {
await agent.cancel({ sessionId: "test-session" });
releaseCancel();
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "second" }] });
@@ -5791,10 +5818,55 @@ describe("post-error recovery", () => {
prompt: [{ type: "text", text: "second" }],
});
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual(expect.objectContaining({ stopReason: "end_turn" }));
});
it("exposes the accumulated usage on a cancelled turn's PromptResponse (issue #844)", async () => {
// The interrupted turn's result is dropped at the `session.cancelled`
// guard, but its usage was already accumulated — the cancelled settle must
// report it so clients metering token spend don't lose the round-trips
// that completed before the cancel.
const agent = createMockAgent();
let releaseAfterCancel!: () => void;
const afterCancel = new Promise<void>((resolve) => (releaseAfterCancel = resolve));
injectGeneratorSession(agent, (input) => {
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const u1 = await iter.next();
yield userEcho(u1.value); // turn 1 active
await afterCancel; // wait until the test has cancelled turn 1
// The interrupt still yields the turn's result (usage 10/5) before the
// trailing idle that settles it cancelled.
yield createResultMessage({ subtype: "success", stop_reason: "end_turn", is_error: false });
yield { type: "system", subtype: "session_state_changed", state: "idle" };
}
return messageGenerator();
});
const first = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
});
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
await agent.cancel({ sessionId: "test-session" });
releaseAfterCancel();
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: {
inputTokens: 10,
outputTokens: 5,
cachedReadTokens: 0,
cachedWriteTokens: 0,
totalTokens: 15,
},
});
});
it("ignores cancel() after the query stream has closed (no interrupt on a dead query)", async () => {
const agent = createMockAgent();
injectGeneratorSession(agent, (input) => {
@@ -5893,7 +5965,10 @@ describe("post-error recovery", () => {
releaseEnd(); // stream ends -> done branch settles turn 1 + rejects turn 2
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).rejects.toThrow(/start a new session/);
});
@@ -5934,7 +6009,10 @@ describe("post-error recovery", () => {
await agent.cancel({ sessionId: "test-session" });
releaseAfterCancel();
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
// session.cancelled is still true here (turn 1 settled, nothing re-activated).
// The /compact result must still settle via head-promotion.
@@ -5997,7 +6075,12 @@ describe("post-error recovery", () => {
});
afterCancelAndQueue();
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
// Turn 1 ran (and was cancelled mid-flight) so it reports its usage;
// turn 2 never ran, so its cancelled settle carries none.
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
const thirdResult = await third;
expect(thirdResult.stopReason).toBe("end_turn");
@@ -6065,7 +6148,10 @@ describe("post-error recovery", () => {
});
release();
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
const compactResult = await compact;
expect(compactResult.stopReason).toBe("end_turn");
@@ -6074,6 +6160,145 @@ describe("post-error recovery", () => {
expect(compactResult.usage?.inputTokens).toBe(10);
await agent.sessions["test-session"]?.consumer;
});
it("uncounts an orphan the interrupt receipt reports dropped, so a no-echo result isn't swallowed", async () => {
// interrupt_receipt_v1 CLIs: interrupt() resolves with `still_queued` —
// the queued messages that will still run. Here the interrupt DROPS turn
// 2's queued message (still_queued: []), so its orphan result never
// arrives. Without the receipt reconciliation the stale count (1) would
// swallow the next echo-less result — the /compact below — and hang its
// prompt; activation's reset can't help because /compact never echoes.
const agent = createMockAgent();
let release!: () => void;
const gate = new Promise<void>((resolve) => (release = resolve));
injectGeneratorSession(agent, (input) => {
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const u1 = await iter.next();
yield userEcho(u1.value); // turn 1 active
await iter.next(); // turn 2's pushed message (cancelled; interrupt drops it)
await gate; // wait until the test cancels and sends /compact
yield { type: "system", subtype: "session_state_changed", state: "idle" }; // turn 1 settles cancelled
// NO orphan result for turn 2 — the interrupt dropped its queued message.
await iter.next(); // /compact's pushed message — never echoes its uuid
yield {
type: "system",
subtype: "status",
status: "compacting",
session_id: "test-session",
};
yield createResultMessage({ subtype: "success", stop_reason: "end_turn", is_error: false });
yield { type: "system", subtype: "session_state_changed", state: "idle" };
}
return messageGenerator();
});
// New-CLI interrupt: nothing queued survives the interrupt.
agent.sessions["test-session"]!.query.interrupt = vi.fn(async () => ({ still_queued: [] }));
const first = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
});
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
const second = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "second" }],
});
await waitFor(() => (agent.sessions["test-session"]?.turnQueue?.length ?? 0) >= 2);
await agent.cancel({ sessionId: "test-session" }); // counts turn 2, then the receipt uncounts it
expect(agent.sessions["test-session"]?.pendingOrphanResults).toBe(0);
const compact = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "/compact" }],
});
release();
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
const compactResult = await compact;
expect(compactResult.stopReason).toBe("end_turn");
expect(compactResult.usage?.inputTokens).toBe(10);
await agent.sessions["test-session"]?.consumer;
});
it("keeps counting an orphan the interrupt receipt reports still queued", async () => {
// The receipt lists turn 2's uuid in still_queued — its message survives
// the interrupt, runs, and emits an orphan result that must still be
// skipped (not promoted onto the later /compact).
const agent = createMockAgent();
let release!: () => void;
const gate = new Promise<void>((resolve) => (release = resolve));
const orphanResult = createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: false,
});
orphanResult.usage.input_tokens = 999;
injectGeneratorSession(agent, (input) => {
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const u1 = await iter.next();
yield userEcho(u1.value); // turn 1 active
await iter.next(); // turn 2's pushed message (cancelled, but survives the interrupt)
await gate;
yield { type: "system", subtype: "session_state_changed", state: "idle" }; // turn 1 settles cancelled
yield orphanResult; // turn 2's orphan — the kept count skips it
await iter.next(); // /compact's pushed message — never echoes its uuid
yield {
type: "system",
subtype: "status",
status: "compacting",
session_id: "test-session",
};
yield createResultMessage({ subtype: "success", stop_reason: "end_turn", is_error: false });
yield { type: "system", subtype: "session_state_changed", state: "idle" };
}
return messageGenerator();
});
const first = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
});
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
const second = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "second" }],
});
await waitFor(() => (agent.sessions["test-session"]?.turnQueue?.length ?? 0) >= 2);
// Report turn 2's queued message as surviving the interrupt. Captured
// before cancel() filters the queue (interrupt runs after the filter).
const session = agent.sessions["test-session"]!;
const survivingUuid = session.turnQueue![1].promptUuid;
session.query.interrupt = vi.fn(async () => ({ still_queued: [survivingUuid] }));
await agent.cancel({ sessionId: "test-session" });
expect(agent.sessions["test-session"]?.pendingOrphanResults).toBe(1);
const compact = agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "/compact" }],
});
release();
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
const compactResult = await compact;
expect(compactResult.stopReason).toBe("end_turn");
// /compact settled with its OWN result, proving the surviving orphan was
// skipped rather than promoted onto it (which would leak the 999).
expect(compactResult.usage?.inputTokens).toBe(10);
await agent.sessions["test-session"]?.consumer;
});
});
describe("session/cancel wedge recovery (issue #680)", () => {
@@ -6233,7 +6458,10 @@ describe("session/cancel wedge recovery (issue #680)", () => {
// Clean up the wedged prompt + long timer.
await agent.closeSession({ sessionId: "test-session" });
await expect(promptPromise).resolves.toEqual({ stopReason: "cancelled" });
await expect(promptPromise).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
});
it("resolves an in-flight wedged prompt immediately when the session is closed", async () => {
@@ -6252,7 +6480,10 @@ describe("session/cancel wedge recovery (issue #680)", () => {
await agent.closeSession({ sessionId: "test-session" });
await expect(promptPromise).resolves.toEqual({ stopReason: "cancelled" });
await expect(promptPromise).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
expect(agent.sessions["test-session"]).toBeUndefined();
});
});
@@ -6370,7 +6601,10 @@ describe("turn abandoned by the SDK (issue #825)", () => {
prompt: [{ type: "text", text: "second" }],
});
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
const secondResult = await second;
expect(secondResult.stopReason).toBe("end_turn");
expect(secondResult.usage?.inputTokens).toBe(10);
@@ -6416,7 +6650,10 @@ describe("turn abandoned by the SDK (issue #825)", () => {
await agent.cancel({ sessionId: "test-session" });
releaseAfterCancel();
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
await expect(second).resolves.toEqual({ stopReason: "cancelled" });
const third = await agent.prompt({
sessionId: "test-session",
@@ -6461,7 +6698,10 @@ describe("turn abandoned by the SDK (issue #825)", () => {
});
await waitFor(() => !!agent.sessions["test-session"]?.activeTurn);
await agent.cancel({ sessionId: "test-session" }); // backstop (10ms) settles turn 1
await expect(first).resolves.toEqual({ stopReason: "cancelled" });
await expect(first).resolves.toEqual({
stopReason: "cancelled",
usage: cancelledTurnUsage,
});
// Queue turn 2 BEFORE the SDK recovers, so the stale result races it.
const second = agent.prompt({
@@ -6710,6 +6950,36 @@ describe("toAcpNotifications thinking chunks", () => {
expect(result).toEqual([]);
});
it("skips thinking chunks without string content", () => {
const result = toAcpNotifications(
[
{ type: "thinking", signature: "abc" },
{ type: "thinking_delta", estimated_tokens: 0 },
{ type: "thinking_delta", thinking: null, estimated_tokens: 0 },
] as any,
"assistant",
"test",
{},
{} as AcpClient,
console,
);
expect(result).toEqual([]);
});
it("skips text deltas without string content", () => {
const result = toAcpNotifications(
[{ type: "text_delta" }, { type: "text_delta", text: null }] as any,
"assistant",
"test",
{},
{} as AcpClient,
console,
);
expect(result).toEqual([]);
});
});
describe("messageIdForGrouping", () => {

View File

@@ -2,7 +2,11 @@ import { describe, it, expect } from "vitest";
import { randomUUID } from "crypto";
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
import { resolveModelPreference, applyAvailableModelsAllowlist } from "../acp-agent.js";
import {
resolveModelPreference,
applyAvailableModelsAllowlist,
matchResumedModel,
} from "../acp-agent.js";
// Mirrors a real `supportedModels()` response: alias rows carry
// `resolvedModel`, and "Sonnet 5" has no `major.minor` dot unlike older
@@ -68,6 +72,124 @@ describe("resolveModelPreference - resolvedModel matching", () => {
it("returns null for a preference that matches nothing", () => {
expect(resolveModelPreference(LIVE_SHAPED_MODELS, "claude-gpt-99")).toBeNull();
});
it("matches a '-1m' id-suffix preference against a '[1m]'-spelled resolvedModel", () => {
// Without hint canonicalization the exact tier misses and the substring
// tier lands on the bare 200k sibling, silently downgrading a 1M pin.
const models: ModelInfo[] = [
{
value: "sonnet",
resolvedModel: "claude-sonnet-5",
displayName: "Sonnet",
description: "Sonnet 5 · Efficient for routine tasks",
},
{
value: "sonnet[1m]",
resolvedModel: "claude-sonnet-5[1m]",
displayName: "Sonnet",
description: "Sonnet 5 with 1M context",
},
];
expect(resolveModelPreference(models, "claude-sonnet-5-1m")?.value).toBe("sonnet[1m]");
});
it("matches a '-1m'-suffix alias directly against a '[1m]'-spelled value", () => {
expect(resolveModelPreference(LIVE_SHAPED_MODELS, "opus-1m")?.value).toBe("opus[1m]");
});
// Rows without resolvedModel (older SDKs, allowlist-synthesized entries,
// option-derived lists) can't use the exact resolved tier, so the hinted
// preference must not be absorbed by the bare sibling in the substring
// tier — the tokenized tier scores the hinted row correctly instead.
it("does not let a bare sibling absorb a hinted preference when resolvedModel is absent", () => {
const models: ModelInfo[] = [
{ value: "sonnet", displayName: "Sonnet", description: "" },
{ value: "sonnet[1m]", displayName: "Sonnet", description: "" },
];
expect(resolveModelPreference(models, "claude-sonnet-5[1m]")?.value).toBe("sonnet[1m]");
expect(resolveModelPreference(models, "claude-sonnet-5-1m")?.value).toBe("sonnet[1m]");
// Bare preferences still land on the bare row via the substring tier.
expect(resolveModelPreference(models, "claude-sonnet-5")?.value).toBe("sonnet");
});
});
describe("matchResumedModel", () => {
// Environment shape from issue #845: the default resolves to Sonnet, the
// session was explicitly switched to Opus before the process restart.
const SONNET_DEFAULT_MODELS: ModelInfo[] = [
{
value: "default",
resolvedModel: "claude-sonnet-5",
displayName: "Default (recommended)",
description: "Use the default model (currently Sonnet 5)",
},
{
value: "opus",
resolvedModel: "claude-opus-4-6[1m]",
displayName: "Opus",
description: "Opus 4.6 · Most capable for complex work",
},
{
value: "sonnet",
resolvedModel: "claude-sonnet-5",
displayName: "Sonnet",
description: "Sonnet 5 · Efficient for routine tasks",
},
{
value: "sonnet[1m]",
resolvedModel: "claude-sonnet-5[1m]",
displayName: "Sonnet",
description: "Sonnet 5 with 1M context",
},
];
it("maps an explicitly-switched session's live model to its picker alias", () => {
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-opus-4-6").value).toBe("opus");
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-opus-4-6[1m]").value).toBe("opus");
});
it("keeps Default selected when the live model is what Default resolves to", () => {
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5").value).toBe("default");
});
it("prefers an exact named-row resolvedModel match over the hint-stripped Default tier", () => {
// "claude-sonnet-5[1m]" differs from Default's "claude-sonnet-5" only by
// the context hint; stripping both would misreport the explicit 1M row as
// Default (and infer the wrong context window downstream). Both hint
// spellings must land on the row regardless of which one it uses.
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5[1m]").value).toBe(
"sonnet[1m]",
);
expect(matchResumedModel(SONNET_DEFAULT_MODELS, "claude-sonnet-5-1m").value).toBe("sonnet[1m]");
});
it("normalizes the '-1m' id-suffix hint form and casing against Default's resolution", () => {
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8-1m").value).toBe("default");
expect(matchResumedModel(LIVE_SHAPED_MODELS, "Claude-Opus-4-8[1M]").value).toBe("default");
});
it("prefers Default over a named row sharing the identical resolvedModel", () => {
// "opus[1m]" carries the same resolvedModel as Default; the live id can't
// tell the two apart, so a never-customized session stays on Default.
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8[1m]").value).toBe("default");
});
it("ignores '[1m]' context hints when comparing against Default's resolution", () => {
// The transcript-restored model drops the "[1m]" context hint, so a
// session that never left the 1M-context default resumes as the bare id.
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-opus-4-8").value).toBe("default");
});
it("still resolves non-default live models against the picker entries", () => {
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-sonnet-5").value).toBe("sonnet");
expect(matchResumedModel(LIVE_SHAPED_MODELS, "claude-haiku-4-5-20251001").value).toBe("haiku");
});
it("tracks a live model with no picker counterpart verbatim", () => {
const result = matchResumedModel(LIVE_SHAPED_MODELS, "claude-gpt-99");
expect(result.value).toBe("claude-gpt-99");
expect(result.displayName).toBe("claude-gpt-99");
});
});
describe("applyAvailableModelsAllowlist - resolvedModel matching", () => {

View File

@@ -819,6 +819,85 @@ describe("session config options", () => {
expect(setModelSpy).toHaveBeenCalledWith("claude-sonnet-4-6");
});
// Option entries carry no resolvedModel, so alias resolution must consult
// session.modelInfos — otherwise a full model id in either hint spelling
// ("[1m]"/"-1m") falls to the substring tier and lands on the bare 200k
// sibling, silently downgrading the session's context lane.
it("resolves a full model id onto its hinted row via session.modelInfos", async () => {
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
session.models = {
currentModelId: "sonnet",
availableModels: [
{ modelId: "sonnet", name: "Sonnet", description: "" },
{ modelId: "sonnet[1m]", name: "Sonnet", description: "" },
],
};
session.modelInfos = [
{
value: "sonnet",
resolvedModel: "claude-sonnet-5",
displayName: "Sonnet",
description: "",
},
{
value: "sonnet[1m]",
resolvedModel: "claude-sonnet-5[1m]",
displayName: "Sonnet",
description: "",
},
];
session.configOptions = session.configOptions.map((o: { id: string }) =>
o.id === "model"
? {
...o,
currentValue: "sonnet",
options: [
{ value: "sonnet", name: "Sonnet" },
{ value: "sonnet[1m]", name: "Sonnet" },
],
}
: o,
);
await agent.setSessionConfigOption({
sessionId: SESSION_ID,
configId: "model",
value: "claude-sonnet-5[1m]",
});
expect(setModelSpy).toHaveBeenCalledWith("sonnet[1m]");
setModelSpy.mockClear();
await agent.setSessionConfigOption({
sessionId: SESSION_ID,
configId: "model",
value: "claude-sonnet-5-1m",
});
expect(setModelSpy).toHaveBeenCalledWith("sonnet[1m]");
});
// A session can be running a model with no picker entry (resumed onto a
// model excluded by the availableModels allowlist, or a refusal
// fallback); its verbatim id is then the option's currentValue. A client
// round-tripping that reported value must not get "Invalid value".
it("accepts the reported currentValue even when it has no options entry", async () => {
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
session.models = { ...session.models, currentModelId: "claude-offlist-9" };
session.configOptions = session.configOptions.map((o: { id: string }) =>
o.id === "model" ? { ...o, currentValue: "claude-offlist-9" } : o,
);
const response = await agent.setSessionConfigOption({
sessionId: SESSION_ID,
configId: "model",
value: "claude-offlist-9",
});
expect(setModelSpy).toHaveBeenCalledWith("claude-offlist-9");
expect(response.configOptions?.find((o) => o.id === "model")?.currentValue).toBe(
"claude-offlist-9",
);
});
it("setSessionMode also syncs configOptions", async () => {
await agent.setSessionMode({ sessionId: SESSION_ID, modeId: "plan" });

View File

@@ -6,9 +6,11 @@ import { AcpClient, ClaudeAcpAgent } from "../acp-agent.js";
import { Pushable } from "../utils.js";
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
function createMockClient(): AcpClient {
function createMockClient(
sessionUpdate: (notification: SessionNotification) => Promise<void> = async () => {},
): AcpClient {
return {
sessionUpdate: async (_notification: SessionNotification) => {},
sessionUpdate,
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
readTextFile: async () => ({ content: "" }),
writeTextFile: async () => ({}),
@@ -330,4 +332,52 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("session load/resume lifecyc
}
expect(recordedUserChunks.some((c) => c.includes("hi"))).toBe(true);
}, 60000);
// Regression test for https://github.com/agentclientprotocol/claude-agent-acp/issues/845
// On resume the CLI restores the model the session's transcript was running,
// so loadSession must report that live model instead of recomputing the
// env/settings/default choice from scratch. Haiku is used because no account
// resolves its default there, so the restored model provably differs from
// the freshly-computed default. Requires no ANTHROPIC_MODEL env var and no
// "model" key in settings.json (either would pin the model on resume and
// mask the reconciliation this test covers).
it("ACP: loadSession reports the model the resumed session is actually running", async () => {
const agentChunks: string[] = [];
const client = createMockClient(async (notification) => {
if (notification.update.sessionUpdate === "agent_message_chunk") {
const content = notification.update.content;
if (content.type === "text") agentChunks.push(content.text);
}
});
const modelOf = (opts: { id: string; currentValue?: unknown }[] | null | undefined) =>
opts?.find((o) => o.id === "model")?.currentValue;
const agent = new ClaudeAcpAgent(client);
try {
const cwd = process.cwd();
const { sessionId, configOptions } = await agent.newSession({ cwd, mcpServers: [] });
expect(modelOf(configOptions)).not.toBe("haiku");
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "haiku" });
// A real turn on Haiku, so the transcript's last assistant message —
// which is what the CLI restores the model from — records it.
await agent.prompt({
sessionId,
prompt: [{ type: "text", text: "Reply with only the word OK" }],
});
await agent.closeSession({ sessionId });
const loadResp = await agent.loadSession({ sessionId, cwd, mcpServers: [] });
expect(modelOf(loadResp.configOptions)).toBe("haiku");
// And the reported option agrees with the live session (`/context`).
agentChunks.length = 0;
await agent.prompt({ sessionId, prompt: [{ type: "text", text: "/context" }] });
const liveModel = agentChunks.join("\n").match(/\*\*Model:\*\*\s*(\S+)/)?.[1];
expect(liveModel).toContain("haiku");
} finally {
await agent.dispose();
}
}, 120000);
});