Compare commits

..

32 Commits

Author SHA1 Message Date
Ben Brandt
affc731e3a docs: clarify stream-id tracking and liveStreamed dedup assumptions
Add comments explaining (1) why the streamed-id tracking runs for every
stream_event rather than only parent_tool_use_id === null, and that the
single currentStreamingMessageId field is safe given serialized events,
and (2) that the liveStreamed drop is all-or-nothing per message id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:03:50 +02:00
cuiyiming.leo95
63b708176e fix: dedup fallback text/thinking per content block, not per message id
The fallback added in dc640e2 keyed its dedup on the assistant message
id: once a message id emitted any text/thinking block, the id was added
to `textStreamedMessageIds` and every later block under that id was
filtered out. That breaks when an OpenAI-compatible gateway (observed
with gpt-5.4) splits a single assistant response into several `assistant`
messages that share one id but each carry a different block — typically
an empty `thinking` block first, then the real `text` block. The empty
thinking block flagged the id, so the actual answer arriving in a later
same-id message was dropped: the client saw the fallback log fire but
received an empty turn.

Track the assembled blocks the fallback already emitted this turn
(`fallbackEmittedBlockKeys`, keyed on id + block type + block content)
instead of flagging the whole message id. Distinct blocks that share an
id are all forwarded, while identical re-deliveries (an incremental block
followed by a consolidated message, or the same message delivered twice)
still dedup. `textStreamedMessageIds` returns to its original meaning —
"was this streamed live via deltas" — and is no longer written by the
fallback. The new set is turn-scoped and cleared alongside it.

Tests cover split-block delivery (empty thinking then text under one id),
multiple distinct text blocks sharing an id, and the
incremental-then-consolidated dedup case.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 23:57:26 -07:00
cuiyiming.leo95
dc640e2dcf fix: fall back to assembled assistant text when no stream deltas arrive
The `assistant` case in the prompt loop unconditionally filtered out
`text` and `thinking` content blocks on the assumption that those had
already been pushed to the client via `stream_event` →
`content_block_delta` notifications. That assumption holds when the
underlying SDK / model proxy emits Anthropic-style streaming deltas, but
it breaks when the model is fronted by a proxy that returns the second
model call (the one that consumes a tool_result) as a single non-
streamed assistant message. In that case `content_block_delta` for the
final text is never produced, the assembled `text` block is filtered
out, and the ACP client receives `stopReason: "end_turn"` with no
`agent_message_chunk` notifications — so the user sees the tool's
progress but an empty final answer.

Track which assistant message ids have actually produced streamed
text/thinking deltas (`textStreamedMessageIds` on `Session`, populated
from `message_start` + `content_block_delta` in the `stream_event`
case). In the `assistant` case, keep the existing filter when the id is
in that set and fall back to forwarding the assembled `text` / `thinking`
blocks as `agent_message_chunk` / `agent_thought_chunk` when it isn't.
The fallback emits exactly once per assistant message id, mirroring the
dedupe pattern already used by `toolUseCache` for tool_use blocks.

Tests cover both branches: a session whose assistant message arrives
with no preceding `content_block_delta` now produces exactly one text
chunk, and a session that did stream the same text via deltas does not
re-emit it.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 18:51:35 -07:00
acp-release-bot[bot]
36822c2b75 chore(main): release 0.37.0 (#690)
🤖 I have created a release *beep* *boop*
---


##
[0.37.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.36.1...v0.37.0)
(2026-05-21)


### Features

* add 'Default' option to effort level config
([#701](https://github.com/agentclientprotocol/claude-agent-acp/issues/701))
([9e259d1](9e259d128a))
* **deps:** bump the claude-agent-sdk to 0.3.146
([#700](https://github.com/agentclientprotocol/claude-agent-acp/issues/700))
([72875b2](72875b248c))
* **deps:** bump the minor group with 13 updates
([#689](https://github.com/agentclientprotocol/claude-agent-acp/issues/689))
([f66bc31](f66bc31ae4))
* Emit tool calls for memory recall events
([#703](https://github.com/agentclientprotocol/claude-agent-acp/issues/703))
([a0bfb98](a0bfb98ba7)),
closes
[#650](https://github.com/agentclientprotocol/claude-agent-acp/issues/650)


### Bug Fixes

* Avoid cross-version model alias matches
([#702](https://github.com/agentclientprotocol/claude-agent-acp/issues/702))
([e1e1c69](e1e1c69029))
* Avoid redundant initial model sync
([#704](https://github.com/agentclientprotocol/claude-agent-acp/issues/704))
([b275f6f](b275f6ff7f)),
closes
[#646](https://github.com/agentclientprotocol/claude-agent-acp/issues/646)
* Don't expose /clear in commands
([#705](https://github.com/agentclientprotocol/claude-agent-acp/issues/705))
([cfce130](cfce130707))
* emit "cancelled" instead of "end_turn" when the session was
interrupted.
([#694](https://github.com/agentclientprotocol/claude-agent-acp/issues/694))
([2414a6f](2414a6f98b))
* Recover prompt stream after a failed turn
([#706](https://github.com/agentclientprotocol/claude-agent-acp/issues/706))
([2711f50](2711f506d5)),
closes
[#654](https://github.com/agentclientprotocol/claude-agent-acp/issues/654)

---
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-05-22 00:15:02 +02:00
AceGain
9e259d128a feat: add 'Default' option to effort level config (#701)
Add a "Default" selection to the effort level config option, allowing
users to let the SDK decide the effort level instead of forcing a
specific level. When "default" is selected, applyFlagSettings is called
with effortLevel: undefined, clearing any previous override.

Replaces the previous fallback logic (xhigh -> high ->
supportedLevels[0]) with a simpler "default" fallback that lets the SDK
use its own default.

Please,agree it. The third-party model may not support the 'Effort', or
not support 'high' and 'xhigh'. We need it.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
2026-05-21 22:11:14 +00:00
Ben Brandt
2711f506d5 fix: Recover prompt stream after a failed turn (#706)
When a prompt errors mid-turn (e.g. a 400/529 from the API), the SDK
leaves a trailing session_state_changed: idle in the query iterator.
The next prompt's first query.next() consumed that stale idle and
short-circuited to end_turn with zero usage, shifting every subsequent
response one slot late until the session was restarted.

In the catch block, call query.interrupt() and drain the iterator
until the trailing idle is consumed (bounded so a misbehaving SDK
can't hang the next prompt). In the finally block, on the error path,
resolve all queued pending prompts as cancelled — the previous code
only resolved one (with handoff semantics), leaking the rest forever.

Closes #654
Supersedes #659, #660, #691
2026-05-21 22:03:39 +00:00
Genhao Liu
2414a6f98b fix: emit "cancelled" instead of "end_turn" when the session was interrupted. (#694)
### Summary

Fix session_state_changed: idle handler to check session.cancelled,
ensuring the prompt response returns stopReason:
  "cancelled" instead of "end_turn" when the session was interrupted.

### Problem

When a client sends session/cancel, the cancel() method sets
session.cancelled = true and calls query.interrupt(). The
SDK then transitions to idle state and emits a session_state_changed
event with state: "idle". However, the handler
for this event returns the prompt response immediately without checking
session.cancelled:

```
  case "session_state_changed": {
    if (message.state === "idle") {
      return { stopReason, usage: sessionUsage(session) };  // stopReason defaults to "end_turn"
    }
    break;
  }
```
Since stopReason is initialized as "end_turn" and may not have been
updated to "cancelled" by a preceding result
message (the result handler at line 601 does check session.cancelled),
the prompt response can incorrectly report
  stopReason: "end_turn" when the turn was actually cancelled.

This causes clients (e.g. JetBrains AI Assistant) to treat the turn as
completed normally rather than cancelled,
  leaving the frontend UI stuck in a "running" state.

### Fix

Check session.cancelled in the session_state_changed: idle handler,
mirroring the checks already present in the done
  handler (line 464) and the result handler (line 601):

```
  case "session_state_changed": {
    if (message.state === "idle") {
      if (session.cancelled) {
        stopReason = "cancelled";
      }
      return { stopReason, usage: sessionUsage(session) };
    }
    break;
  }
```

Per the ACP spec, upon receiving session/cancel the agent must respond
to the original session/prompt request with
StopReason::Cancelled. This fix ensures that obligation is met
regardless of which message type triggers the prompt
  response.

### Test plan

- Start a session, send a prompt, then immediately cancel — verify the
prompt response returns stopReason: "cancelled"
   and the client UI transitions out of "running" state
- Start a session, send a prompt, let it complete normally — verify
stopReason remains "end_turn" (no false
  cancellation)
  - Existing test suite passes (npm run test:run)
2026-05-21 22:00:44 +00:00
Ben Brandt
cfce130707 fix: Don't expose /clear in commands (#705)
Closes #657

This probably isn't the desired fix, but as /clear creates a new
session, it breaks a lot of guarantees on ACP's usage of session ids we
want to uphold. Given you can do session/new to get the same effect, it
seems safer.
2026-05-21 21:31:53 +00:00
Ben Brandt
b275f6ff7f fix: Avoid redundant initial model sync (#704)
Addresses #646
2026-05-21 21:07:46 +00:00
Ben Brandt
a0bfb98ba7 feat: Emit tool calls for memory recall events (#703)
Closes #650
2026-05-21 21:05:45 +00:00
Ben Brandt
e1e1c69029 fix: Avoid cross-version model alias matches (#702)
Partially addresses #639

Deduping may reintroduce issues from #620 so going to hold off on that
for now.
2026-05-21 20:49:29 +00:00
dependabot[bot]
72875b248c feat(deps): bump the claude-agent-sdk to 0.3.146 (#700)
Bumps the minor group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
|
[@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript)
| `0.3.143` | `0.3.146` |
|
[@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript)
| `0.96.0` | `0.97.1` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `25.9.0` | `25.9.1` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.6` | `4.1.7` |
| [hono](https://github.com/honojs/hono) | `4.12.19` | `4.12.21` |


Updates `@anthropic-ai/claude-agent-sdk` from 0.3.143 to 0.3.146
<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.146</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.146</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.146
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.146
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.146
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.146
</code></pre>
<h2>v0.3.145</h2>
<h2>What's changed</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.145</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.145
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.145
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.145
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.145
</code></pre>
<h2>v0.3.144</h2>
<h2>What's changed</h2>
<ul>
<li>Assistant messages and <code>StopFailure</code> hooks now report
<code>error: 'model_not_found'</code> when the selected model doesn't
exist or isn't available, instead of the generic
<code>'invalid_request'</code>. The <code>api_error_status</code> field
on result messages is now documented.</li>
<li>Added <code>@anthropic-ai/claude-agent-sdk/extract</code> export for
<code>bun build --compile</code> consumers: import the platform native
binary with <code>with { type: 'file' }</code>, call
<code>extractFromBunfs(binPath)</code> to copy it out of the compiled
executable's virtual filesystem, and pass the result to
<code>options.pathToClaudeCodeExecutable</code></li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.144
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.144
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.144
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.144
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</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.146</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.146</li>
</ul>
<h2>0.3.145</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.145</li>
</ul>
<h2>0.3.144</h2>
<ul>
<li>Assistant messages and <code>StopFailure</code> hooks now report
<code>error: 'model_not_found'</code> when the selected model doesn't
exist or isn't available, instead of the generic
<code>'invalid_request'</code>. The <code>api_error_status</code> field
on result messages is now documented.</li>
<li>Added <code>@anthropic-ai/claude-agent-sdk/extract</code> export for
<code>bun build --compile</code> consumers: import the platform native
binary with <code>with { type: 'file' }</code>, call
<code>extractFromBunfs(binPath)</code> to copy it out of the compiled
executable's virtual filesystem, and pass the result to
<code>options.pathToClaudeCodeExecutable</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c2f379d9cf"><code>c2f379d</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="a1500f03cf"><code>a1500f0</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="e62865e42f"><code>e62865e</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.143...v0.3.146">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/sdk` from 0.96.0 to 0.97.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-typescript/releases">@​anthropic-ai/sdk's
releases</a>.</em></p>
<blockquote>
<h2>sdk: v0.97.1</h2>
<h2>0.97.1 (2026-05-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.97.0...sdk-v0.97.1">sdk-v0.97.0...sdk-v0.97.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>runner:</strong> skip tool calls SessionToolRunner does not
own (<a
href="9987379abe">9987379</a>)</li>
</ul>
<h2>sdk: v0.97.0</h2>
<h2>0.97.0 (2026-05-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.96.0...sdk-v0.97.0">sdk-v0.96.0...sdk-v0.97.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>client:</strong> Add support for self-hosted sandboxes in
CMA with sandbox helpers (<a
href="659a343c82">659a343</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>typescript:</strong> upgrade tsc-multi so that it works with
Node 26 (<a
href="623f71c848">623f71c</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>tests:</strong> remove redundant File import (<a
href="cf821fcc06">cf821fc</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md">@​anthropic-ai/sdk's
changelog</a>.</em></p>
<blockquote>
<h2>0.97.1 (2026-05-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.97.0...sdk-v0.97.1">sdk-v0.97.0...sdk-v0.97.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>runner:</strong> skip tool calls SessionToolRunner does not
own (<a
href="9987379abe">9987379</a>)</li>
</ul>
<h2>0.97.0 (2026-05-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.96.0...sdk-v0.97.0">sdk-v0.96.0...sdk-v0.97.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>client:</strong> Add support for self-hosted sandboxes in
CMA with sandbox helpers (<a
href="659a343c82">659a343</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>typescript:</strong> upgrade tsc-multi so that it works with
Node 26 (<a
href="623f71c848">623f71c</a>)</li>
</ul>
<h3>Chores</h3>
<ul>
<li><strong>tests:</strong> remove redundant File import (<a
href="cf821fcc06">cf821fc</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ac9ece3c56"><code>ac9ece3</code></a>
chore: release main</li>
<li><a
href="1987147935"><code>1987147</code></a>
fix(runner): skip tool calls SessionToolRunner does not own</li>
<li><a
href="409ff0e502"><code>409ff0e</code></a>
chore: release main (<a
href="https://redirect.github.com/anthropics/anthropic-sdk-typescript/issues/1052">#1052</a>)</li>
<li>See full diff in <a
href="https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.96.0...sdk-v0.97.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 25.9.0 to 25.9.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 `vitest` from 4.1.6 to 4.1.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.7</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li><strong>runner</strong>: Limit concurrency per task branch in
addition to per leaf callbacks (backport)  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10384">vitest-dev/vitest#10384</a>
<a href="https://github.com/vitest-dev/vitest/commit/4f0f2a1ee"><!-- raw
HTML omitted -->(4f0f2)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.1.6...v4.1.7">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a09d47236e"><code>a09d472</code></a>
chore: release v4.1.7</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.3.143
to 0.3.146

Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.3.143 to
0.3.146

Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.3.143 to
0.3.146

Updates `@oxc-project/types` from 0.130.0 to 0.132.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">@​oxc-project/types's
releases</a>.</em></p>
<blockquote>
<h2>oxc crates_v0.132.0</h2>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>0f26de6 ecmascript: Resolve identifier value type via tracked
constants (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22234">#22234</a>)
(Alexander Lichter)</li>
<li>c27a8cf minifier: Normalize <code>{ x: x }</code> shorthand so
adjacent-if merge is idempotent (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22401">#22401</a>)
(Dunqing)</li>
<li>e431a0e parser: Break extends clause loop on fatal error (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22517">#22517</a>)
(Boshen)</li>
<li>e9ec7c6 minifier: Fold optional chains by base nullishness (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22236">#22236</a>)
(Alexander Lichter)</li>
<li>e6090e7 transformer: Keep enum IIFE when a non-inlinable value
reference remains (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22501">#22501</a>)
(Dunqing)</li>
<li>931b7d6 transformer: Inline const enum members through type-cast
wrappers (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22500">#22500</a>)
(Dunqing)</li>
<li>b9615b2 codegen: Preserve string quotes in require() calls during
minification (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22475">#22475</a>)
(zennnnnnn11)</li>
<li>c73c159 transformer/async-to-generator: Reparent parameter
initializer scopes (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22507">#22507</a>)
(camc314)</li>
<li>ecfd3ca transformer/async-to-generator: Move only parameter bindings
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22503">#22503</a>)
(camc314)</li>
<li>3ce3431 transformer/explicit-resource-managment: Preserve shadowed
for-head block (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22451">#22451</a>)
(camc314)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>ce92c6c semantic: <code>#[inline]</code>
<code>Scoping::get_binding</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22414">#22414</a>)
(Dunqing)</li>
<li>98be95c regular_expression: Track regex flags via bitflags (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22427">#22427</a>)
(Boshen)</li>
<li>dbbc059 jsdoc: Skip should_attach_jsdoc when no remaining comments
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22409">#22409</a>)
(Boshen)</li>
<li>217d7d8 minifier: Index <code>SymbolValues</code> by
<code>SymbolId</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22441">#22441</a>)
(Dunqing)</li>
<li>d782b78 minifier: Use BitSet for LiveUsageCollector live references
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22425">#22425</a>)
(Boshen)</li>
</ul>
<h2>oxc crates_v0.131.0</h2>
<h3>🚀 Features</h3>
<ul>
<li>bc91a17 codegen: Expose <code>Codegen::with_source_type</code>
method (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22432">#22432</a>)
(camc314)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>703557c codegen: Keep <code>exports[STR] = …</code> key as plain
string in minify (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22402">#22402</a>)
(Dunqing)</li>
<li>5ac7e79 minifier: Drop unused-var-init pure IIFEs and preserve
annotation for downstream (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22349">#22349</a>)
(Dunqing)</li>
<li>4ab57eb allocator: Fixed-size allocators use
<code>VirtualAlloc</code> on Windows (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22124">#22124</a>)
(overlookmotel)</li>
<li>66d77eb allocator: Fix segfault on Linux MUSL with fixed-size
allocators (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22388">#22388</a>)
(overlookmotel)</li>
<li>b8fbc1f transformer/object-rest-spread: Correct scope id when moving
bindings (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22419">#22419</a>)
(camc314)</li>
<li>18edc2c codegen: Keep <code>Object.defineProperty</code> property
name as plain string in minify (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22400">#22400</a>)
(Dunqing)</li>
<li>dda33de transformer/explicit-resource-management: Align lexical
binding scopes (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22320">#22320</a>)
(camc314)</li>
<li>8e79de8 transformer: Preserve for-await statement bodies (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22361">#22361</a>)
(camc314)</li>
<li>0cba210 transformer/class: Replace <code>new.target</code> in static
blocks (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22360">#22360</a>)
(camc314)</li>
<li>67ab1c9 transformer/es2018/for-await: Hoist for-await generated
bindings (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22355">#22355</a>)
(camc314)</li>
<li>c3ceb4a transformer/object-rest-spread: Use hoisted scope for
<code>for-of</code> temp refs (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22347">#22347</a>)
(camc314)</li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>73a9043 allocator/bitset: Avoid temp heap <code>String</code>
allocation (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22403">#22403</a>)
(camc314)</li>
<li>8b2f4f9 transformer/object-rest-spread: Collect
<code>Vec&lt;SymbolId</code> over
<code>Vec&lt;BindingIdentifier&gt;</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22418">#22418</a>)
(camc314)</li>
<li>83679ea parser: Split TriviaBuilder::handle_token hot/cold paths (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22415">#22415</a>)
(Boshen)</li>
<li>2c7d781 codegen: Inline identifier-name accessors (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22411">#22411</a>)
(Boshen)</li>
<li>618bc76 diagnostics: Inline <code>OxcDiagnosticInner</code> to avoid
heap allocation (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22406">#22406</a>)
(Boshen)</li>
<li>0b4e158 parser: Reserve cap <code>2</code> for sequence expressions
vec (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22374">#22374</a>)
(camc314)</li>
<li>5f3bdd0 codegen: Add <code>#[inline]</code> to <code>code</code>,
<code>code_len</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22373">#22373</a>)
(camc314)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="63b129182d"><code>63b1291</code></a>
release(crates): oxc v0.132.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22527">#22527</a>)</li>
<li><a
href="564ebbf46f"><code>564ebbf</code></a>
release(crates): oxc v0.131.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22459">#22459</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/crates_v0.132.0/npm/oxc-types">compare
view</a></li>
</ul>
</details>
<br />

Updates `@rolldown/binding-android-arm64` from 1.0.1 to 1.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">@​rolldown/binding-android-arm64's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.2</h2>
<h2>[1.0.2] - 2026-05-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>devtools: emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>update oxc to 0.132.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9449">#9449</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>update oxc to 0.131.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9424">#9424</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>allow checks.* to escalate emissions to hard errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9388">#9388</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: support watcher options <code>include</code> and
<code>exclude</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9395">#9395</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>emit warnings for invalid pure annotations (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9381">#9381</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>hash: keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>call <code>codeSplitting.groups[].name</code> in deterministic order
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>dev/lazy: make <code>resolve_id</code> idempotent when the resolved
id is already a lazy entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9439">#9439</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>chunk-optimization: publish absorbed dynamic-entry namespace
cross-chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9448">#9448</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: propagate pure annotation through compound exprs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9431">#9431</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>finalizer: skip redundant init call when barrel executes in same
chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9354">#9354</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: initialize wrapped ESM re-export owners (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9353">#9353</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>do not inherit __toESM across chunks for named-only external imports
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9333">#9333</a>)
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9415">#9415</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>watcher: don't write output or emit events after close() (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9328">#9328</a>)
by <a
href="https://github.com/situ2001"><code>@​situ2001</code></a></li>
<li>chunk-optimization: avoid unsafe dynamic-only merges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9398">#9398</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>cjs: rename CJS-wrapped locals that would shadow chunk-scope names
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9392">#9392</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev/lazy: watch lazy modules added in rebuilds (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9391">#9391</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>rolldown_dev: move dev example to break publish cycle (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9465">#9465</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>binding: drop unsafe napi string helper, hoist transform ArcStr (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9456">#9456</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>ecmascript_utils: split rewrite_ident_reference off JsxExt trait (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9417">#9417</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>use <code>ThreadsafeFunction::call_async_catch</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9390">#9390</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>devtools: document <code>@​rolldown/debug</code> usage and package
graph consumption (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9435">#9435</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>replace <code>Inter</code> with system font stack in OG template SVG
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9240">#9240</a>)
by <a
href="https://github.com/yvbopeng"><code>@​yvbopeng</code></a></li>
<li>remove <code>output.comments</code> warning as all issues have been
resolved (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9393">#9393</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>in-depth: clarify @<strong>PURE</strong> scope and document
positions (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9389">#9389</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>readme: remove release candidate notice (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9387">#9387</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>vite-resolve: cache importer existence checks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9443">#9443</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</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/binding-android-arm64's
changelog</a>.</em></p>
<blockquote>
<h2>[1.0.2] - 2026-05-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>devtools: emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>update oxc to 0.132.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9449">#9449</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>update oxc to 0.131.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9424">#9424</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>allow checks.* to escalate emissions to hard errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9388">#9388</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: support watcher options <code>include</code> and
<code>exclude</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9395">#9395</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>emit warnings for invalid pure annotations (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9381">#9381</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>hash: keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>call <code>codeSplitting.groups[].name</code> in deterministic order
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>dev/lazy: make <code>resolve_id</code> idempotent when the resolved
id is already a lazy entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9439">#9439</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>chunk-optimization: publish absorbed dynamic-entry namespace
cross-chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9448">#9448</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: propagate pure annotation through compound exprs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9431">#9431</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>finalizer: skip redundant init call when barrel executes in same
chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9354">#9354</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: initialize wrapped ESM re-export owners (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9353">#9353</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>do not inherit __toESM across chunks for named-only external imports
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9333">#9333</a>)
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9415">#9415</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>watcher: don't write output or emit events after close() (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9328">#9328</a>)
by <a
href="https://github.com/situ2001"><code>@​situ2001</code></a></li>
<li>chunk-optimization: avoid unsafe dynamic-only merges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9398">#9398</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>cjs: rename CJS-wrapped locals that would shadow chunk-scope names
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9392">#9392</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev/lazy: watch lazy modules added in rebuilds (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9391">#9391</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>rolldown_dev: move dev example to break publish cycle (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9465">#9465</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>binding: drop unsafe napi string helper, hoist transform ArcStr (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9456">#9456</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>ecmascript_utils: split rewrite_ident_reference off JsxExt trait (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9417">#9417</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>use <code>ThreadsafeFunction::call_async_catch</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9390">#9390</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>devtools: document <code>@​rolldown/debug</code> usage and package
graph consumption (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9435">#9435</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>replace <code>Inter</code> with system font stack in OG template SVG
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9240">#9240</a>)
by <a
href="https://github.com/yvbopeng"><code>@​yvbopeng</code></a></li>
<li>remove <code>output.comments</code> warning as all issues have been
resolved (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9393">#9393</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>in-depth: clarify @<strong>PURE</strong> scope and document
positions (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9389">#9389</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>readme: remove release candidate notice (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9387">#9387</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>vite-resolve: cache importer existence checks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9443">#9443</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
<li>binding: reduce plugin string clones (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9436">#9436</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f2757ed768"><code>f2757ed</code></a>
release: v1.0.2 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9478">#9478</a>)</li>
<li><a
href="b88de34520"><code>b88de34</code></a>
docs(devtools): document <code>@​rolldown/debug</code> usage and package
graph consumption ...</li>
<li><a
href="0f811f4d79"><code>0f811f4</code></a>
fix(hash): keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)</li>
<li><a
href="ed010fad05"><code>ed010fa</code></a>
feat(devtools): emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)</li>
<li><a
href="2b0bb7ffe7"><code>2b0bb7f</code></a>
feat(devtools): classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)</li>
<li><a
href="ff6fff24da"><code>ff6fff2</code></a>
fix: call <code>codeSplitting.groups[].name</code> in deterministic
order (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)</li>
<li><a
href="81fcc89b78"><code>81fcc89</code></a>
feat(devtools): map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)</li>
<li><a
href="98619ddbf8"><code>98619dd</code></a>
feat(devtools): mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)</li>
<li><a
href="0c7ac80de7"><code>0c7ac80</code></a>
feat(devtools): make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)</li>
<li><a
href="98e0620775"><code>98e0620</code></a>
feat(devtools): emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/rolldown/rolldown/commits/v1.0.2/packages/rolldown">compare
view</a></li>
</ul>
</details>
<br />

Updates `@rolldown/binding-darwin-arm64` from 1.0.1 to 1.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">@​rolldown/binding-darwin-arm64's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.2</h2>
<h2>[1.0.2] - 2026-05-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>devtools: emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>update oxc to 0.132.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9449">#9449</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>update oxc to 0.131.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9424">#9424</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>allow checks.* to escalate emissions to hard errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9388">#9388</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: support watcher options <code>include</code> and
<code>exclude</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9395">#9395</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>emit warnings for invalid pure annotations (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9381">#9381</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>hash: keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>call <code>codeSplitting.groups[].name</code> in deterministic order
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>dev/lazy: make <code>resolve_id</code> idempotent when the resolved
id is already a lazy entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9439">#9439</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>chunk-optimization: publish absorbed dynamic-entry namespace
cross-chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9448">#9448</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: propagate pure annotation through compound exprs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9431">#9431</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>finalizer: skip redundant init call when barrel executes in same
chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9354">#9354</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: initialize wrapped ESM re-export owners (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9353">#9353</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>do not inherit __toESM across chunks for named-only external imports
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9333">#9333</a>)
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9415">#9415</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>watcher: don't write output or emit events after close() (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9328">#9328</a>)
by <a
href="https://github.com/situ2001"><code>@​situ2001</code></a></li>
<li>chunk-optimization: avoid unsafe dynamic-only merges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9398">#9398</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>cjs: rename CJS-wrapped locals that would shadow chunk-scope names
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9392">#9392</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev/lazy: watch lazy modules added in rebuilds (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9391">#9391</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>rolldown_dev: move dev example to break publish cycle (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9465">#9465</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>binding: drop unsafe napi string helper, hoist transform ArcStr (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9456">#9456</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>ecmascript_utils: split rewrite_ident_reference off JsxExt trait (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9417">#9417</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>use <code>ThreadsafeFunction::call_async_catch</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9390">#9390</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>devtools: document <code>@​rolldown/debug</code> usage and package
graph consumption (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9435">#9435</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>replace <code>Inter</code> with system font stack in OG template SVG
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9240">#9240</a>)
by <a
href="https://github.com/yvbopeng"><code>@​yvbopeng</code></a></li>
<li>remove <code>output.comments</code> warning as all issues have been
resolved (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9393">#9393</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>in-depth: clarify @<strong>PURE</strong> scope and document
positions (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9389">#9389</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>readme: remove release candidate notice (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9387">#9387</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>vite-resolve: cache importer existence checks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9443">#9443</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</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/binding-darwin-arm64's
changelog</a>.</em></p>
<blockquote>
<h2>[1.0.2] - 2026-05-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>devtools: emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>update oxc to 0.132.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9449">#9449</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>update oxc to 0.131.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9424">#9424</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>allow checks.* to escalate emissions to hard errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9388">#9388</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: support watcher options <code>include</code> and
<code>exclude</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9395">#9395</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>emit warnings for invalid pure annotations (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9381">#9381</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>hash: keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>call <code>codeSplitting.groups[].name</code> in deterministic order
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>dev/lazy: make <code>resolve_id</code> idempotent when the resolved
id is already a lazy entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9439">#9439</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>chunk-optimization: publish absorbed dynamic-entry namespace
cross-chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9448">#9448</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: propagate pure annotation through compound exprs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9431">#9431</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>finalizer: skip redundant init call when barrel executes in same
chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9354">#9354</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: initialize wrapped ESM re-export owners (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9353">#9353</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>do not inherit __toESM across chunks for named-only external imports
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9333">#9333</a>)
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9415">#9415</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>watcher: don't write output or emit events after close() (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9328">#9328</a>)
by <a
href="https://github.com/situ2001"><code>@​situ2001</code></a></li>
<li>chunk-optimization: avoid unsafe dynamic-only merges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9398">#9398</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>cjs: rename CJS-wrapped locals that would shadow chunk-scope names
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9392">#9392</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev/lazy: watch lazy modules added in rebuilds (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9391">#9391</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>rolldown_dev: move dev example to break publish cycle (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9465">#9465</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>binding: drop unsafe napi string helper, hoist transform ArcStr (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9456">#9456</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>ecmascript_utils: split rewrite_ident_reference off JsxExt trait (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9417">#9417</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>use <code>ThreadsafeFunction::call_async_catch</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9390">#9390</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>devtools: document <code>@​rolldown/debug</code> usage and package
graph consumption (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9435">#9435</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>replace <code>Inter</code> with system font stack in OG template SVG
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9240">#9240</a>)
by <a
href="https://github.com/yvbopeng"><code>@​yvbopeng</code></a></li>
<li>remove <code>output.comments</code> warning as all issues have been
resolved (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9393">#9393</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>in-depth: clarify @<strong>PURE</strong> scope and document
positions (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9389">#9389</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>readme: remove release candidate notice (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9387">#9387</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>vite-resolve: cache importer existence checks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9443">#9443</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
<li>binding: reduce plugin string clones (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9436">#9436</a>)
by <a
href="https://github.com/Brooooooklyn"><code>@​Brooooooklyn</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f2757ed768"><code>f2757ed</code></a>
release: v1.0.2 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9478">#9478</a>)</li>
<li><a
href="b88de34520"><code>b88de34</code></a>
docs(devtools): document <code>@​rolldown/debug</code> usage and package
graph consumption ...</li>
<li><a
href="0f811f4d79"><code>0f811f4</code></a>
fix(hash): keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)</li>
<li><a
href="ed010fad05"><code>ed010fa</code></a>
feat(devtools): emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)</li>
<li><a
href="2b0bb7ffe7"><code>2b0bb7f</code></a>
feat(devtools): classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)</li>
<li><a
href="ff6fff24da"><code>ff6fff2</code></a>
fix: call <code>codeSplitting.groups[].name</code> in deterministic
order (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)</li>
<li><a
href="81fcc89b78"><code>81fcc89</code></a>
feat(devtools): map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)</li>
<li><a
href="98619ddbf8"><code>98619dd</code></a>
feat(devtools): mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)</li>
<li><a
href="0c7ac80de7"><code>0c7ac80</code></a>
feat(devtools): make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)</li>
<li><a
href="98e0620775"><code>98e0620</code></a>
feat(devtools): emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/rolldown/rolldown/commits/v1.0.2/packages/rolldown">compare
view</a></li>
</ul>
</details>
<br />

Updates `@rolldown/binding-darwin-x64` from 1.0.1 to 1.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">@​rolldown/binding-darwin-x64's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.2</h2>
<h2>[1.0.2] - 2026-05-20</h2>
<h3>🚀 Features</h3>
<ul>
<li>devtools: emit package size in PackageGraphReady (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9434">#9434</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: classify package dependency types (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9427">#9427</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: map packages to modules and chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9426">#9426</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: mark used packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9423">#9423</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: make duplicate packages discoverable (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9422">#9422</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>devtools: emit package metadata (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9421">#9421</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>update oxc to 0.132.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9449">#9449</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>update oxc to 0.131.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9424">#9424</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>allow checks.* to escalate emissions to hard errors (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9388">#9388</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>dev: support watcher options <code>include</code> and
<code>exclude</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9395">#9395</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>emit warnings for invalid pure annotations (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9381">#9381</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>hash: keep chunk file names stable when an unrelated entry is added
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9444">#9444</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>call <code>codeSplitting.groups[].name</code> in deterministic order
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9457">#9457</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>dev/lazy: make <code>resolve_id</code> idempotent when the resolved
id is already a lazy entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9439">#9439</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>chunk-optimization: publish absorbed dynamic-entry namespace
cross-chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9448">#9448</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>treeshake: propagate pure annotation through compound exprs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9431">#9431</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>finalizer: skip redundant init call when barrel executes in same
chunk (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9354">#9354</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>linking: initialize wrapped ESM re-export owners (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9353">#9353</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>do not inherit __toESM across chunks for named-only external imports
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9333">#9333</a>)
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9415">#9415</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>watcher: don't write output or emit events after close() (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9328">#9328</a>)
by <a
href="https://github.com/situ2001"><code>@​situ2001</code></a></li>
<li>chunk-optimization: avoid unsafe dynamic-only merges (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9398">#9398</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>cjs: rename CJS-wrapped locals that would shadow chunk-scope names
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9392">#9392</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>dev/lazy: watch lazy modules added in rebuilds (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9391">#9391</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>rolldown_dev: move dev example to break publish cycle (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9465">#9465</a>)
by <a href="https://github.com/Boshen"><code>@​Boshen</code></a></li>
<li>binding: drop unsafe napi string helper, hoist transform ArcStr (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9456">#9456</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>ecmascript_utils: split rewrite_ident_reference off JsxExt trait (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9417">#9417</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>use <code>ThreadsafeFunction::call_async_catch</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9390">#9390</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>devtools: document <code>@​rolldown/debug</code> usage and package
graph consumption (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9435">#9435</a>)
by <a href="https://github.com/IWANABETHATGUY"><cod...

_Description has been truncated_

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-21 22:35:33 +02:00
dependabot[bot]
f66bc31ae4 feat(deps): bump the minor group with 13 updates (#689)
Bumps the minor group with 13 updates:

| Package | From | To |
| --- | --- | --- |
|
[@agentclientprotocol/sdk](https://github.com/agentclientprotocol/typescript-sdk)
| `0.22.0` | `0.22.1` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `25.8.0` | `25.9.0` |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/project-service](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/project-service)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/scope-manager](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/scope-manager)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/tsconfig-utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/tsconfig-utils)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/type-utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/type-utils)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/types](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/types)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/typescript-estree](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-estree)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils)
| `8.59.3` | `8.59.4` |
|
[@typescript-eslint/visitor-keys](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/visitor-keys)
| `8.59.3` | `8.59.4` |
| [qs](https://github.com/ljharb/qs) | `6.15.1` | `6.15.2` |

Updates `@agentclientprotocol/sdk` from 0.22.0 to 0.22.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/releases">@​agentclientprotocol/sdk's
releases</a>.</em></p>
<blockquote>
<h2>v0.22.1</h2>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.22.0...v0.22.1">0.22.1</a>
(2026-05-18)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Event ordering (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/153">#153</a>)
(<a
href="7b632266f0">7b63226</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/blob/main/CHANGELOG.md">@​agentclientprotocol/sdk's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.22.0...v0.22.1">0.22.1</a>
(2026-05-18)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Event ordering (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/153">#153</a>)
(<a
href="7b632266f0">7b63226</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="47da5c6657"><code>47da5c6</code></a>
chore(main): release 0.22.1 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/154">#154</a>)</li>
<li><a
href="7b632266f0"><code>7b63226</code></a>
fix: Event ordering (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/153">#153</a>)</li>
<li>See full diff in <a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.22.0...v0.22.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 25.8.0 to 25.9.0
<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 `@typescript-eslint/eslint-plugin` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/eslint-plugin's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md">@​typescript-eslint/eslint-plugin's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li><a
href="4302433ae6"><code>4302433</code></a>
fix(eslint-plugin): [no-floating-promises] stack overflow when using
recursiv...</li>
<li><a
href="10b79f1ba8"><code>10b79f1</code></a>
chore(deps): update dependency eslint to v10.4.0 (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12339">#12339</a>)</li>
<li><a
href="2a6765d962"><code>2a6765d</code></a>
chore: clenaup <code>getAwaitedType</code> from
<code>typescript.d.ts</code> (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12302">#12302</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/parser's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md">@​typescript-eslint/parser's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/project-service` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/project-service's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/project-service/CHANGELOG.md">@​typescript-eslint/project-service's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li><a
href="a98fa1fd0e"><code>a98fa1f</code></a>
fix(project-service): throw error cause in
<code>getParsedConfigFileFromTSServer</code> ...</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/project-service">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/scope-manager` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/scope-manager's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/scope-manager/CHANGELOG.md">@​typescript-eslint/scope-manager's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for scope-manager to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/scope-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/tsconfig-utils` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/tsconfig-utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/tsconfig-utils/CHANGELOG.md">@​typescript-eslint/tsconfig-utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for tsconfig-utils to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/tsconfig-utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/type-utils` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/type-utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/type-utils/CHANGELOG.md">@​typescript-eslint/type-utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for type-utils to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li><a
href="2a6765d962"><code>2a6765d</code></a>
chore: clenaup <code>getAwaitedType</code> from
<code>typescript.d.ts</code> (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/type-utils/issues/12302">#12302</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/type-utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/types` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/types's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/types/CHANGELOG.md">@​typescript-eslint/types's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for types to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/types">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/typescript-estree` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/typescript-estree's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-estree/CHANGELOG.md">@​typescript-eslint/typescript-estree's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for typescript-estree to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/typescript-estree">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/utils` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/utils's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md">@​typescript-eslint/utils's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for utils to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/utils">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/visitor-keys` from 8.59.3 to 8.59.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/visitor-keys's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.4</h2>
<h2>8.59.4 (2026-05-18)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-floating-promises] stack
overflow when using recursive types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12294">#12294</a>)</li>
<li><strong>project-service:</strong> throw error cause in
<code>getParsedConfigFileFromTSServer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12321">#12321</a>)</li>
<li><strong>typescript-eslint:</strong> export Compatible* types from
typescript-eslint to resolve pnpm TS error (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12340">#12340</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>lumir</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/visitor-keys/CHANGELOG.md">@​typescript-eslint/visitor-keys's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.4 (2026-05-18)</h2>
<p>This was a version bump only for visitor-keys to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.4">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ca6ca1431b"><code>ca6ca14</code></a>
chore(release): publish 8.59.4</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.4/packages/visitor-keys">compare
view</a></li>
</ul>
</details>
<br />

Updates `qs` from 6.15.1 to 6.15.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's
changelog</a>.</em></p>
<blockquote>
<h2><strong>6.15.2</strong></h2>
<ul>
<li>[Fix] <code>stringify</code>: skip null/undefined entries in
<code>arrayFormat: 'comma'</code> + <code>encodeValuesOnly</code>
instead of crashing in <code>encoder</code></li>
<li>[Fix] <code>stringify</code>: use configured <code>delimiter</code>
after <code>charsetSentinel</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/555">#555</a>)</li>
<li>[Fix] <code>stringify</code>: apply <code>formatter</code> to
encoded key under <code>strictNullHandling</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/554">#554</a>)</li>
<li>[Fix] <code>stringify</code>: skip null/undefined filter-array
entries instead of crashing in <code>encoder</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/551">#551</a>)</li>
<li>[Fix] <code>parse</code>: handle nested bracket groups and add
regression tests (<a
href="https://redirect.github.com/ljharb/qs/issues/530">#530</a>)</li>
<li>[readme] fix grammar (<a
href="https://redirect.github.com/ljharb/qs/issues/550">#550</a>)</li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li>[Tests] add regression tests for keys containing percent-encoded
bracket text</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9aca4076fe"><code>9aca407</code></a>
v6.15.2</li>
<li><a
href="5e33d33447"><code>5e33d33</code></a>
[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li><a
href="21f80b33e5"><code>21f80b3</code></a>
[Fix] <code>stringify</code>: skip null/undefined entries in
<code>arrayFormat: 'comma'</code> + `e...</li>
<li><a
href="a0a81ea207"><code>a0a81ea</code></a>
[Fix] <code>stringify</code>: use configured <code>delimiter</code>
after <code>charsetSentinel</code></li>
<li><a
href="e3062f78f5"><code>e3062f7</code></a>
[Fix] <code>stringify</code>: apply <code>formatter</code> to encoded
key under <code>strictNullHandling</code></li>
<li><a
href="0c180a40ad"><code>0c180a4</code></a>
[Fix] <code>stringify</code>: skip null/undefined filter-array entries
instead of crashi...</li>
<li><a
href="3a8b94aec1"><code>3a8b94a</code></a>
[Tests] add regression tests for keys containing percent-encoded bracket
text</li>
<li><a
href="96755abd35"><code>96755ab</code></a>
[readme] fix grammar</li>
<li><a
href="a419ce5bbf"><code>a419ce5</code></a>
[Fix] <code>parse</code>: handle nested bracket groups and add
regression tests</li>
<li>See full diff in <a
href="https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2">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>
2026-05-19 06:31:55 +02:00
acp-release-bot[bot]
5b2e147b5d chore(main): release 0.36.1 (#687)
🤖 I have created a release *beep* *boop*
---


##
[0.36.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.36.0...v0.36.1)
(2026-05-18)


### Bug Fixes

* flaky authentication bypass for gateway
([#686](https://github.com/agentclientprotocol/claude-agent-acp/issues/686))
([db852dc](db852dcf7c))

---
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-05-18 15:33:45 +02:00
Aleksandr Suhinin
db852dcf7c fix: flaky authentication bypass for gateway (#686)
Sometimes leads to auth required error.
2026-05-18 15:31:51 +02:00
acp-release-bot[bot]
505f9c13c0 chore(main): release 0.36.0 (#683)
🤖 I have created a release *beep* *boop*
---


##
[0.36.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.35.0...v0.36.0)
(2026-05-18)


### Features

* Add experimental session delete support
([#682](https://github.com/agentclientprotocol/claude-agent-acp/issues/682))
([2162d5a](2162d5af62))
* Support experimental additionalDirectories field
([#684](https://github.com/agentclientprotocol/claude-agent-acp/issues/684))
([f37e9a0](f37e9a0d47))

---
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-05-18 13:02:28 +02:00
Ben Brandt
f37e9a0d47 feat: Support experimental additionalDirectories field (#684) 2026-05-18 10:51:45 +00:00
Ben Brandt
2162d5af62 feat: Add experimental session delete support (#682)
- **Update Agent Client Protocol SDK to 0.22.0**
- **feat: Add session delete support**
2026-05-18 10:43:07 +00:00
Ben Brandt
798844726f chore: tighten local-command-stdout handler comment and pass full options (#678)
## Summary

Follow-up cleanup to #649. Two small changes:

- **Tighten the comment block** above the marker-strip branch in
`acp-agent.ts:1119`. The original 15-line block restated history that
already lives in the PR description; replaced with a 5-line "what this
does, and why the no-op case is preserved" comment that points at the
replay path for parallel context.
- **Forward `clientCapabilities`, `parentToolUseId`, `cwd`, and
`taskState`** to `toAcpNotifications` so the new strip-and-render call
matches the sibling call below it at `acp-agent.ts:1192`. Without these
the rendered output loses threading under a wrapping tool use and skips
terminal-output detection.
- **Only log the raw payload when nothing renders** — previously every
marker-containing message was logged AND rendered. Now we render the
stripped prose if anything remains, and fall back to logging only the
pure-marker case (e.g. `/compact`).

## Test plan

- [x] `npm run check` (lint + format) — passes
- [x] `npm run build` (tsc) — passes
- [x] `stripLocalCommandMetadata` unit tests — 8 passed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:45:33 +02:00
acp-release-bot[bot]
f6e12d425e chore(main): release 0.35.0 (#670)
🤖 I have created a release *beep* *boop*
---


##
[0.35.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.34.1...v0.35.0)
(2026-05-16)


### Features

* **deps:** bump actions/create-github-app-token from 3.1.1 to 3.2.0
([#669](https://github.com/agentclientprotocol/claude-agent-acp/issues/669))
([47df1f2](47df1f2a0b))
* **deps:** bump hono from 4.12.18 to 4.12.19 in the minor group
([#671](https://github.com/agentclientprotocol/claude-agent-acp/issues/671))
([5d7165d](5d7165d961))
* **deps:** update to claude-agent-sdk 0.3.143
([#664](https://github.com/agentclientprotocol/claude-agent-acp/issues/664))
([27ca2e5](27ca2e5d40))
* Use SDK settings resolution for defaults
([#677](https://github.com/agentclientprotocol/claude-agent-acp/issues/677))
([eb1259c](eb1259cf88))


### Bug Fixes

* Add task hooks for plan state updates
([#676](https://github.com/agentclientprotocol/claude-agent-acp/issues/676))
([5ff7d50](5ff7d50f0e))
* render local-command-stdout messages instead of dropping them
([#649](https://github.com/agentclientprotocol/claude-agent-acp/issues/649))
([3b9b7d5](3b9b7d5a56))

---
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-05-16 23:41:18 +02:00
Wandering Consciousness
3b9b7d5a56 fix: render local-command-stdout messages instead of dropping them (#649)
AI disclaimer: I identified this bug after noticing that custom skills
produced no output within the Zed Thread/Claude Agent UI. I used Claude
to investigate the issue and develop the fix. I have manually verified
the equivalent fix against an installed
`@zed-industries/claude-code-acp@0.16.1` (the deprecated package Zed
currently ships): custom slash commands that previously produced empty
threads now render the model's response correctly after Zed restart.

## Summary

The live `case "user" / "assistant"` handler in `src/acp-agent.ts` drops
every SDK message whose string content contains
`<local-command-stdout>`. The branch was added to tolerate `/compact`'s
malformed output, but its side-effect is silencing every successful
slash command invocation — custom user-defined skills (whose expanded
bodies arrive wrapped in `<command-*>` + `<local-command-stdout>`
markers) and built-in commands that emit textual output through these
tags.

## Symptom in ACP clients

User types `/some-command` (custom skill, or one of the affected
built-ins). Thread renders the user's `/some-command` bubble at the top,
then nothing — no expanded skill body, no model response. Under the hood
the model has actually received the expansion and produced a turn, but
the user message carrying the expansion never reaches the UI, and the
cascade of subsequent assistant content gets visually decoupled.

In CLI Claude Code the same expansion is printed to the terminal, so the
asymmetry is purely client-side.

## Fix

This repo already ships `stripLocalCommandMetadata` (and its underlying
`stripMarkerTags` helper) — they return `null` for marker-only payloads
and the stripped prose otherwise. They are already wired into session
replay/load (`acp-agent.ts:1385`) but not into the live handler.

The change wires the same helper into the live handler: strip the
markers and route what remains through the existing `toAcpNotifications`
path, preserving the message's role. Pure-marker payloads (e.g.
`/compact`'s) still no-op via the `null` branch, so the existing
`/compact works` integration test in `src/tests/acp-agent.test.ts`
continues to pass.

## Why the previous behaviour was conservative-but-wrong

The inline comment notes the filter exists for `/compact`'s SDK-side
malformed output. That tradeoff swallows a much larger class of
legitimate output (anything that uses `<local-command-stdout>` to wrap
real prose) to avoid one specific command's noise. With
`stripLocalCommandMetadata` available, the conservative behaviour can
target exactly the empty-after-strip case (still `null` → no-op) without
affecting messages that have real prose alongside markers — which is
exactly the regression case the helper's tests were written to cover
(see `src/tests/acp-agent.test.ts:1145-1163`, *"in the original bug
report the entire /model preamble and the user's real 'hi' prompt were
concatenated into a single message. We want to strip the marker tags and
preserve the real prose, not drop the whole message."*).

The fix applies that same principle to the live handler, where the
helper is most needed.

## Refs

- `zed-industries/claude-code-acp#624` — "'Skill' Session Updates
missing when invoked via slash command". The reporter correctly
diagnoses the mechanism: *"Claude just attaches the skill directly to
context when the /command is used, which means the skill loader isn't
used."* The skill body arrives wrapped in markers and gets dropped by
this filter.
- `zed-industries/claude-code-acp#642` — built-in slash commands
(`/usage`, `/status`, `/model`, `/memory`, `/permissions`, `/agents`,
`/mcp`) emit no output when invoked. Same root cause: their output
arrives in messages tagged with `<local-command-stdout>` and is filtered
out.

Both upstream issues remain open against the old repo
`zed-industries/claude-code-acp` and have no comments / linked PRs yet.
Cross-linking here so this PR can be the closing reference for both.

## Test plan

- [x] `npm run check` (lint + format) — passes
- [x] `npm run build` (tsc) — passes
- [x] `npx vitest run src/tests/acp-agent.test.ts -t
"stripLocalCommandMetadata"` — 8 passed
- [ ] Existing `/compact works` integration test — needs a CLI
environment to run; relies on the `null` branch preserving the no-op
behaviour for marker-only payloads, which the helper tests already cover

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-16 21:31:05 +00:00
Ben Brandt
eb1259cf88 feat: Use SDK settings resolution for defaults (#677) 2026-05-16 23:08:50 +02:00
Ben Brandt
5ff7d50f0e fix: Add task hooks for plan state updates (#676) 2026-05-16 21:05:27 +00:00
dependabot[bot]
47df1f2a0b feat(deps): bump actions/create-github-app-token from 3.1.1 to 3.2.0 (#669)
Bumps
[actions/create-github-app-token](https://github.com/actions/create-github-app-token)
from 3.1.1 to 3.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/releases">actions/create-github-app-token's
releases</a>.</em></p>
<blockquote>
<h2>v3.2.0</h2>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0">3.2.0</a>
(2026-05-12)</h2>
<h3>Features</h3>
<ul>
<li>add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)
(<a
href="952a2a7073">952a2a7</a>)</li>
<li>support full repository names in <code>repositories</code> input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)
(<a
href="85eb8dd414">85eb8dd</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump <code>@​actions/core</code> from 3.0.0
to 3.0.1 in the production-dependencies group (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/364">#364</a>)
(<a
href="43e5c345bf">43e5c34</a>)</li>
<li>validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)
(<a
href="f24bbd8964">f24bbd8</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md">actions/create-github-app-token's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0">3.2.0</a>
(2026-05-12)</h2>
<h3>Features</h3>
<ul>
<li>add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)
(<a
href="952a2a7073">952a2a7</a>)</li>
<li>support full repository names in <code>repositories</code> input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)
(<a
href="85eb8dd414">85eb8dd</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump <code>@​actions/core</code> from 3.0.0
to 3.0.1 in the production-dependencies group (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/364">#364</a>)
(<a
href="43e5c345bf">43e5c34</a>)</li>
<li>validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)
(<a
href="f24bbd8964">f24bbd8</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bcd2ba4921"><code>bcd2ba4</code></a>
chore(main): release 3.2.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/370">#370</a>)</li>
<li><a
href="f24bbd8964"><code>f24bbd8</code></a>
fix: validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)</li>
<li><a
href="363531b6d9"><code>363531b</code></a>
docs: capitalize Git as a proper noun in README (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/374">#374</a>)</li>
<li><a
href="fd2801133e"><code>fd28011</code></a>
docs: update procedure to configure Git (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/287">#287</a>)</li>
<li><a
href="85eb8dd414"><code>85eb8dd</code></a>
feat: support full repository names in <code>repositories</code> input
(<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)</li>
<li><a
href="c9aabb8372"><code>c9aabb8</code></a>
build(deps-dev): bump yaml from 2.8.3 to 2.8.4 in the
development-dependencie...</li>
<li><a
href="e02e816e55"><code>e02e816</code></a>
build(deps-dev): bump undici from 7.24.6 to 8.2.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/366">#366</a>)</li>
<li><a
href="8d835bfd37"><code>8d835bf</code></a>
build(deps-dev): bump esbuild from 0.27.4 to 0.28.0 in the
development-depend...</li>
<li><a
href="952a2a7073"><code>952a2a7</code></a>
feat: add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)</li>
<li><a
href="43e5c345bf"><code>43e5c34</code></a>
fix(deps): bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in the
production-dependenc...</li>
<li>Additional commits viewable in <a
href="1b10c78c78...bcd2ba4921">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/create-github-app-token&package-manager=github_actions&previous-version=3.1.1&new-version=3.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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 this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-16 22:26:39 +02:00
dependabot[bot]
5d7165d961 feat(deps): bump hono from 4.12.18 to 4.12.19 in the minor group (#671)
Bumps the minor group with 1 update:
[hono](https://github.com/honojs/hono).

Updates `hono` from 4.12.18 to 4.12.19
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/honojs/hono/releases">hono's
releases</a>.</em></p>
<blockquote>
<h2>v4.12.19</h2>
<h2>What's Changed</h2>
<ul>
<li>ci: pin GitHub Actions to SHAs by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4932">honojs/hono#4932</a></li>
<li>fix(serveStatic): make options parameter optional in all adapters by
<a href="https://github.com/mixelburg"><code>@​mixelburg</code></a> in
<a
href="https://redirect.github.com/honojs/hono/pull/4934">honojs/hono#4934</a></li>
<li>fix(cookie): return the first cookie when there are multiple cookies
with the same name by <a
href="https://github.com/usualoma"><code>@​usualoma</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4922">honojs/hono#4922</a></li>
<li>feat(bearer-auth): make bearerAuth generic for typed context in
verifyToken by <a
href="https://github.com/justinnais"><code>@​justinnais</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4913">honojs/hono#4913</a></li>
<li>feat(cache): key cache entries by configured vary headers by <a
href="https://github.com/usualoma"><code>@​usualoma</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4915">honojs/hono#4915</a></li>
<li>feat(request): add <code>bytes()</code> by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4921">honojs/hono#4921</a></li>
<li>fix(stream): upgrade <code>@hono/node-server</code> to v2 and fix
abort handling by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/hono/pull/4940">honojs/hono#4940</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/justinnais"><code>@​justinnais</code></a> made
their first contribution in <a
href="https://redirect.github.com/honojs/hono/pull/4913">honojs/hono#4913</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/hono/compare/v4.12.18...v4.12.19">https://github.com/honojs/hono/compare/v4.12.18...v4.12.19</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7e62bcd22f"><code>7e62bcd</code></a>
4.12.19</li>
<li><a
href="e2f252a874"><code>e2f252a</code></a>
fix(stream): upgrade <code>@hono/node-server</code> to v2 and fix abort
handling (<a
href="https://redirect.github.com/honojs/hono/issues/4940">#4940</a>)</li>
<li><a
href="54f2f0cfda"><code>54f2f0c</code></a>
feat(request): add <code>bytes()</code> (<a
href="https://redirect.github.com/honojs/hono/issues/4921">#4921</a>)</li>
<li><a
href="e59db594fb"><code>e59db59</code></a>
feat(cache): key cache entries by configured vary headers (<a
href="https://redirect.github.com/honojs/hono/issues/4915">#4915</a>)</li>
<li><a
href="48a7ccbc0e"><code>48a7ccb</code></a>
feat(bearer-auth): make bearerAuth generic for typed context in
verifyToken (...</li>
<li><a
href="ff7522fdcc"><code>ff7522f</code></a>
fix(cookie): return the first cookie when there are multiple cookies
with the...</li>
<li><a
href="26f8c33f23"><code>26f8c33</code></a>
fix(serveStatic): make options parameter optional in all adapters (<a
href="https://redirect.github.com/honojs/hono/issues/4934">#4934</a>)</li>
<li><a
href="16c4e3885f"><code>16c4e38</code></a>
ci: pin GitHub Actions to SHAs (<a
href="https://redirect.github.com/honojs/hono/issues/4932">#4932</a>)</li>
<li>See full diff in <a
href="https://github.com/honojs/hono/compare/v4.12.18...v4.12.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.12.18&new-version=4.12.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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>
2026-05-16 22:26:23 +02:00
dependabot[bot]
27ca2e5d40 feat(deps): update to claude-agent-sdk 0.3.143 (#664)
Bumps the minor group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
|
[@agentclientprotocol/sdk](https://github.com/agentclientprotocol/typescript-sdk)
| `0.21.0` | `0.21.1` |
|
[@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript)
| `0.2.132` | `0.3.143` |
|
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
| `25.6.0` | `25.8.0` |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.59.2` | `8.59.3` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.59.2` | `8.59.3` |
| [eslint](https://github.com/eslint/eslint) | `10.3.0` | `10.4.0` |
|
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)
| `4.1.5` | `4.1.6` |
| [brace-expansion](https://github.com/juliangruber/brace-expansion) |
`5.0.5` | `5.0.6` |
|
[express-rate-limit](https://github.com/express-rate-limit/express-rate-limit)
| `8.5.1` | `8.5.2` |
| [semver](https://github.com/npm/node-semver) | `7.7.4` | `7.8.0` |
| [type-is](https://github.com/jshttp/type-is) | `2.0.1` | `2.1.0` |


Updates `@agentclientprotocol/sdk` from 0.21.0 to 0.21.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/releases">@​agentclientprotocol/sdk's
releases</a>.</em></p>
<blockquote>
<h2>v0.21.1</h2>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.21.0...v0.21.1">0.21.1</a>
(2026-05-14)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>emit .js extensions in generated schema barrel for nodenext
consumers (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/146">#146</a>)
(<a
href="63b96db49d">63b96db</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/agentclientprotocol/typescript-sdk/blob/main/CHANGELOG.md">@​agentclientprotocol/sdk's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.21.0...v0.21.1">0.21.1</a>
(2026-05-14)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>emit .js extensions in generated schema barrel for nodenext
consumers (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/146">#146</a>)
(<a
href="63b96db49d">63b96db</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e0099180fe"><code>e009918</code></a>
chore(main): release 0.21.1 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/147">#147</a>)</li>
<li><a
href="63b96db49d"><code>63b96db</code></a>
fix: emit .js extensions in generated schema barrel for nodenext
consumers (#...</li>
<li><a
href="dfb5692fb0"><code>dfb5692</code></a>
chore(deps): bump crate-ci/typos from 1.46.0 to 1.46.1 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/144">#144</a>)</li>
<li><a
href="8ca90556b0"><code>8ca9055</code></a>
chore(deps): bump the minor group with 22 updates (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/145">#145</a>)</li>
<li><a
href="7355cdd139"><code>7355cdd</code></a>
chore(deps): bump crate-ci/typos from 1.45.1 to 1.46.0 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/140">#140</a>)</li>
<li><a
href="0b33b85eba"><code>0b33b85</code></a>
chore(deps): bump the minor group with 20 updates (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/141">#141</a>)</li>
<li><a
href="f017cfaf52"><code>f017cfa</code></a>
chore(deps-dev): bump nanoid from 3.3.11 to 3.3.12 (<a
href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/142">#142</a>)</li>
<li>See full diff in <a
href="https://github.com/agentclientprotocol/typescript-sdk/compare/v0.21.0...v0.21.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk` from 0.2.132 to 0.3.143
<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.143</h2>
<h2>What's changed</h2>
<ul>
<li><code>@anthropic-ai/sdk</code> and
<code>@modelcontextprotocol/sdk</code> are now
<code>peerDependencies</code> instead of <code>dependencies</code>.
Runtime is unaffected (both are bundled); npm/bun/pnpm auto-install
them. yarn classic users should add them explicitly for full TypeScript
type resolution</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.143
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.143
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.143
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.143
</code></pre>
<h2>v0.3.142</h2>
<h2>What's changed</h2>
<ul>
<li><strong>Breaking:</strong> Removed the v2 session API
(<code>unstable_v2_createSession</code>,
<code>unstable_v2_resumeSession</code>, <code>unstable_v2_prompt</code>,
<code>SDKSession</code>, <code>SDKSessionOptions</code>), deprecated
since 0.2.133. Use <code>query()</code> — pass an
<code>AsyncIterable&lt;SDKUserMessage&gt;</code> for multi-turn, or
<code>options.resume</code> to continue a session.</li>
<li><strong>Breaking:</strong> MCP servers now connect in the background
by default; sessions start immediately and slow servers report
<code>status: &quot;pending&quot;</code> in <code>init</code> until
ready. Set <code>MCP_CONNECTION_NONBLOCKING=0</code> to restore the old
behavior of waiting up to 5s before the first query, or mark a server
<code>alwaysLoad: true</code> to require it in turn 1.</li>
<li><strong>Breaking:</strong> Headless and SDK sessions now use Task
tools (<code>TaskCreate</code> / <code>TaskUpdate</code> /
<code>TaskGet</code> / <code>TaskList</code>) instead of
<code>TodoWrite</code>, deprecated since 0.2.136. Tool consumers should
accumulate by task ID instead of replacing a snapshot list.</li>
<li>Surfaced <code>request_id</code>, <code>subagent_type</code>, and
<code>task_description</code> on SDK message types and task system
events</li>
<li>Headless <code>--sdk-url</code> sessions now exit non-zero with a
stderr diagnostic when the remote transport closes permanently
(401/403/404 or WS permanent close), instead of silently exiting 0</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.3.142
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.142
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.142
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.142
</code></pre>
<h2>v0.2.141</h2>
<h2>What's changed</h2>
<ul>
<li><code>TaskCreateInput</code>, <code>TaskCreateOutput</code>,
<code>TaskGetInput</code>, <code>TaskGetOutput</code>,
<code>TaskUpdateInput</code>, <code>TaskUpdateOutput</code>,
<code>TaskListInput</code>, and <code>TaskListOutput</code> types are
now exported from <code>@anthropic-ai/claude-agent-sdk/sdk-tools</code>
and included in the
<code>ToolInputSchemas</code>/<code>ToolOutputSchemas</code> unions</li>
<li>Aligned <code>@anthropic-ai/sdk</code> dependency to ^0.93.0</li>
</ul>
<h2>Update</h2>
<pre lang="sh"><code>npm install @anthropic-ai/claude-agent-sdk@0.2.141
# or
yarn add @anthropic-ai/claude-agent-sdk@0.2.141
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</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.143</h2>
<ul>
<li><code>@anthropic-ai/sdk</code> and
<code>@modelcontextprotocol/sdk</code> are now
<code>peerDependencies</code> instead of <code>dependencies</code>.
Runtime is unaffected (both are bundled); npm/bun/pnpm auto-install
them. yarn classic users should add them explicitly for full TypeScript
type resolution</li>
</ul>
<h2>0.3.142</h2>
<ul>
<li><strong>Breaking:</strong> Removed the v2 session API
(<code>unstable_v2_createSession</code>,
<code>unstable_v2_resumeSession</code>, <code>unstable_v2_prompt</code>,
<code>SDKSession</code>, <code>SDKSessionOptions</code>), deprecated
since 0.2.133. Use <code>query()</code> — pass an
<code>AsyncIterable&lt;SDKUserMessage&gt;</code> for multi-turn, or
<code>options.resume</code> to continue a session.</li>
<li><strong>Breaking:</strong> MCP servers now connect in the background
by default; sessions start immediately and slow servers report
<code>status: &quot;pending&quot;</code> in <code>init</code> until
ready. Set <code>MCP_CONNECTION_NONBLOCKING=0</code> to restore the old
behavior of waiting up to 5s before the first query, or mark a server
<code>alwaysLoad: true</code> to require it in turn 1.</li>
<li><strong>Breaking:</strong> Headless and SDK sessions now use Task
tools (<code>TaskCreate</code> / <code>TaskUpdate</code> /
<code>TaskGet</code> / <code>TaskList</code>) instead of
<code>TodoWrite</code>, deprecated since 0.2.136. Tool consumers should
accumulate by task ID instead of replacing a snapshot list.</li>
<li>Surfaced <code>request_id</code>, <code>subagent_type</code>, and
<code>task_description</code> on SDK message types and task system
events</li>
<li>Headless <code>--sdk-url</code> sessions now exit non-zero with a
stderr diagnostic when the remote transport closes permanently
(401/403/404 or WS permanent close), instead of silently exiting 0</li>
</ul>
<h2>0.2.141</h2>
<ul>
<li><code>TaskCreateInput</code>, <code>TaskCreateOutput</code>,
<code>TaskGetInput</code>, <code>TaskGetOutput</code>,
<code>TaskUpdateInput</code>, <code>TaskUpdateOutput</code>,
<code>TaskListInput</code>, and <code>TaskListOutput</code> types are
now exported from <code>@anthropic-ai/claude-agent-sdk/sdk-tools</code>
and included in the
<code>ToolInputSchemas</code>/<code>ToolOutputSchemas</code> unions</li>
<li>Aligned <code>@anthropic-ai/sdk</code> dependency to ^0.93.0</li>
</ul>
<h2>0.2.140</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.140</li>
</ul>
<h2>0.2.139</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.139</li>
</ul>
<h2>0.2.138</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.138</li>
</ul>
<h2>0.2.137</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.137</li>
</ul>
<h2>0.2.136</h2>
<ul>
<li>Added <code>resolveSettings()</code> (alpha) to inspect effective
merged settings without spawning the Claude CLI; reads MDM
(plist/HKLM/HKCU) for parity with CLI startup</li>
<li>Deprecated <code>TodoWrite</code> tool — future versions will switch
to Task tools (<code>TaskCreate</code>, <code>TaskGet</code>,
<code>TaskUpdate</code>, <code>TaskList</code>)</li>
</ul>
<h2>0.2.135</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.135</li>
</ul>
<h2>0.2.134</h2>
<ul>
<li>Updated to parity with Claude Code v2.1.134</li>
</ul>
<h2>0.2.133</h2>
<ul>
<li>Deprecated the unstable V2 session API
(<code>unstable_v2_createSession</code> /
<code>unstable_v2_resumeSession</code> /
<code>unstable_v2_prompt</code>) — use <code>query()</code> instead</li>
<li>Deprecated passing <code>'Skill'</code> in <code>allowedTools</code>
— use the <code>skills</code> option instead</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fa5d004c65"><code>fa5d004</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="dcac1633cc"><code>dcac163</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="b6d0490791"><code>b6d0490</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="b14b7c97ff"><code>b14b7c9</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="ee3fe61081"><code>ee3fe61</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="1d9038531e"><code>1d90385</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="8d0a9588e6"><code>8d0a958</code></a>
Pin GitHub Actions to commit SHAs (<a
href="https://redirect.github.com/anthropics/claude-agent-sdk-typescript/issues/320">#320</a>)</li>
<li><a
href="3ccbe544fd"><code>3ccbe54</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="de7d6c1a35"><code>de7d6c1</code></a>
chore: Update CHANGELOG.md</li>
<li><a
href="1b3ab7bd01"><code>1b3ab7b</code></a>
chore: Update CHANGELOG.md</li>
<li>See full diff in <a
href="https://github.com/anthropics/claude-agent-sdk-typescript/compare/v0.2.132...v0.3.143">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/node` from 25.6.0 to 25.8.0
<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 `@typescript-eslint/eslint-plugin` from 8.59.2 to 8.59.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/eslint-plugin's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.3</h2>
<h2>8.59.3 (2026-05-11)</h2>
<p>This was a version bump only, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.3">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md">@​typescript-eslint/eslint-plugin's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.3 (2026-05-11)</h2>
<p>This was a version bump only for eslint-plugin to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.3">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="48e13c0261"><code>48e13c0</code></a>
chore(release): publish 8.59.3</li>
<li><a
href="e26dc8003a"><code>e26dc80</code></a>
docs: update stale links to latest (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12313">#12313</a>)</li>
<li><a
href="44f9625336"><code>44f9625</code></a>
chore(deps): update vitest monorepo to v4.1.5 (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12307">#12307</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.3/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.59.2 to 8.59.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/parser's
releases</a>.</em></p>
<blockquote>
<h2>v8.59.3</h2>
<h2>8.59.3 (2026-05-11)</h2>
<p>This was a version bump only, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.3">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md">@​typescript-eslint/parser's
changelog</a>.</em></p>
<blockquote>
<h2>8.59.3 (2026-05-11)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.3">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="48e13c0261"><code>48e13c0</code></a>
chore(release): publish 8.59.3</li>
<li><a
href="44f9625336"><code>44f9625</code></a>
chore(deps): update vitest monorepo to v4.1.5 (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser/issues/12307">#12307</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.3/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.3.0 to 10.4.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.4.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="1a45ec596a"><code>1a45ec5</code></a>
feat: check sequence expressions in <code>for-direction</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20701">#20701</a>)
(kuldeep kumar)</li>
<li><a
href="450040bd89"><code>450040b</code></a>
feat: add <code>includeIgnoreFile()</code> to <code>eslint/config</code>
(<a
href="https://redirect.github.com/eslint/eslint/issues/20735">#20735</a>)
(Kirk Waiblinger)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="544c0c3da5"><code>544c0c3</code></a>
fix: escape code path DOT labels in debug output (<a
href="https://redirect.github.com/eslint/eslint/issues/20866">#20866</a>)
(Pixel998)</li>
<li><a
href="6799431203"><code>6799431</code></a>
fix: update dependency <code>@​eslint/config-helpers</code> to ^0.6.0
(<a
href="https://redirect.github.com/eslint/eslint/issues/20850">#20850</a>)
(renovate[bot])</li>
<li><a
href="f078fef500"><code>f078fef</code></a>
fix: handle non-array deprecated rule replacements (<a
href="https://redirect.github.com/eslint/eslint/issues/20825">#20825</a>)
(xbinaryx)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="7e52a7151f"><code>7e52a71</code></a>
docs: add mention of <code>@eslint-react/eslint-plugin</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20869">#20869</a>)
(Pavel)</li>
<li><a
href="db3468ba74"><code>db3468b</code></a>
docs: tweak wording around ambiguous CJS-vs-ESM config (<a
href="https://redirect.github.com/eslint/eslint/issues/20865">#20865</a>)
(Kirk Waiblinger)</li>
<li><a
href="90846643ec"><code>9084664</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="9cc7387504"><code>9cc7387</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="3d7b548440"><code>3d7b548</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="191ec3c0a3"><code>191ec3c</code></a>
docs: Update README (GitHub Actions Bot)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="6616856f28"><code>6616856</code></a>
chore: upgrade knip to v6 (<a
href="https://redirect.github.com/eslint/eslint/issues/20875">#20875</a>)
(Pixel998)</li>
<li><a
href="d13b084a3a"><code>d13b084</code></a>
ci: ensure auto-created PRs run CI (<a
href="https://redirect.github.com/eslint/eslint/issues/20860">#20860</a>)
(lumir)</li>
<li><a
href="e71c7af86d"><code>e71c7af</code></a>
ci: bump pnpm/action-setup from 6.0.5 to 6.0.7 (<a
href="https://redirect.github.com/eslint/eslint/issues/20862">#20862</a>)
(dependabot[bot])</li>
<li><a
href="d84393dea1"><code>d84393d</code></a>
test: add unit tests for SuppressionsService.applySuppressions() (<a
href="https://redirect.github.com/eslint/eslint/issues/20863">#20863</a>)
(kuldeep kumar)</li>
<li><a
href="24db8cb8e6"><code>24db8cb</code></a>
test: add tests for SuppressionsService.save() (<a
href="https://redirect.github.com/eslint/eslint/issues/20802">#20802</a>)
(kuldeep kumar)</li>
<li><a
href="2ef0549cac"><code>2ef0549</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/20857">#20857</a>)
(github-actions[bot])</li>
<li><a
href="a4297918d2"><code>a429791</code></a>
ci: remove <code>eslint-webpack-plugin</code> types integration test (<a
href="https://redirect.github.com/eslint/eslint/issues/20668">#20668</a>)
(Milos Djermanovic)</li>
<li><a
href="9e37386aa7"><code>9e37386</code></a>
chore: replace <code>recast</code> with range approach in
code-sample-minimizer (<a
href="https://redirect.github.com/eslint/eslint/issues/20682">#20682</a>)
(Copilot)</li>
<li><a
href="0dd1f9ffc9"><code>0dd1f9f</code></a>
test: disable warning for
<code>vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20845">#20845</a>)
(Francesco Trotta)</li>
<li><a
href="9da3c7bc92"><code>9da3c7b</code></a>
refactor: remove deprecated <code>meta.language</code> and migrate
<code>meta.dialects</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20716">#20716</a>)
(Pixel998)</li>
<li><a
href="2099ed12a0"><code>2099ed1</code></a>
refactor: add <code>meta.defaultOptions</code> to more rules, enable
linting (<a
href="https://redirect.github.com/eslint/eslint/issues/20800">#20800</a>)
(xbinaryx)</li>
<li><a
href="f1dfbc9ca5"><code>f1dfbc9</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/20836">#20836</a>)
(github-actions[bot])</li>
<li><a
href="c75941390c"><code>c759413</code></a>
ci: bump pnpm/action-setup from 6.0.3 to 6.0.5 (<a
href="https://redirect.github.com/eslint/eslint/issues/20843">#20843</a>)
(dependabot[bot])</li>
<li><a
href="5b817d6fdc"><code>5b817d6</code></a>
test: add unit tests for lib/shared/ast-utils (<a
href="https://redirect.github.com/eslint/eslint/issues/20838">#20838</a>)
(kuldeep kumar)</li>
<li><a
href="1c13ae3934"><code>1c13ae3</code></a>
test: add unit tests for lib/shared/severity (<a
href="https://redirect.github.com/eslint/eslint/issues/20835">#20835</a>)
(kuldeep kumar)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="452c4010c0"><code>452c401</code></a>
10.4.0</li>
<li><a
href="b6417e8b55"><code>b6417e8</code></a>
Build: changelog update for 10.4.0</li>
<li><a
href="6616856f28"><code>6616856</code></a>
chore: upgrade knip to v6 (<a
href="https://redirect.github.com/eslint/eslint/issues/20875">#20875</a>)</li>
<li><a
href="d13b084a3a"><code>d13b084</code></a>
ci: ensure auto-created PRs run CI (<a
href="https://redirect.github.com/eslint/eslint/issues/20860">#20860</a>)</li>
<li><a
href="7e52a7151f"><code>7e52a71</code></a>
docs: add mention of <code>@eslint-react/eslint-plugin</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20869">#20869</a>)</li>
<li><a
href="e71c7af86d"><code>e71c7af</code></a>
ci: bump pnpm/action-setup from 6.0.5 to 6.0.7 (<a
href="https://redirect.github.com/eslint/eslint/issues/20862">#20862</a>)</li>
<li><a
href="544c0c3da5"><code>544c0c3</code></a>
fix: escape code path DOT labels in debug output (<a
href="https://redirect.github.com/eslint/eslint/issues/20866">#20866</a>)</li>
<li><a
href="db3468ba74"><code>db3468b</code></a>
docs: tweak wording around ambiguous CJS-vs-ESM config (<a
href="https://redirect.github.com/eslint/eslint/issues/20865">#20865</a>)</li>
<li><a
href="d84393dea1"><code>d84393d</code></a>
test: add unit tests for SuppressionsService.applySuppressions() (<a
href="https://redirect.github.com/eslint/eslint/issues/20863">#20863</a>)</li>
<li><a
href="90846643ec"><code>9084664</code></a>
docs: Update README</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.3.0...v10.4.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.1.5 to 4.1.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.6</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li><strong>browser</strong>: Provide project reference in
<code>ToMatchScreenshotResolvePath</code>  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> and <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10138">vitest-dev/vitest#10138</a>
<a href="https://github.com/vitest-dev/vitest/commit/31882607c"><!-- raw
HTML omitted -->(31882)<!-- raw HTML omitted --></a></li>
<li>Global <code>sequence.concurrent: true</code> with top-level
<code>test(..., { concurrent: false })</code> + depreacte
<code>sequential</code> test API and options  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a>,
<strong>Codex</strong> and <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10196">vitest-dev/vitest#10196</a>
<a href="https://github.com/vitest-dev/vitest/commit/2847dfa2a"><!-- raw
HTML omitted -->(2847d)<!-- raw HTML omitted --></a></li>
<li><strong>browser</strong>: Simplify orchestrator otel carrier  -  by
<a href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/10285">vitest-dev/vitest#10285</a>
<a href="https://github.com/vitest-dev/vitest/commit/18af98cee"><!-- raw
HTML omitted -->(18af9)<!-- raw HTML omitted --></a></li>
</ul>
<h3>   🏎 Performance</h3>
<ul>
<li>Stringify diff objects only once  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/10276">vitest-dev/vitest#10276</a>
<a href="https://github.com/vitest-dev/vitest/commit/9f7b1528c"><!-- raw
HTML omitted -->(9f7b1)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.1.5...v4.1.6">View
changes on GitHub</a></h5>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a8fd24c1ca"><code>a8fd24c</code></a>
chore: release v4.1.6</li>
<li><a
href="18af98cee1"><code>18af98c</code></a>
fix(browser): simplify orchestrator otel carrier (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/10285">#10285</a>)</li>
<li><a
href="31882607cc"><code>3188260</code></a>
feat(browser): provide project reference in
<code>ToMatchScreenshotResolvePath</code> (#...</li>
<li>See full diff in <a
href="https://github.com/vitest-dev/vitest/commits/v4.1.6/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />

Updates `@anthropic-ai/claude-agent-sdk-darwin-arm64` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-darwin-x64` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-linux-arm64-musl` from 0.2.132
to 0.3.143

Updates `@anthropic-ai/claude-agent-sdk-linux-x64` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-linux-x64-musl` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-win32-arm64` from 0.2.132 to
0.3.143

Updates `@anthropic-ai/claude-agent-sdk-win32-x64` from 0.2.132 to
0.3.143

Updates `@eslint/config-helpers` from 0.5.5 to 0.6.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/rewrite/releases">@​eslint/config-helpers's
releases</a>.</em></p>
<blockquote>
<h2>plugin-kit: v0.6.0</h2>
<h2><a
href="https://github.com/eslint/rewrite/compare/plugin-kit-v0.5.1...plugin-kit-v0.6.0">0.6.0</a>
(2026-01-29)</h2>
<h3>Features</h3>
<ul>
<li>add <code>CustomRuleVisitorWithExit</code> type to
<code>@eslint/plugin-kit</code> (<a
href="https://redirect.github.com/eslint/rewrite/issues/351">#351</a>)
(<a
href="e7d1be4066">e7d1be4</a>)</li>
<li>Add custom rule type helpers to <code>@eslint/plugin-kit</code> (<a
href="https://redirect.github.com/eslint/rewrite/issues/355">#355</a>)
(<a
href="8ac853046a">8ac8530</a>)</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​eslint/core</code> bumped from ^1.0.1 to ^1.1.0</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>config-helpers: v0.6.0</h2>
<h2><a
href="https://github.com/eslint/rewrite/compare/config-helpers-v0.5.5...config-helpers-v0.6.0">0.6.0</a>
(2026-05-08)</h2>
<h3>Features</h3>
<ul>
<li>Add new <code>includeIgnoreFile()</code> to config-helpers (<a
href="https://redirect.github.com/eslint/rewrite/issues/430">#430</a>)
(<a
href="9b51352902">9b51352</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/rewrite/blob/main/packages/config-helpers/CHANGELOG.md">@​eslint/config-helpers's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/eslint/rewrite/compare/config-helpers-v0.5.5...config-helpers-v0.6.0">0.6.0</a>
(2026-05-08)</h2>
<h3>Features</h3>
<ul>
<li>Add new <code>includeIgnoreFile()</code> to config-helpers (<a
href="https://redirect.github.com/eslint/rewrite/issues/430">#430</a>)
(<a
href="9b51352902">9b51352</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/eslint/rewrite/commits/core-v0.6.0/packages/config-helpers">compare
view</a></li>
</ul>
</details>
<br />

Updates `@oxc-project/types` from 0.128.0 to 0.130.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/oxc-project/oxc/releases">@​oxc-project/types's
releases</a>.</em></p>
<blockquote>
<h2>oxc crates_v0.130.0</h2>
<h3>🚀 Features</h3>
<ul>
<li>66c9b01 transformer/typescript: Debug_assert that
<code>enum_eval</code> ran in semantic (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22252">#22252</a>)
(Dunqing)</li>
<li>ffe6475 minifier: Fold <code>Array</code> constructor with safe
spreads (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22215">#22215</a>)
(camc314)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>d3d0b18 traverse: Handle
<code>ChainElement::TSNonNullExpression</code> in
<code>GatherNodeParts</code> (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22247">#22247</a>)
(leaysgur)</li>
<li>4e880de transformer/object-rest-spread: Declare temp vars for
computed keys (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22284">#22284</a>)
(camc314)</li>
<li>a7c3e22 semantic: Clear member write target for computed keys (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22302">#22302</a>)
(camc314)</li>
<li>6a8852d codegen: Emit newline after legal-comment orphan flush (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22304">#22304</a>)
(Dunqing)</li>
<li>5da9fda transformer/explicit-resource-management: Preserve class
names (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22306">#22306</a>)
(Dunqing)</li>
<li>b5d970f transformer/explicit-resource-management: Preserve class
names (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22290">#22290</a>)
(camc314)</li>
<li>bc54fd4 minifier: Keep function / class names if direct eval is
present in the scope (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22241">#22241</a>)
(sapphi-red)</li>
<li>7a810c0 minifier: Refresh direct eval flags after DCE (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21787">#21787</a>)
(Dunqing)</li>
<li>dd88726 transformer/legacy-decorator: Preserve accessor type
annotation for emitDecoratorMetadata (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21966">#21966</a>)
(Dunqing)</li>
<li>29a3cd7 codegen: Swap mapping/indent order for top-level decls (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22206">#22206</a>)
(Dunqing)</li>
<li>73b4f40 minifier: Preserve catch binding with direct eval (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22221">#22221</a>)
(camc314)</li>
<li>0e13d17 minifier: Preserve optional chain base side effects (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22219">#22219</a>)
(camc314)</li>
<li>0c7c01c transformer/typescript: Inline optional-chain enum member
access (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21834">#21834</a>)
(Dunqing)</li>
<li>a6aff7e codegen: Emit block/array/object end mapping at close char
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22200">#22200</a>)
(Dunqing)</li>
<li>a099b03 codegen: Emit call end mapping at <code>)</code> position,
not past it (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22199">#22199</a>)
(Dunqing)</li>
<li>5753774 minifier: Cap if-return ternary collapse for firefox (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21841">#21841</a>)
(Gurupungav Narayanan)</li>
<li>2493bdd codegen: Correct sourcemap end mappings for closing
delimiters (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22001">#22001</a>)
(Mark Dalgleish)</li>
<li>3b385e2 minifier: Bail optimizing <code>Array</code> with unknown
arg count (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22188">#22188</a>)
(camc314)</li>
<li>9fa2122 parser: Parse array computed class keys (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22159">#22159</a>)
(camc314)</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>a4a6892 napi/parser: Correct code comment (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22278">#22278</a>)
(overlookmotel)</li>
<li>9305373 oxc: Update README (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22178">#22178</a>)
(camc314)</li>
</ul>
<h2>oxc crates_v0.129.0</h2>
<h3>💥 BREAKING CHANGES</h3>
<ul>
<li>0ffbe0d allocator: [<strong>BREAKING</strong>] Remove
<code>Allocator::end_ptr</code> method (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21871">#21871</a>)
(overlookmotel)</li>
</ul>
<h3>🚀 Features</h3>
<ul>
<li>9593ec8 transformer/jsx: Add jsxDEV source metadata for fragments
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21932">#21932</a>)
(Ido Rosenthal)</li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>81e834c parser: Prevent multiple <code>Parser</code> instances in
binary (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22120">#22120</a>)
(overlookmotel)</li>
<li>429deac napi/parser: Export <code>visitorKeys</code> from
<code>wasm</code> entrypoint (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21996">#21996</a>)
(NullVoxPopuli)</li>
<li>e852911 codegen: Preserve legal comments orphaned by upstream passes
(<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21575">#21575</a>)
(Dunqing)</li>
<li>e3399ec transformer/class-properties: Preserve RHS in
logical-assignment to static private field (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21950">#21950</a>)
(Dunqing)</li>
<li>c59c199 transformer/typescript: Emit class fields for parameter
properties (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21831">#21831</a>)
(Dunqing)</li>
<li>aaabde4 parser: Attach legal comments to following token (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/21670">#21670</a>)
(Dunqing)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="650417ffd0"><code>650417f</code></a>
release(crates): oxc v0.130.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22316">#22316</a>)</li>
<li><a
href="ec00055e64"><code>ec00055</code></a>
release(crates): oxc v0.129.0 (<a
href="https://github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types/issues/22108">#22108</a>)</li>
<li>See full diff in <a
href="https://github.com/oxc-project/oxc/commits/crates_v0.130.0/npm/oxc-types">compare
view</a></li>
</ul>
</details>
<br />

Updates `@rolldown/binding-android-arm64` from 1.0.0-rc.18 to 1.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">@​rolldown/binding-android-arm64's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.1</h2>
<h2>[1.0.1] - 2026-05-13</h2>
<h3>🚀 Features</h3>
<ul>
<li>experimental/lazy-barrel: advice on oversized barrel modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9236">#9236</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>rolldown: inline optional-chain enum access (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9379">#9379</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>chunk-optimization: dedupe already-loaded dynamic deps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9305">#9305</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>binding: call moduleParsed hook in ParallelJsPlugin (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9318">#9318</a>)
by <a href="https://github.com/jaehafe"><code>@​jaehafe</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>transform: enable <code>enum_eval</code> for
<code>transformSync</code> and vite TS transform (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9325">#9325</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>error: remove severity prefix from diagnostic messages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9262">#9262</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>deps: pin pnpm to 10.23.0 to work around catalog mismatch on Netlify
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9364">#9364</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>ci: pin mimalloc-safe to 0.1.58 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9361">#9361</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev/lazy: fix exports of lazy requests in lazy chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9249">#9249</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>rolldown_plugin_vite_resolve: handle errors in
<code>resolveSubpathImports</code> callback (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9355">#9355</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>rolldown_plugin_lazy_compilation: use loadExports for fetched proxy
to preserve original export names (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9132">#9132</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>common: include offending index in HybridIndexVec panic message (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9296">#9296</a>)
by <a href="https://github.com/SAY-5"><code>@​SAY-5</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>ecmascript: extract semantic_builder_for_transform helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9326">#9326</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>test: extract reusable static-import-cycle helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9332">#9332</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>clarify scope of <code>topLevelVar</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9380">#9380</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>meta/design: add ast-mutation design doc (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9338">#9338</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>feat: add ai policy in contribution guide (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9315">#9315</a>)
by <a
href="https://github.com/mdong1909"><code>@​mdong1909</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>binding: enable mimalloc v3 to reduce idle memory (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9349">#9349</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>🧪 Testing</h3>
<ul>
<li>mcs: cover require() in <code>$initial</code> group (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9376">#9376</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>add regression for CJS facade chunk merge into entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9351">#9351</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>⚙️ Miscellaneous Tasks</h3>
<ul>
<li>switch prepare-release to manual dispatch with version input (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9383">#9383</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>migrate <code>@rolldown/pluginutils</code> to
<code>rolldown/plugins</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9317">#9317</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>deps: pin libmimalloc-sys2 to 0.1.54 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9372">#9372</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>replace <code>igorskyflyer/action-readfile</code> with
<code>cat</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9369">#9369</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>deps: update test262 submodule for tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9371">#9371</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
<li>use app token for test dep update PRs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9368">#9368</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>replace some actions with gh commands (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9367">#9367</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</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/binding-android-arm64's
changelog</a>.</em></p>
<blockquote>
<h2>[1.0.1] - 2026-05-13</h2>
<h3>🚀 Features</h3>
<ul>
<li>experimental/lazy-barrel: advice on oversized barrel modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9236">#9236</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>rolldown: inline optional-chain enum access (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9379">#9379</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>chunk-optimization: dedupe already-loaded dynamic deps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9305">#9305</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>binding: call moduleParsed hook in ParallelJsPlugin (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9318">#9318</a>)
by <a href="https://github.com/jaehafe"><code>@​jaehafe</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>transform: enable <code>enum_eval</code> for
<code>transformSync</code> and vite TS transform (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9325">#9325</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>error: remove severity prefix from diagnostic messages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9262">#9262</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>deps: pin pnpm to 10.23.0 to work around catalog mismatch on Netlify
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9364">#9364</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>ci: pin mimalloc-safe to 0.1.58 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9361">#9361</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev/lazy: fix exports of lazy requests in lazy chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9249">#9249</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>rolldown_plugin_vite_resolve: handle errors in
<code>resolveSubpathImports</code> callback (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9355">#9355</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>rolldown_plugin_lazy_compilation: use loadExports for fetched proxy
to preserve original export names (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9132">#9132</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>common: include offending index in HybridIndexVec panic message (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9296">#9296</a>)
by <a href="https://github.com/SAY-5"><code>@​SAY-5</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>ecmascript: extract semantic_builder_for_transform helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9326">#9326</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>test: extract reusable static-import-cycle helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9332">#9332</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>clarify scope of <code>topLevelVar</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9380">#9380</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>meta/design: add ast-mutation design doc (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9338">#9338</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>feat: add ai policy in contribution guide (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9315">#9315</a>)
by <a
href="https://github.com/mdong1909"><code>@​mdong1909</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>binding: enable mimalloc v3 to reduce idle memory (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9349">#9349</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>🧪 Testing</h3>
<ul>
<li>mcs: cover require() in <code>$initial</code> group (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9376">#9376</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>add regression for CJS facade chunk merge into entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9351">#9351</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>⚙️ Miscellaneous Tasks</h3>
<ul>
<li>switch prepare-release to manual dispatch with version input (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9383">#9383</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>migrate <code>@rolldown/pluginutils</code> to
<code>rolldown/plugins</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9317">#9317</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>deps: pin libmimalloc-sys2 to 0.1.54 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9372">#9372</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>replace <code>igorskyflyer/action-readfile</code> with
<code>cat</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9369">#9369</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>deps: update test262 submodule for tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9371">#9371</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
<li>use app token for test dep update PRs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9368">#9368</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>replace some actions with gh commands (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9367">#9367</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>replace action-semantic-pull-request with inline regex (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9366">#9366</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="27779450b5"><code>2777945</code></a>
release: v1.0.1 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9384">#9384</a>)</li>
<li><a
href="4e353804a3"><code>4e35380</code></a>
feat(experimental/lazy-barrel): advice on oversized barrel modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9236">#9236</a>)</li>
<li><a
href="83d2762658"><code>83d2762</code></a>
docs: clarify scope of <code>topLevelVar</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9380">#9380</a>)</li>
<li><a
href="9ba2e73935"><code>9ba2e73</code></a>
chore: migrate <code>@rolldown/pluginutils</code> to
<code>rolldown/plugins</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9317">#9317</a>)</li>
<li><a
href="ef5796f0ec"><code>ef5796f</code></a>
fix(transform): enable <code>enum_eval</code> for
<code>transformSync</code> and vite TS transform ...</li>
<li><a
href="1f5dce1dc8"><code>1f5dce1</code></a>
fix(error): remove severity prefix from diagnostic messages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9262">#9262</a>)</li>
<li><a
href="fd479191a9"><code>fd47919</code></a>
chore(deps): upgrade oxc to 0.130.0 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9360">#9360</a>)</li>
<li><a
href="4c121d30d1"><code>4c121d3</code></a>
chore(deps): update npm packages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9343">#9343</a>)</li>
<li><a
href="024de36969"><code>024de36</code></a>
fix(rolldown_plugin_vite_resolve): handle errors in
<code>resolveSubpathImports</code> c...</li>
<li><a
href="7e5855c336"><code>7e5855c</code></a>
feat(chunk-optimization): dedupe already-loaded dynamic deps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9305">#9305</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/rolldown/rolldown/commits/v1.0.1/packages/rolldown">compare
view</a></li>
</ul>
</details>
<br />

Updates `@rolldown/binding-darwin-arm64` from 1.0.0-rc.18 to 1.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rolldown/rolldown/releases">@​rolldown/binding-darwin-arm64's
releases</a>.</em></p>
<blockquote>
<h2>v1.0.1</h2>
<h2>[1.0.1] - 2026-05-13</h2>
<h3>🚀 Features</h3>
<ul>
<li>experimental/lazy-barrel: advice on oversized barrel modules (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9236">#9236</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>rolldown: inline optional-chain enum access (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9379">#9379</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>chunk-optimization: dedupe already-loaded dynamic deps (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9305">#9305</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>binding: call moduleParsed hook in ParallelJsPlugin (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9318">#9318</a>)
by <a href="https://github.com/jaehafe"><code>@​jaehafe</code></a></li>
</ul>
<h3>🐛 Bug Fixes</h3>
<ul>
<li>transform: enable <code>enum_eval</code> for
<code>transformSync</code> and vite TS transform (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9325">#9325</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>error: remove severity prefix from diagnostic messages (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9262">#9262</a>)
by <a
href="https://github.com/Kyujenius"><code>@​Kyujenius</code></a></li>
<li>deps: pin pnpm to 10.23.0 to work around catalog mismatch on Netlify
(<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9364">#9364</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>ci: pin mimalloc-safe to 0.1.58 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9361">#9361</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>dev/lazy: fix exports of lazy requests in lazy chunks (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9249">#9249</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>rolldown_plugin_vite_resolve: handle errors in
<code>resolveSubpathImports</code> callback (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9355">#9355</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>rolldown_plugin_lazy_compilation: use loadExports for fetched proxy
to preserve original export names (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9132">#9132</a>)
by <a href="https://github.com/h-a-n-a"><code>@​h-a-n-a</code></a></li>
<li>common: include offending index in HybridIndexVec panic message (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9296">#9296</a>)
by <a href="https://github.com/SAY-5"><code>@​SAY-5</code></a></li>
</ul>
<h3>🚜 Refactor</h3>
<ul>
<li>ecmascript: extract semantic_builder_for_transform helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9326">#9326</a>)
by <a href="https://github.com/Dunqing"><code>@​Dunqing</code></a></li>
<li>test: extract reusable static-import-cycle helper (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9332">#9332</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>clarify scope of <code>topLevelVar</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9380">#9380</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
<li>meta/design: add ast-mutation design doc (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9338">#9338</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>feat: add ai policy in contribution guide (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9315">#9315</a>)
by <a
href="https://github.com/mdong1909"><code>@​mdong1909</code></a></li>
</ul>
<h3>⚡ Performance</h3>
<ul>
<li>binding: enable mimalloc v3 to reduce idle memory (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9349">#9349</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
</ul>
<h3>🧪 Testing</h3>
<ul>
<li>mcs: cover require() in <code>$initial</code> group (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9376">#9376</a>)
by <a href="https://github.com/hyf0"><code>@​hyf0</code></a></li>
<li>add regression for CJS facade chunk merge into entry (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9351">#9351</a>)
by <a
href="https://github.com/IWANABETHATGUY"><code>@​IWANABETHATGUY</code></a></li>
</ul>
<h3>⚙️ Miscellaneous Tasks</h3>
<ul>
<li>switch prepare-release to manual dispatch with version input (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9383">#9383</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>migrate <code>@rolldown/pluginutils</code> to
<code>rolldown/plugins</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9317">#9317</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>deps: pin libmimalloc-sys2 to 0.1.54 (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9372">#9372</a>)
by <a
href="https://github.com/shulaoda"><code>@​shulaoda</code></a></li>
<li>replace <code>igorskyflyer/action-readfile</code> with
<code>cat</code> (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9369">#9369</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>deps: update test262 submodule for tests (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9371">#9371</a>)
by <a
href="https://github.com/rolldown-guard"><code>@​rolldown-guard</code></a>[bot]</li>
<li>use app token for test dep update PRs (<a
href="https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown/issues/9368">#9368</a>)
by <a
href="https://github.com/sapphi-red"><code>@​sapphi-red</code></a></li>
<li>replace some actions with gh c...

_Description has been truncated_

---------

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-05-16 22:13:53 +02:00
acp-release-bot[bot]
0db6e3d6f8 chore(main): release 0.34.1 (#668)
🤖 I have created a release *beep* *boop*
---


##
[0.34.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.34.0...v0.34.1)
(2026-05-16)


### Bug Fixes

* load credential error for bedrock gateway
([#667](https://github.com/agentclientprotocol/claude-agent-acp/issues/667))
([8d76be3](8d76be356a))

---
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-05-16 08:13:32 +02:00
Aleksandr Suhinin
8d76be356a fix: load credential error for bedrock gateway (#667)
Sometimes leads to long initialization and error if
AWS_BEARER_TOKEN_BEDROCK:
Could not load credentials from any providers
2026-05-16 08:11:04 +02:00
acp-release-bot[bot]
9258652169 chore(main): release 0.34.0 (#666)
🤖 I have created a release *beep* *boop*
---


##
[0.34.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.33.1...v0.34.0)
(2026-05-15)


### Features

* add bedrock gateway authentication
([#665](https://github.com/agentclientprotocol/claude-agent-acp/issues/665))
([002c63a](002c63a78b))

---
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-05-15 20:24:14 +02:00
Aleksandr Suhinin
002c63a78b feat: add bedrock gateway authentication (#665) 2026-05-15 20:20:49 +02:00
acp-release-bot[bot]
e0ea9d898a chore(main): release 0.33.1 (#638)
🤖 I have created a release *beep* *boop*
---


##
[0.33.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.33.0...v0.33.1)
(2026-05-07)


### Bug Fixes

* Honor availableModels settings allowlist
([#637](https://github.com/agentclientprotocol/claude-agent-acp/issues/637))
([867a3a0](867a3a0de2)),
closes
[#620](https://github.com/agentclientprotocol/claude-agent-acp/issues/620)

---
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-05-07 23:19:25 +02:00
Ben Brandt
867a3a0de2 fix: Honor availableModels settings allowlist (#637)
Merge and dedupe entries across settings sources, preserve the
default model, and pass user-specified model IDs through to setModel.

Closes #620
2026-05-07 21:15:04 +00:00
19 changed files with 3133 additions and 566 deletions

View File

@@ -11,13 +11,6 @@ updates:
interval: "daily"
allow:
- dependency-type: "all"
ignore:
# Pinned to match the peer range of @anthropic-ai/claude-agent-sdk;
# newer minors break type compatibility between the two installed copies.
- dependency-name: "@anthropic-ai/sdk"
update-types:
- "version-update:semver-minor"
- "version-update:semver-major"
groups:
minor:
update-types:

View File

@@ -16,7 +16,7 @@ jobs:
# Generating a GitHub token, so that PRs and tags created by
# the release-please-action can trigger actions workflows.
- name: Generate GitHub token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
id: generate-token
with:
# GitHub App ID secret name

View File

@@ -1,3 +1,3 @@
{
".": "0.33.0"
".": "0.37.0"
}

View File

@@ -1,5 +1,76 @@
# Changelog
## [0.37.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.36.1...v0.37.0) (2026-05-21)
### Features
* add 'Default' option to effort level config ([#701](https://github.com/agentclientprotocol/claude-agent-acp/issues/701)) ([9e259d1](https://github.com/agentclientprotocol/claude-agent-acp/commit/9e259d128aa58dbb8107e53362781ca4e8ee071e))
* **deps:** bump the claude-agent-sdk to 0.3.146 ([#700](https://github.com/agentclientprotocol/claude-agent-acp/issues/700)) ([72875b2](https://github.com/agentclientprotocol/claude-agent-acp/commit/72875b248c7b90a89638de2f1709eec9e2153e7e))
* **deps:** bump the minor group with 13 updates ([#689](https://github.com/agentclientprotocol/claude-agent-acp/issues/689)) ([f66bc31](https://github.com/agentclientprotocol/claude-agent-acp/commit/f66bc31ae4bea73b48eeeb7f1db90c7d69cc0dac))
* Emit tool calls for memory recall events ([#703](https://github.com/agentclientprotocol/claude-agent-acp/issues/703)) ([a0bfb98](https://github.com/agentclientprotocol/claude-agent-acp/commit/a0bfb98ba73eaf37e9c071e886c054f90a5d5629)), closes [#650](https://github.com/agentclientprotocol/claude-agent-acp/issues/650)
### Bug Fixes
* Avoid cross-version model alias matches ([#702](https://github.com/agentclientprotocol/claude-agent-acp/issues/702)) ([e1e1c69](https://github.com/agentclientprotocol/claude-agent-acp/commit/e1e1c69029ef08579c841e6fbdffb32a5b94df06))
* Avoid redundant initial model sync ([#704](https://github.com/agentclientprotocol/claude-agent-acp/issues/704)) ([b275f6f](https://github.com/agentclientprotocol/claude-agent-acp/commit/b275f6ff7f0f2e21adedbd8e63a6fc3d63cbbb8d)), closes [#646](https://github.com/agentclientprotocol/claude-agent-acp/issues/646)
* Don't expose /clear in commands ([#705](https://github.com/agentclientprotocol/claude-agent-acp/issues/705)) ([cfce130](https://github.com/agentclientprotocol/claude-agent-acp/commit/cfce1307076e93f43a3ed8cc0134f9bb14a0f2d6))
* emit "cancelled" instead of "end_turn" when the session was interrupted. ([#694](https://github.com/agentclientprotocol/claude-agent-acp/issues/694)) ([2414a6f](https://github.com/agentclientprotocol/claude-agent-acp/commit/2414a6f98bec5bf50f3a13af528aa38d5a0fc974))
* Recover prompt stream after a failed turn ([#706](https://github.com/agentclientprotocol/claude-agent-acp/issues/706)) ([2711f50](https://github.com/agentclientprotocol/claude-agent-acp/commit/2711f506d5799f0ae25160de311a4459ffb46c49)), closes [#654](https://github.com/agentclientprotocol/claude-agent-acp/issues/654)
## [0.36.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.36.0...v0.36.1) (2026-05-18)
### Bug Fixes
* flaky authentication bypass for gateway ([#686](https://github.com/agentclientprotocol/claude-agent-acp/issues/686)) ([db852dc](https://github.com/agentclientprotocol/claude-agent-acp/commit/db852dcf7c8e3b461fabb1bdcf0c80a27d1da77d))
## [0.36.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.35.0...v0.36.0) (2026-05-18)
### Features
* Add experimental session delete support ([#682](https://github.com/agentclientprotocol/claude-agent-acp/issues/682)) ([2162d5a](https://github.com/agentclientprotocol/claude-agent-acp/commit/2162d5af62d493a82381bd88bc8fc67d376e358b))
* Support experimental additionalDirectories field ([#684](https://github.com/agentclientprotocol/claude-agent-acp/issues/684)) ([f37e9a0](https://github.com/agentclientprotocol/claude-agent-acp/commit/f37e9a0d47d8d201d98b2177083664a6781895cc))
## [0.35.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.34.1...v0.35.0) (2026-05-16)
### Features
* **deps:** bump actions/create-github-app-token from 3.1.1 to 3.2.0 ([#669](https://github.com/agentclientprotocol/claude-agent-acp/issues/669)) ([47df1f2](https://github.com/agentclientprotocol/claude-agent-acp/commit/47df1f2a0be57ddb8873ae8e341f748471c7bc53))
* **deps:** bump hono from 4.12.18 to 4.12.19 in the minor group ([#671](https://github.com/agentclientprotocol/claude-agent-acp/issues/671)) ([5d7165d](https://github.com/agentclientprotocol/claude-agent-acp/commit/5d7165d96158da2ddf3087676cdb0a19d1ff57aa))
* **deps:** update to claude-agent-sdk 0.3.143 ([#664](https://github.com/agentclientprotocol/claude-agent-acp/issues/664)) ([27ca2e5](https://github.com/agentclientprotocol/claude-agent-acp/commit/27ca2e5d40917887671cc4c39a854fd8f92c01e6))
* Use SDK settings resolution for defaults ([#677](https://github.com/agentclientprotocol/claude-agent-acp/issues/677)) ([eb1259c](https://github.com/agentclientprotocol/claude-agent-acp/commit/eb1259cf88375ca3c20a219ab99f12f5488fb360))
### Bug Fixes
* Add task hooks for plan state updates ([#676](https://github.com/agentclientprotocol/claude-agent-acp/issues/676)) ([5ff7d50](https://github.com/agentclientprotocol/claude-agent-acp/commit/5ff7d50f0ec7f34a7d9d901223aed73e2dbcce68))
* render local-command-stdout messages instead of dropping them ([#649](https://github.com/agentclientprotocol/claude-agent-acp/issues/649)) ([3b9b7d5](https://github.com/agentclientprotocol/claude-agent-acp/commit/3b9b7d5a56defd925eb2038fa97b6484ad951587))
## [0.34.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.34.0...v0.34.1) (2026-05-16)
### Bug Fixes
* load credential error for bedrock gateway ([#667](https://github.com/agentclientprotocol/claude-agent-acp/issues/667)) ([8d76be3](https://github.com/agentclientprotocol/claude-agent-acp/commit/8d76be356a3e74c58a7c93c3c201cca6a317d784))
## [0.34.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.33.1...v0.34.0) (2026-05-15)
### Features
* add bedrock gateway authentication ([#665](https://github.com/agentclientprotocol/claude-agent-acp/issues/665)) ([002c63a](https://github.com/agentclientprotocol/claude-agent-acp/commit/002c63a78b844c168d3dfc744cf408362a6adf97))
## [0.33.1](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.33.0...v0.33.1) (2026-05-07)
### Bug Fixes
* Honor availableModels settings allowlist ([#637](https://github.com/agentclientprotocol/claude-agent-acp/issues/637)) ([867a3a0](https://github.com/agentclientprotocol/claude-agent-acp/commit/867a3a0de2a050592d79aa76d7dd3dd5b478162d)), closes [#620](https://github.com/agentclientprotocol/claude-agent-acp/issues/620)
## [0.33.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.32.0...v0.33.0) (2026-05-07)

703
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "0.33.0",
"version": "0.37.0",
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
@@ -60,22 +60,22 @@
"author": "Zed Industries",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "0.21.0",
"@anthropic-ai/claude-agent-sdk": "0.2.132",
"@agentclientprotocol/sdk": "0.22.1",
"@anthropic-ai/claude-agent-sdk": "0.3.146",
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.89.0",
"@anthropic-ai/sdk": "0.97.1",
"@eslint/js": "10.0.1",
"@types/node": "25.6.0",
"@typescript-eslint/eslint-plugin": "8.59.2",
"@typescript-eslint/parser": "8.59.2",
"eslint": "10.3.0",
"@types/node": "25.9.1",
"@typescript-eslint/eslint-plugin": "8.59.4",
"@typescript-eslint/parser": "8.59.4",
"eslint": "10.4.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.6.0",
"prettier": "3.8.3",
"ts-node": "10.9.2",
"typescript": "6.0.3",
"vitest": "4.1.5"
"vitest": "4.1.7"
}
}

View File

@@ -17,6 +17,7 @@ import {
ndJsonStream,
NewSessionRequest,
NewSessionResponse,
PermissionOption,
PromptRequest,
PromptResponse,
ReadTextFileRequest,
@@ -36,6 +37,8 @@ import {
SetSessionModeResponse,
CloseSessionRequest,
CloseSessionResponse,
DeleteSessionRequest,
DeleteSessionResponse,
TerminalHandle,
TerminalOutputResponse,
WriteTextFileRequest,
@@ -44,6 +47,7 @@ import {
} from "@agentclientprotocol/sdk";
import {
CanUseTool,
deleteSession,
getSessionMessages,
listSessions,
McpServerConfig,
@@ -69,10 +73,16 @@ import * as path from "node:path";
import packageJson from "../package.json" with { type: "json" };
import { SettingsManager } from "./settings.js";
import {
applyTaskCreate,
applyTaskUpdate,
ClaudePlanEntry,
createPostToolUseHook,
createTaskHook,
parseTaskCreateOutput,
planEntries,
registerHookCallback,
TaskState,
taskStateToPlanEntries,
toolInfoFromToolUse,
toolUpdateFromDiffToolResponse,
toolUpdateFromToolResult,
@@ -152,6 +162,32 @@ type Session = {
* DEFAULT_CONTEXT_WINDOW, refreshed from each result's modelUsage, and
* invalidated when the user switches the session's model. */
contextWindowSize: number;
/** Accumulated task list for the session, keyed by task ID. Task IDs are
* per-session, so this state must not be shared across sessions. */
taskState: TaskState;
/** Assistant message ids whose text/thinking content was already emitted as
* `agent_message_chunk` / `agent_thought_chunk` via `stream_event` partial
* messages. Used by the top-level `assistant` case to decide whether to
* skip text/thinking blocks (the default, when streaming worked) or fall
* back to emitting the assembled blocks (when the underlying SDK / model
* proxy never produced `content_block_delta` for them). */
textStreamedMessageIds: Set<string>;
/** Keys of assembled text/thinking blocks the fallback path has already
* forwarded this turn, used to dedup re-delivered blocks without dropping
* genuinely new ones. A single assistant message id is frequently
* delivered as several `assistant` messages, each carrying a *different*
* subset of content blocks (e.g. an empty `thinking` block, then the real
* `text` block); deduping at message-id granularity would emit the first
* block, flag the id, then silently drop the later text. Keying on the
* block content instead lets distinct blocks that share an id all through
* while still collapsing identical re-deliveries (incremental + consolidated
* messages, or the same message delivered twice). */
fallbackEmittedBlockKeys: Set<string>;
/** Id of the assistant message currently being streamed via `stream_event`
* partial messages, captured at `message_start` so subsequent
* `content_block_delta` events can record their parent into
* `textStreamedMessageIds`. */
currentStreamingMessageId?: string;
};
/** Compute a stable fingerprint of the session-defining params so we can
@@ -232,6 +268,8 @@ type GatewayAuthMeta = {
};
};
type GatewayAuthRequest = AuthenticateRequest & { _meta?: GatewayAuthMeta };
/**
* Extra metadata that the agent provides for each tool_call / tool_update update.
*/
@@ -476,7 +514,7 @@ export class ClaudeAcpAgent implements Agent {
backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
clientCapabilities?: ClientCapabilities;
logger: Logger;
gatewayAuthMeta?: GatewayAuthMeta;
gatewayAuthRequest?: GatewayAuthRequest;
constructor(client: AgentSideConnection, logger?: Logger) {
this.sessions = {};
@@ -503,6 +541,17 @@ export class ClaudeAcpAgent implements Agent {
},
};
const gatewayBedrockAuthMethod: AuthMethod = {
id: "gateway-bedrock",
name: "Custom model gateway",
description: "Use a custom gateway to authenticate and access models",
_meta: {
gateway: {
protocol: "bedrock",
},
},
};
const supportsTerminalAuth = request.clientCapabilities?.auth?.terminal === true;
const supportsMetaTerminalAuth = request.clientCapabilities?._meta?.["terminal-auth"] === true;
@@ -602,10 +651,12 @@ export class ClaudeAcpAgent implements Agent {
},
loadSession: true,
sessionCapabilities: {
additionalDirectories: {},
close: {},
delete: {},
fork: {},
list: {},
resume: {},
close: {},
},
},
agentInfo: {
@@ -613,7 +664,10 @@ export class ClaudeAcpAgent implements Agent {
title: "Claude Agent",
version: packageJson.version,
},
authMethods: [...terminalAuthMethods, ...(supportsGatewayAuth ? [gatewayAuthMethod] : [])],
authMethods: [
...terminalAuthMethods,
...(supportsGatewayAuth ? [gatewayAuthMethod, gatewayBedrockAuthMethod] : []),
],
};
}
@@ -634,6 +688,7 @@ export class ClaudeAcpAgent implements Agent {
{
cwd: params.cwd,
mcpServers: params.mcpServers ?? [],
additionalDirectories: params.additionalDirectories,
_meta: params._meta,
},
{
@@ -690,8 +745,8 @@ export class ClaudeAcpAgent implements Agent {
}
async authenticate(_params: AuthenticateRequest): Promise<void> {
if (_params.methodId === "gateway") {
this.gatewayAuthMeta = _params._meta as GatewayAuthMeta | undefined;
if (_params.methodId === "gateway" || _params.methodId === "gateway-bedrock") {
this.gatewayAuthRequest = _params as GatewayAuthRequest;
return;
}
throw new Error("Method not implemented.");
@@ -711,6 +766,14 @@ export class ClaudeAcpAgent implements Agent {
cachedWriteTokens: 0,
};
// Assistant message ids and fallback block keys are turn-scoped — clear
// before a new turn so the sets don't grow unboundedly over a long-lived
// session, and so that an id reused across turns (uncommon but possible)
// is re-evaluated.
session.textStreamedMessageIds.clear();
session.fallbackEmittedBlockKeys.clear();
session.currentStreamingMessageId = undefined;
let lastAssistantTotalUsage: number | null = null;
let lastAssistantUsage: UsageSnapshot | null = null;
let lastAssistantModel: string | null = null;
@@ -749,6 +812,7 @@ export class ClaudeAcpAgent implements Agent {
session.promptRunning = true;
let handedOff = false;
let errored = false;
let stopReason: StopReason = "end_turn";
try {
@@ -832,10 +896,53 @@ export class ClaudeAcpAgent implements Agent {
}
case "session_state_changed": {
if (message.state === "idle") {
if (session.cancelled) {
stopReason = "cancelled";
}
return { stopReason, usage: sessionUsage(session) };
}
break;
}
case "memory_recall": {
const isSynthesis = message.mode === "synthesize";
const locations = isSynthesis
? []
: message.memories.map((m) => ({ path: m.path }));
const content = isSynthesis
? message.memories
.filter(
(m): m is (typeof message.memories)[number] & { content: string } =>
typeof m.content === "string",
)
.map((m) => ({
type: "content" as const,
content: { type: "text" as const, text: m.content },
}))
: [];
const count = message.memories.length;
const title = isSynthesis
? "Recalled synthesized memory"
: `Recalled ${count} ${count === 1 ? "memory" : "memories"}`;
await this.client.sessionUpdate({
sessionId: message.session_id,
update: {
sessionUpdate: "tool_call",
toolCallId: message.uuid,
title,
kind: "read",
status: "completed",
...(locations.length > 0 && { locations }),
...(content.length > 0 && { content }),
_meta: {
claudeCode: {
toolName: "memory_recall",
toolResponse: { mode: message.mode },
},
} satisfies ToolUpdateMeta,
},
});
break;
}
case "hook_started":
case "hook_progress":
case "hook_response":
@@ -846,10 +953,10 @@ export class ClaudeAcpAgent implements Agent {
case "task_updated":
case "elicitation_complete":
case "plugin_install":
case "memory_recall":
case "notification":
case "api_retry":
case "mirror_error":
case "permission_denied":
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
break;
default:
@@ -980,6 +1087,31 @@ export class ClaudeAcpAgent implements Agent {
break;
}
case "stream_event": {
// Track which assistant message ids have produced streamed text /
// thinking deltas so the `assistant` case below can fall back to
// emitting the assembled content when streaming didn't (e.g. when
// the SDK is talking to a model proxy that never produces
// `content_block_delta` events for the second model call in a
// tool-use loop).
//
// This runs for every stream_event (not gated on
// `parent_tool_use_id === null` like the usage block below) because
// subagent assistant messages flow through the same content filter.
// `currentStreamingMessageId` is a single field, which is safe
// because the SDK serializes the event stream and `message_start`
// always precedes its own `content_block_delta`s, so the captured
// id is always the parent of the deltas that follow.
if (message.event.type === "message_start") {
session.currentStreamingMessageId = message.event.message.id;
} else if (
message.event.type === "content_block_delta" &&
(message.event.delta.type === "text_delta" ||
message.event.delta.type === "thinking_delta") &&
session.currentStreamingMessageId
) {
session.textStreamedMessageIds.add(session.currentStreamingMessageId);
}
if (
message.parent_tool_use_id === null &&
(message.event.type === "message_start" || message.event.type === "message_delta")
@@ -1040,6 +1172,7 @@ export class ClaudeAcpAgent implements Agent {
{
clientCapabilities: this.clientCapabilities,
cwd: session.cwd,
taskState: session.taskState,
},
)) {
await this.client.sessionUpdate(notification);
@@ -1088,13 +1221,36 @@ export class ClaudeAcpAgent implements Agent {
}
}
// Slash commands like /compact can generate invalid output... doesn't match
// their own docs: https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-slash-commands#%2Fcompact-compact-conversation-history
// Strip <command-*>/<local-command-stdout> markers and render any
// remaining prose. Skill bodies and built-in slash commands (e.g.
// /usage, /status, /model) arrive wrapped in these tags; pure-marker
// payloads (e.g. /compact's malformed output) strip to null and are
// skipped. Mirrors the replay path at replaySessionHistory.
if (
typeof message.message.content === "string" &&
message.message.content.includes("<local-command-stdout>")
) {
this.logger.log(message.message.content);
const stripped = stripLocalCommandMetadata(message.message.content);
if (typeof stripped === "string") {
for (const notification of toAcpNotifications(
stripped,
message.message.role,
params.sessionId,
this.toolUseCache,
this.client,
this.logger,
{
clientCapabilities: this.clientCapabilities,
parentToolUseId: message.parent_tool_use_id,
cwd: session.cwd,
taskState: session.taskState,
},
)) {
await this.client.sessionUpdate(notification);
}
} else {
this.logger.log(message.message.content);
}
break;
}
@@ -1127,14 +1283,77 @@ export class ClaudeAcpAgent implements Agent {
throw RequestError.authRequired();
}
// Text and thinking blocks are normally emitted from the
// `stream_event` case above as `content_block_delta` arrives,
// so the assembled message replays them here. We skip them in
// that case to avoid duplication. However, if streaming never
// produced any text/thinking delta for this assistant message
// (e.g. because the underlying model proxy returned the response
// as a single non-streamed block, which is common with OpenAI-
// compatible gateways translating to the Anthropic protocol), the
// delta path emitted nothing — filtering the assembled blocks out
// would drop the assistant's final text on the floor while still
// returning `stopReason: "end_turn"`. Detect that case via
// `textStreamedMessageIds` and emit the assembled blocks as a
// fallback.
//
// The fallback dedup must be per content block, not per message
// id: such gateways frequently deliver one assistant response as
// several `assistant` messages that *share a message id* but each
// carry a *different* block (e.g. an empty `thinking` block, then
// the real `text` block). Flagging the whole id after the first
// block would emit that block, then silently drop the real text
// arriving under the same id. So we key the dedup on the block
// contents, which lets distinct blocks through while still
// collapsing identical re-deliveries (an incremental block
// followed by a consolidated message, or the same message
// delivered twice).
const assistantMessageId =
message.type === "assistant" ? message.message.id : undefined;
const liveStreamed =
!!assistantMessageId && session.textStreamedMessageIds.has(assistantMessageId);
let emittedFallbackBlock = false;
const content =
message.type === "assistant"
? // Handled by stream events above
message.message.content.filter(
(item) => !["text", "thinking"].includes(item.type),
)
? message.message.content.filter((item) => {
if (item.type !== "text" && item.type !== "thinking") {
return true;
}
// Already delivered live via streaming deltas — drop the
// assembled copy to avoid duplication. Note this is
// all-or-nothing per message id: a single delta of either
// kind flags the id, so if a gateway streamed (say)
// thinking deltas but delivered text only in the assembled
// message under that same id, the assembled text is
// dropped here. That mixed mode hasn't been observed —
// gateways stream either everything or nothing — and it is
// not a regression (the old code dropped it unconditionally).
if (liveStreamed) {
return false;
}
// Fallback path: emit each assembled block once, keyed on
// its contents so re-delivered blocks dedup but distinct
// blocks sharing a message id are all forwarded.
const blockText = item.type === "text" ? item.text : item.thinking;
const key = `${assistantMessageId ?? "<unknown>"}\u0000${item.type}\u0000${blockText}`;
if (session.fallbackEmittedBlockKeys.has(key)) {
return false;
}
session.fallbackEmittedBlockKeys.add(key);
emittedFallbackBlock = true;
return true;
})
: message.message.content;
if (emittedFallbackBlock) {
this.logger.log(
`[claude-agent-acp] No streamed text/thinking deltas seen for ` +
`assistant message ${assistantMessageId ?? "<unknown>"}; ` +
`emitting assembled blocks as fallback.`,
);
}
for (const notification of toAcpNotifications(
content,
message.message.role,
@@ -1146,6 +1365,7 @@ export class ClaudeAcpAgent implements Agent {
clientCapabilities: this.clientCapabilities,
parentToolUseId: message.parent_tool_use_id,
cwd: session.cwd,
taskState: session.taskState,
},
)) {
await this.client.sessionUpdate(notification);
@@ -1165,6 +1385,34 @@ export class ClaudeAcpAgent implements Agent {
}
throw new Error("Session did not end in result");
} catch (error) {
errored = true;
// A failed turn typically leaves a trailing `session_state_changed: idle`
// (and possibly more) in the query iterator. If we don't drain it here,
// the next prompt's first `query.next()` consumes that stale idle and
// short-circuits to end_turn with zero usage
// Bounded so a misbehaving SDK can't hang the next prompt indefinitely.
try {
await session.query.interrupt();
const MAX_DRAIN = 100;
for (let i = 0; i < MAX_DRAIN; i++) {
const { value: m, done } = await session.query.next();
if (done || !m) break;
if (m.type === "system" && m.subtype === "session_state_changed" && m.state === "idle") {
break;
}
if (i === MAX_DRAIN - 1) {
this.logger.error(
`Session ${params.sessionId}: drained ${MAX_DRAIN} messages after error without observing idle`,
);
}
}
} catch (drainErr) {
this.logger.error(
`Session ${params.sessionId}: failed to drain query after prompt error:`,
drainErr,
);
}
if (error instanceof RequestError || !(error instanceof Error)) {
throw error;
}
@@ -1189,10 +1437,19 @@ export class ClaudeAcpAgent implements Agent {
} finally {
if (!handedOff) {
session.promptRunning = false;
// This usually should not happen, but in case the loop finishes
// without claude sending all message replays, we resolve the
// next pending prompt call to ensure no prompts get stuck.
if (session.pendingMessages.size > 0) {
if (errored) {
// The query stream was just drained — handing pending prompts off
// onto it would let them race with the recovery. Cancel them so
// each waiting prompt() returns stopReason: "cancelled" and the
// client can decide whether to retry.
for (const pending of session.pendingMessages.values()) {
pending.resolve(true);
}
session.pendingMessages.clear();
} else if (session.pendingMessages.size > 0) {
// This usually should not happen, but in case the loop finishes
// without claude sending all message replays, we resolve the
// next pending prompt call to ensure no prompts get stuck.
const next = [...session.pendingMessages.entries()].sort(
(a, b) => a[1].order - b[1].order,
)[0];
@@ -1245,6 +1502,16 @@ export class ClaudeAcpAgent implements Agent {
return {};
}
async unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
// Tear down any active in-memory state first so the on-disk file isn't
// recreated by an outstanding query writing to it.
if (this.sessions[params.sessionId]) {
await this.teardownSession(params.sessionId);
}
await deleteSession(params.sessionId);
return {};
}
async unstable_setSessionModel(
params: SetSessionModelRequest,
): Promise<SetSessionModelResponse | void> {
@@ -1399,6 +1666,7 @@ export class ClaudeAcpAgent implements Agent {
registerHooks: false,
clientCapabilities: this.clientCapabilities,
cwd: this.sessions[sessionId]?.cwd,
taskState: this.sessions[sessionId]?.taskState,
},
)) {
await this.client.sessionUpdate(notification);
@@ -1429,7 +1697,7 @@ export class ClaudeAcpAgent implements Agent {
}
if (toolName === "ExitPlanMode") {
const optionsAll = [
const optionsAll: PermissionOption[] = [
{ kind: "allow_always", name: 'Yes, and use "auto" mode', optionId: "auto" },
{
kind: "allow_always",
@@ -1673,7 +1941,7 @@ export class ClaudeAcpAgent implements Agent {
typeof newEffortOpt?.currentValue === "string" ? newEffortOpt.currentValue : undefined;
if (newEffort !== currentEffort) {
await session.query.applyFlagSettings({
effortLevel: newEffort as Settings["effortLevel"],
effortLevel: toSdkEffortLevel(newEffort),
});
}
@@ -1698,7 +1966,7 @@ export class ClaudeAcpAgent implements Agent {
);
if (configId === "effort") {
await session.query.applyFlagSettings({
effortLevel: value as Settings["effortLevel"],
effortLevel: toSdkEffortLevel(value),
});
}
}
@@ -1708,6 +1976,7 @@ export class ClaudeAcpAgent implements Agent {
sessionId: string;
cwd: string;
mcpServers?: NewSessionRequest["mcpServers"];
additionalDirectories?: NewSessionRequest["additionalDirectories"];
_meta?: NewSessionRequest["_meta"];
}): Promise<NewSessionResponse> {
const existingSession = this.sessions[params.sessionId];
@@ -1732,6 +2001,7 @@ export class ClaudeAcpAgent implements Agent {
{
cwd: params.cwd,
mcpServers: params.mcpServers ?? [],
additionalDirectories: params.additionalDirectories,
_meta: params._meta,
},
{
@@ -1781,7 +2051,7 @@ export class ClaudeAcpAgent implements Agent {
? Object.fromEntries(server.headers.map((e) => [e.name, e.value]))
: undefined,
};
} else {
} else if (!("type" in server)) {
// Stdio type MCP server (with or without explicit type field)
mcpServers[server.name] = {
type: "stdio",
@@ -1845,6 +2115,11 @@ export class ClaudeAcpAgent implements Agent {
const abortController = userProvidedOptions?.abortController || new AbortController();
// Per-session task state. Created here (rather than in the session record
// below) so the TaskCreated/TaskCompleted hook callbacks can close over
// the same Map that the streaming message handler will read from.
const taskState: TaskState = new Map();
const options: Options = {
systemPrompt,
settingSources: ["user", "project", "local"],
@@ -1864,7 +2139,7 @@ export class ClaudeAcpAgent implements Agent {
env: {
...process.env,
...userProvidedOptions?.env,
...createEnvForGateway(this.gatewayAuthMeta),
...createEnvForGateway(this.gatewayAuthRequest),
// Opt-in to session state events like when the agent is idle
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
},
@@ -1905,14 +2180,58 @@ export class ClaudeAcpAgent implements Agent {
],
},
],
TaskCreated: [
...(userProvidedOptions?.hooks?.TaskCreated || []),
{
hooks: [
createTaskHook({
taskState,
onChange: async () => {
await this.client.sessionUpdate({
sessionId,
update: {
sessionUpdate: "plan",
entries: taskStateToPlanEntries(taskState),
},
});
},
}),
],
},
],
TaskCompleted: [
...(userProvidedOptions?.hooks?.TaskCompleted || []),
{
hooks: [
createTaskHook({
taskState,
onChange: async () => {
await this.client.sessionUpdate({
sessionId,
update: {
sessionUpdate: "plan",
entries: taskStateToPlanEntries(taskState),
},
});
},
}),
],
},
],
},
...creationOpts,
abortController,
};
// Prefer the official ACP `additionalDirectories` field. Fall back to the
// legacy `_meta.additionalRoots` extension for clients that haven't been
// updated yet. Either source is merged with directories supplied via
// `_meta.claudeCode.options.additionalDirectories` (SDK pass-through).
const acpAdditionalDirectories =
params.additionalDirectories ?? sessionMeta?.additionalRoots ?? [];
options.additionalDirectories = [
...(userProvidedOptions?.additionalDirectories ?? []),
...(sessionMeta?.additionalRoots ?? []),
...acpAdditionalDirectories,
];
if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
@@ -1948,7 +2267,7 @@ export class ClaudeAcpAgent implements Agent {
if (
shouldHideClaudeAuth() &&
initializationResult.account.subscriptionType &&
!this.gatewayAuthMeta
!this.gatewayAuthRequest
) {
throw RequestError.authRequired(
undefined,
@@ -1956,8 +2275,19 @@ export class ClaudeAcpAgent implements Agent {
);
}
// Apply user's `availableModels` allowlist from settings.json before any
// downstream model handling. The SDK only enforces this allowlist in its
// own UI, not in `initializationResult.models`, so we filter here to keep
// configOptions, the current-model resolver, and the stored modelInfos
// consistent with what the user configured.
const settingsAvailableModels = settingsManager.getSettings().availableModels;
const allowedModels = Array.isArray(settingsAvailableModels)
? applyAvailableModelsAllowlist(initializationResult.models, settingsAvailableModels)
: initializationResult.models;
const models = await getAvailableModels(
q,
allowedModels,
initializationResult.models,
settingsManager,
this.logger,
@@ -1965,9 +2295,7 @@ export class ClaudeAcpAgent implements Agent {
// Gate `auto` (and future model-specific modes) on the resolved model's
// `ModelInfo`. See `buildAvailableModes` for the canonical SDK signal.
const currentModelInfo = initializationResult.models.find(
(m) => m.value === models.currentModelId,
);
const currentModelInfo = allowedModels.find((m) => m.value === models.currentModelId);
const availableModes = buildAvailableModes(currentModelInfo);
// Clamp `permissionMode` if the resolved session does not offer it. The
@@ -2008,13 +2336,17 @@ export class ClaudeAcpAgent implements Agent {
const configOptions = buildConfigOptions(
modes,
models,
initializationResult.models,
allowedModels,
settingsManager.getSettings().effortLevel,
);
// Apply the initial effort level to the SDK so it matches the UI default
const initialEffort = configOptions.find((o) => o.id === "effort");
if (initialEffort && typeof initialEffort.currentValue === "string") {
if (
initialEffort &&
typeof initialEffort.currentValue === "string" &&
initialEffort.currentValue !== "default"
) {
await q.applyFlagSettings({
effortLevel: initialEffort.currentValue as Settings["effortLevel"],
});
@@ -2035,7 +2367,7 @@ export class ClaudeAcpAgent implements Agent {
},
modes,
models,
modelInfos: initializationResult.models,
modelInfos: allowedModels,
configOptions,
promptRunning: false,
pendingMessages: new Map(),
@@ -2044,6 +2376,9 @@ export class ClaudeAcpAgent implements Agent {
emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
contextWindowSize:
inferContextWindowFromModel(models.currentModelId) ?? DEFAULT_CONTEXT_WINDOW,
taskState,
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
return {
@@ -2134,16 +2469,26 @@ function snapshotFromUsage(usage: {
};
}
function createEnvForGateway(gatewayMeta?: GatewayAuthMeta) {
if (!gatewayMeta) {
function createEnvForGateway(request?: GatewayAuthRequest) {
if (!request?._meta) {
return {};
}
const customHeaders = Object.entries(request._meta.gateway.headers)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
if (request.methodId === "gateway-bedrock") {
return {
CLAUDE_CODE_USE_BEDROCK: "1",
AWS_BEARER_TOKEN_BEDROCK: " ", // Must be non-empty to bypass pass configuration check
ANTHROPIC_BEDROCK_BASE_URL: request._meta.gateway.baseUrl,
ANTHROPIC_CUSTOM_HEADERS: customHeaders,
};
}
return {
ANTHROPIC_BASE_URL: gatewayMeta.gateway.baseUrl,
ANTHROPIC_CUSTOM_HEADERS: Object.entries(gatewayMeta.gateway.headers)
.map(([key, value]) => `${key}: ${value}`)
.join("\n"),
ANTHROPIC_AUTH_TOKEN: "", // Must be specified to bypass claude login requirement
ANTHROPIC_BASE_URL: request._meta.gateway.baseUrl,
ANTHROPIC_CUSTOM_HEADERS: customHeaders,
ANTHROPIC_AUTH_TOKEN: " ", // Must be specified to bypass claude login requirement
};
}
@@ -2199,6 +2544,16 @@ function buildAvailableModes(modelInfo: ModelInfo | undefined): SessionModeState
return modes;
}
// Translate a UI effort value into the flag-layer payload. The SDK
// shallow-merges `applyFlagSettings`, drops `undefined` during JSON transport,
// and only clears a key when an explicit `null` is sent — see
// `applyFlagSettings` in @anthropic-ai/claude-agent-sdk. Mapping both the
// `"default"` sentinel and `undefined` (effort option absent for the model) to
// `null` ensures any previously-applied flag is actually cleared.
function toSdkEffortLevel(value: string | undefined): Settings["effortLevel"] | null {
return value === undefined || value === "default" ? null : (value as Settings["effortLevel"]);
}
function buildConfigOptions(
modes: SessionModeState,
models: SessionModelState,
@@ -2241,25 +2596,20 @@ function buildConfigOptions(
: [];
if (supportedLevels.length > 0) {
const effortOptions = supportedLevels.map((level) => ({
value: level,
name: level
.split(/[_-]/)
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
.join(" "),
}));
const effortOptions = [
{ value: "default", name: "Default" },
...supportedLevels.map((level) => ({
value: level,
name: level
.split(/[_-]/)
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
.join(" "),
})),
];
// Keep the current level if valid, otherwise prefer xhigh (Claude Code's
// recommended default for capable models), then high (the API default).
const includes = (l: string) => (supportedLevels as string[]).includes(l);
const includes = (l: string) => l === "default" || (supportedLevels as string[]).includes(l);
const validEffort =
currentEffortLevel && includes(currentEffortLevel)
? currentEffortLevel
: includes("xhigh")
? "xhigh"
: includes("high")
? "high"
: supportedLevels[0];
currentEffortLevel && includes(currentEffortLevel) ? currentEffortLevel : "default";
options.push({
id: "effort",
@@ -2279,6 +2629,27 @@ function buildConfigOptions(
// but the SDK model list uses IDs like "claude-opus-4-6-1m".
const MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
// Captures a model family version such as `4-6` or `4.7` so we can keep
// `claude-opus-4-6` from being copied onto the SDK's `opus` alias when that
// alias currently resolves to a different family version (e.g. Opus 4.7).
const MODEL_FAMILY_VERSION_PATTERN = /\b(\d+)[-.](\d+)\b/;
function extractModelFamilyVersion(s: string): string | null {
const match = s.match(MODEL_FAMILY_VERSION_PATTERN);
return match ? `${match[1]}.${match[2]}` : null;
}
function modelVersionsCompatible(preference: string, candidate: ModelInfo): boolean {
const preferred = extractModelFamilyVersion(preference);
if (!preferred) return true;
const candidateVersion =
extractModelFamilyVersion(candidate.value) ??
extractModelFamilyVersion(candidate.displayName) ??
extractModelFamilyVersion(candidate.description);
if (!candidateVersion) return true;
return preferred === candidateVersion;
}
function tokenizeModelPreference(model: string): { tokens: string[]; contextHint?: string } {
const lower = model.trim().toLowerCase();
const contextHint = lower.match(MODEL_CONTEXT_HINT_PATTERN)?.[1]?.toLowerCase();
@@ -2325,6 +2696,7 @@ function resolveModelPreference(models: ModelInfo[], preference: string): ModelI
// Substring match
const includesMatch = models.find((model) => {
if (!modelVersionsCompatible(trimmed, model)) return false;
const value = model.value.toLowerCase();
const display = model.displayName.toLowerCase();
return value.includes(lower) || display.includes(lower) || lower.includes(value);
@@ -2338,6 +2710,7 @@ function resolveModelPreference(models: ModelInfo[], preference: string): ModelI
let bestMatch: ModelInfo | null = null;
let bestScore = 0;
for (const model of models) {
if (!modelVersionsCompatible(trimmed, model)) continue;
const score = scoreModelMatch(model, tokens, contextHint);
if (0 < score && (!bestMatch || bestScore < score)) {
bestMatch = model;
@@ -2364,15 +2737,63 @@ function resolveSettingsModel(
return resolveModelPreference(models, settingsModel);
}
/**
* Restrict the SDK's model list to the user's `availableModels` allowlist
* (already merged-and-deduped across settings sources by `SettingsManager`).
* The user's exact entries become the model IDs surfaced via configOptions
* and passed to `setModel`, which prevents Claude Code from silently
* substituting a date-pinned variant (e.g. `haiku` →
* `claude-haiku-4-5-20251001`) that the user may not have access to.
*
* Display info and capability flags are copied from the closest SDK match so
* the UI still renders sensible names and effort levels.
*
* Semantics from https://code.claude.com/docs/en/model-config#restrict-model-selection:
* - `undefined` is handled by the caller (no allowlist applied).
* - The Default option is unaffected by `availableModels` — it always remains
* available, even when the allowlist is `[]`.
*/
function applyAvailableModelsAllowlist(sdkModels: ModelInfo[], allowlist: string[]): ModelInfo[] {
// Default is always preserved per the docs. Synthesize one if the SDK
// didn't surface it so downstream code (e.g. `getAvailableModels` picking
// `models[0]` as a fallback) still has something to work with.
const defaultModel = sdkModels.find((m) => m.value === "default") ?? {
value: "default",
displayName: "Default",
description: "",
};
const result: ModelInfo[] = [defaultModel];
const seen = new Set<string>([defaultModel.value]);
const sdkModelsWithoutDefault = sdkModels.filter((m) => m.value !== "default");
for (const entry of allowlist) {
const trimmed = entry.trim();
if (!trimmed || seen.has(trimmed)) continue;
const sdkMatch = resolveModelPreference(sdkModelsWithoutDefault, trimmed);
if (sdkMatch) {
result.push({ ...sdkMatch, value: trimmed });
} else {
result.push({ value: trimmed, displayName: trimmed, description: "" });
}
seen.add(trimmed);
}
return result;
}
async function getAvailableModels(
query: Query,
models: ModelInfo[],
sdkModels: ModelInfo[],
settingsManager: SettingsManager,
logger: Logger,
): Promise<SessionModelState> {
const settings = settingsManager.getSettings();
let currentModel = models[0];
let resolvedFromInput: string | undefined;
// Model priority (highest to lowest):
// 1. ANTHROPIC_MODEL environment variable
@@ -2382,15 +2803,33 @@ async function getAvailableModels(
const match = resolveModelPreference(models, process.env.ANTHROPIC_MODEL);
if (match) {
currentModel = match;
resolvedFromInput = process.env.ANTHROPIC_MODEL;
}
} else {
} else if (typeof settings.model === "string") {
const match = resolveSettingsModel(models, settings.model, logger);
if (match) {
currentModel = match;
resolvedFromInput = settings.model;
}
}
await query.setModel(currentModel.value);
// 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.
// (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.
// 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);
if (!skipSetModel) {
await query.setModel(currentModel.value);
}
return {
availableModels: models.map((model) => ({
@@ -2404,6 +2843,7 @@ async function getAvailableModels(
function getAvailableSlashCommands(commands: SlashCommand[]): AvailableCommand[] {
const UNSUPPORTED_COMMANDS = [
"clear",
"cost",
"keybindings-help",
"login",
@@ -2547,8 +2987,10 @@ export function toAcpNotifications(
clientCapabilities?: ClientCapabilities;
parentToolUseId?: string | null;
cwd?: string;
taskState?: TaskState;
},
): SessionNotification[] {
const taskState = options?.taskState ?? new Map();
const registerHooks = options?.registerHooks !== false;
const supportsTerminalOutput = options?.clientCapabilities?._meta?.["terminal_output"] === true;
if (typeof content === "string") {
@@ -2622,6 +3064,15 @@ export function toAcpNotifications(
entries: planEntries(chunk.input as { todos: ClaudePlanEntry[] }),
};
}
} else if (
chunk.name === "TaskCreate" ||
chunk.name === "TaskUpdate" ||
chunk.name === "TaskList" ||
chunk.name === "TaskGet"
) {
// Task* tool_use is suppressed; the plan update is emitted at
// tool_result time once we have the task ID (for TaskCreate) and
// confirmation that the change took effect.
} else {
// Only register hooks on first encounter to avoid double-firing
if (registerHooks && !alreadyCached) {
@@ -2726,7 +3177,36 @@ export function toAcpNotifications(
break;
}
if (toolUse.name !== "TodoWrite") {
if (
toolUse.name === "TaskCreate" ||
toolUse.name === "TaskUpdate" ||
toolUse.name === "TaskList" ||
toolUse.name === "TaskGet"
) {
// Headless/SDK sessions emit Task* tools instead of TodoWrite.
// TaskCreate / TaskUpdate mutate the accumulated task list; TaskList
// and TaskGet are read-only so we just suppress their tool_call /
// tool_result events. The plan update is emitted as a snapshot of
// the accumulated state, mirroring the legacy TodoWrite behavior.
const isError = "is_error" in chunk && chunk.is_error;
if (!isError) {
if (toolUse.name === "TaskCreate") {
applyTaskCreate(
taskState,
toolUse.input as Parameters<typeof applyTaskCreate>[1],
parseTaskCreateOutput(chunk.content),
);
} else if (toolUse.name === "TaskUpdate") {
applyTaskUpdate(taskState, toolUse.input as Parameters<typeof applyTaskUpdate>[1]);
}
}
if (!isError && (toolUse.name === "TaskCreate" || toolUse.name === "TaskUpdate")) {
update = {
sessionUpdate: "plan",
entries: taskStateToPlanEntries(taskState),
};
}
} else if (toolUse.name !== "TodoWrite") {
const { _meta: toolMeta, ...toolUpdate } = toolUpdateFromToolResult(
chunk,
toolUseCache[chunk.tool_use_id],
@@ -2813,6 +3293,7 @@ export function streamEventToAcpNotifications(
options?: {
clientCapabilities?: ClientCapabilities;
cwd?: string;
taskState?: TaskState;
},
): SessionNotification[] {
const event = message.event;
@@ -2829,6 +3310,7 @@ export function streamEventToAcpNotifications(
clientCapabilities: options?.clientCapabilities,
parentToolUseId: message.parent_tool_use_id,
cwd: options?.cwd,
taskState: options?.taskState,
},
);
case "content_block_delta":
@@ -2843,6 +3325,7 @@ export function streamEventToAcpNotifications(
clientCapabilities: options?.clientCapabilities,
parentToolUseId: message.parent_tool_use_id,
cwd: options?.cwd,
taskState: options?.taskState,
},
);
// No content

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env node
// Load managed settings and apply environment variables
import { loadManagedSettings, applyEnvironmentSettings } from "./utils.js";
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
import { claudeCliPath, runAcp } from "./acp-agent.js";
if (process.argv.includes("--cli")) {
@@ -35,9 +34,13 @@ if (process.argv.includes("--cli")) {
process.exit(1);
});
} else {
const managedSettings = loadManagedSettings();
if (managedSettings) {
applyEnvironmentSettings(managedSettings);
// Apply env vars from the managed-policy tier before any SDK call so the
// SDK subprocess inherits them. Going through resolveSettings (vs. a raw
// read of managed-settings.json) also picks up MDM sources on macOS and
// HKLM/HKCU on Windows.
const policy = await resolveSettings({ settingSources: [] });
for (const [key, value] of Object.entries(policy.effective.env ?? {})) {
process.env[key] = value;
}
// stdout is used to send messages to the client

View File

@@ -10,25 +10,14 @@ export {
type NewSessionMeta,
type SDKMessageFilter,
} from "./acp-agent.js";
export {
loadManagedSettings,
applyEnvironmentSettings,
nodeToWebReadable,
nodeToWebWritable,
Pushable,
unreachable,
} from "./utils.js";
export { nodeToWebReadable, nodeToWebWritable, Pushable, unreachable } from "./utils.js";
export {
toolInfoFromToolUse,
toDisplayPath,
planEntries,
toolUpdateFromToolResult,
} from "./tools.js";
export {
SettingsManager,
type ClaudeCodeSettings,
type SettingsManagerOptions,
} from "./settings.js";
export { SettingsManager, type SettingsManagerOptions } from "./settings.js";
// Export types
export type { ClaudePlanEntry } from "./tools.js";

View File

@@ -1,5 +1,12 @@
import * as fs from "node:fs";
import * as path from "node:path";
// resolveSettings and filterEscalatingDefaultMode are marked @alpha in the
// SDK; API may shift in a future release.
import {
filterEscalatingDefaultMode,
resolveSettings,
type Settings,
} from "@anthropic-ai/claude-agent-sdk";
import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
/**
@@ -14,51 +21,10 @@ import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
* Docs: https://code.claude.com/docs/en/iam#tool-specific-permission-rules
*/
export interface PermissionSettings {
defaultMode?: string;
}
export interface ClaudeCodeSettings {
permissions?: PermissionSettings;
env?: Record<string, string>;
model?: string;
effortLevel?: string;
}
/**
* Reads and parses a JSON settings file, returning an empty object if not found or invalid.
* Silently ignores missing files (ENOENT) but logs warnings for other errors
* (malformed JSON, permission errors, etc.) to aid debugging.
*/
async function loadSettingsFile(
filePath: string | null,
logger?: { error: (...args: any[]) => void },
): Promise<ClaudeCodeSettings> {
if (!filePath) {
return {};
}
try {
const content = await fs.promises.readFile(filePath, "utf-8");
return JSON.parse(content) as ClaudeCodeSettings;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return {};
}
logger?.error(`Failed to load settings from ${filePath}:`, error);
return {};
}
}
/**
* Gets the enterprise settings path based on the current platform
*/
export function getManagedSettingsPath(): string {
function getManagedSettingsPath(): string {
switch (process.platform) {
case "darwin":
return "/Library/Application Support/ClaudeCode/managed-settings.json";
case "linux":
return "/etc/claude-code/managed-settings.json";
case "win32":
return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
default:
@@ -72,23 +38,17 @@ export interface SettingsManagerOptions {
}
/**
* Manages Claude Code settings from multiple sources with proper precedence.
* Manages Claude Code settings using the SDK's `resolveSettings` merge engine
* so the values we see match what `query()` would observe.
*
* Settings are loaded from (in order of increasing precedence):
* 1. User settings (~/.claude/settings.json)
* 2. Project settings (<cwd>/.claude/settings.json)
* 3. Local project settings (<cwd>/.claude/settings.local.json)
* 4. Enterprise managed settings (platform-specific path)
*
* The manager watches all settings files for changes and automatically reloads.
* Watches the user/project/local/managed settings files for changes and
* re-resolves through the SDK on update. Escalating `permissions.defaultMode`
* values from repo-committed sources are filtered out via
* `filterEscalatingDefaultMode`, matching the CLI's trust policy.
*/
export class SettingsManager {
private cwd: string;
private userSettings: ClaudeCodeSettings = {};
private projectSettings: ClaudeCodeSettings = {};
private localSettings: ClaudeCodeSettings = {};
private enterpriseSettings: ClaudeCodeSettings = {};
private mergedSettings: ClaudeCodeSettings = {};
private effective: Settings = {};
private watchers: fs.FSWatcher[] = [];
private onChange?: () => void;
private logger: { log: (...args: any[]) => void; error: (...args: any[]) => void };
@@ -126,98 +86,37 @@ export class SettingsManager {
}
/**
* Returns the path to the user settings file
* Paths the SDK reads when resolving settings for this cwd. Watching the
* containing directories means we pick up file creation as well as edits.
*/
private getUserSettingsPath(): string {
return path.join(CLAUDE_CONFIG_DIR, "settings.json");
private getWatchedPaths(): string[] {
return [
path.join(CLAUDE_CONFIG_DIR, "settings.json"),
path.join(this.cwd, ".claude", "settings.json"),
path.join(this.cwd, ".claude", "settings.local.json"),
getManagedSettingsPath(),
];
}
/**
* Returns the path to the project settings file
*/
private getProjectSettingsPath(): string {
return path.join(this.cwd, ".claude", "settings.json");
}
/**
* Returns the path to the local project settings file
*/
private getLocalSettingsPath(): string {
return path.join(this.cwd, ".claude", "settings.local.json");
}
/**
* Loads settings from all sources
* Resolves the effective settings via the SDK and applies the CLI's trust
* filter for escalating `permissions.defaultMode` values.
*/
private async loadAllSettings(): Promise<void> {
const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
loadSettingsFile(this.getUserSettingsPath(), this.logger),
loadSettingsFile(this.getProjectSettingsPath(), this.logger),
loadSettingsFile(this.getLocalSettingsPath(), this.logger),
loadSettingsFile(getManagedSettingsPath(), this.logger),
]);
this.userSettings = userSettings;
this.projectSettings = projectSettings;
this.localSettings = localSettings;
this.enterpriseSettings = enterpriseSettings;
this.mergeSettings();
}
/**
* Merges all settings sources with proper precedence.
* For permissions, rules from all sources are combined.
* Deny rules always take precedence during permission checks.
*/
private mergeSettings(): void {
const allSettings = [
this.userSettings,
this.projectSettings,
this.localSettings,
this.enterpriseSettings,
];
const merged: ClaudeCodeSettings = {};
for (const settings of allSettings) {
if (settings.env) {
merged.env = { ...merged.env, ...settings.env };
}
if (settings.model) {
merged.model = settings.model;
}
if (settings.effortLevel !== undefined) {
merged.effortLevel = settings.effortLevel;
}
if (settings.permissions?.defaultMode !== undefined) {
merged.permissions = {
...merged.permissions,
defaultMode: settings.permissions.defaultMode,
};
}
try {
const resolved = await resolveSettings({ cwd: this.cwd });
this.effective = filterEscalatingDefaultMode(resolved);
} catch (error) {
this.logger.error("Failed to resolve settings:", error);
this.effective = {};
}
this.mergedSettings = merged;
}
/**
* Sets up file watchers for all settings files
*/
private setupWatchers(): void {
const paths = [
this.getUserSettingsPath(),
this.getProjectSettingsPath(),
this.getLocalSettingsPath(),
getManagedSettingsPath(),
];
for (const filePath of paths) {
if (!filePath) continue;
for (const filePath of this.getWatchedPaths()) {
try {
const dir = path.dirname(filePath);
const filename = path.basename(filePath);
@@ -268,8 +167,8 @@ export class SettingsManager {
/**
* Returns the current merged settings
*/
getSettings(): ClaudeCodeSettings {
return this.mergedSettings;
getSettings(): Settings {
return this.effective;
}
/**

View File

@@ -126,6 +126,31 @@ describe("ClaudeAcpAgent settings", () => {
expect(response.modes.currentModeId).toBe("acceptEdits");
});
it("drops escalating defaultMode when it comes from project-tier settings", async () => {
// bypassPermissions in .claude/settings.json (a repo-committed tier) is
// filtered by the SDK's trust policy and clamps to 'default'.
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(path.join(projectDir, ".claude"), { recursive: true });
await fs.promises.writeFile(
path.join(projectDir, ".claude", "settings.json"),
JSON.stringify({ permissions: { defaultMode: "bypassPermissions" } }),
);
const { getCapturedOptions } = mockQuery();
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
expect(getCapturedOptions().permissionMode).toBe("default");
expect(response.modes.currentModeId).toBe("default");
});
it("defaults to 'default' when no permissions.defaultMode is set", async () => {
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
@@ -197,7 +222,9 @@ describe("ClaudeAcpAgent settings", () => {
});
// Bad model is ignored at the usage site; falls back to the first SDK model.
expect(setModelSpy).toHaveBeenCalledWith("claude-sonnet-4-6");
// No setModel call is needed because no override was applied — the SDK is
// already on its own default.
expect(setModelSpy).not.toHaveBeenCalled();
expect(response.models.currentModelId).toBe("claude-sonnet-4-6");
});
@@ -362,6 +389,261 @@ describe("ClaudeAcpAgent settings", () => {
});
});
describe("availableModels allowlist from settings", () => {
function mockQueryWithModels(models: any[]): {
setModelSpy: ReturnType<typeof vi.fn>;
} {
const setModelSpy = vi.fn();
querySpy.mockImplementation(() => {
return {
initializationResult: async () => ({ models }),
setModel: setModelSpy,
supportedCommands: async () => [],
} as any;
});
return { setModelSpy };
}
it("restricts configOptions to the user's allowlist using their exact IDs", async () => {
// Reproduces the scenario from
// https://github.com/agentclientprotocol/claude-agent-acp/issues/620:
// user lists `claude-haiku-4-5` (no date pin) in availableModels, but
// the SDK still surfaces its `haiku` alias which resolves to a
// date-pinned variant the user doesn't have access to.
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: [
"claude-sonnet-4-6[1m]",
"claude-opus-4-6[1m]",
"claude-haiku-4-5",
"claude-opus-4-7[1m]",
],
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{
value: "sonnet[1m]",
displayName: "Sonnet (1M context)",
description: "Sonnet 4.6 long context",
},
{
value: "opus[1m]",
displayName: "Opus (1M context)",
description: "Opus 1M context",
},
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
expect(modelOption.options.map((o: any) => o.value)).toEqual([
"default",
"claude-sonnet-4-6[1m]",
"claude-opus-4-6[1m]",
"claude-haiku-4-5",
"claude-opus-4-7[1m]",
]);
});
it("unions availableModels across user and project settings", async () => {
// https://code.claude.com/docs/en/model-config#merge-behavior
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({ availableModels: ["claude-haiku-4-5"] }),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(path.join(projectDir, ".claude"), { recursive: true });
await fs.promises.writeFile(
path.join(projectDir, ".claude", "settings.json"),
JSON.stringify({
availableModels: ["claude-haiku-4-5", "claude-opus-4-7[1m]"],
}),
);
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
// User and project entries are unioned and deduplicated.
expect(modelOption.options.map((o: any) => o.value)).toEqual([
"default",
"claude-haiku-4-5",
"claude-opus-4-7[1m]",
]);
});
it("returns only the default entry when availableModels is an empty array", async () => {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({ availableModels: [] }),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
expect(modelOption.options.map((o: any) => o.value)).toEqual(["default"]);
});
it("does not filter when availableModels is absent from settings", async () => {
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
expect(modelOption.options.map((o: any) => o.value)).toEqual(["default", "haiku"]);
});
it("passes the user's exact ID to setModel when it matches an SDK alias", async () => {
// Without the allowlist, the SDK would resolve `haiku` to a
// date-pinned variant. Forcing setModel to receive `claude-haiku-4-5`
// is exactly what the issue's workaround
// (`ANTHROPIC_DEFAULT_HAIKU_MODEL`) achieves manually.
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-haiku-4-5"],
model: "claude-haiku-4-5",
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
const { setModelSpy } = mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{ value: "haiku", displayName: "Haiku", description: "Fast" },
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
expect(setModelSpy).toHaveBeenCalledWith("claude-haiku-4-5");
expect(response.models.currentModelId).toBe("claude-haiku-4-5");
});
it("does not inherit display info across mismatched model family versions", async () => {
// https://github.com/agentclientprotocol/claude-agent-acp/issues/639:
// when the SDK's `opus` alias resolves to Opus 4.7, an allowlist entry
// of `claude-opus-4-6` (or `claude-opus-4-6[1m]`) used to substring-match
// `opus` and inherit the "Opus 4.7" display info. With version-aware
// matching, these entries fall back to showing their literal ID rather
// than a misleading newer name.
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: [
"claude-opus-4-6",
"claude-opus-4-6[1m]",
"claude-opus-4-7",
"claude-opus-4-7[1m]",
],
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
mockQueryWithModels([
{ value: "default", displayName: "Default", description: "Default model" },
{
value: "opus",
displayName: "Opus 4.7",
description: "Claude Opus 4.7 — complex tasks, higher cost",
},
{
value: "opus[1m]",
displayName: "Opus 4.7 (1M context)",
description: "Opus 4.7 with 1M context",
},
]);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const modelOption = response.configOptions.find((o: any) => o.id === "model");
const byValue: Record<string, { name: string; description?: string }> = {};
for (const opt of modelOption.options) {
byValue[opt.value] = { name: opt.name, description: opt.description };
}
// 4-6 entries must NOT inherit the 4.7 SDK alias display info.
expect(byValue["claude-opus-4-6"].name).toBe("claude-opus-4-6");
expect(byValue["claude-opus-4-6"].description).toBe("");
expect(byValue["claude-opus-4-6[1m]"].name).toBe("claude-opus-4-6[1m]");
expect(byValue["claude-opus-4-6[1m]"].description).toBe("");
// 4-7 entries continue to inherit display info from a 4.7 SDK alias.
expect(byValue["claude-opus-4-7"].name).toBe("Opus 4.7");
expect(byValue["claude-opus-4-7[1m]"].name).toMatch(/Opus 4\.7/);
});
});
it("resolves model aliases like opus[1m] to the correct model", async () => {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
@@ -407,4 +689,94 @@ describe("ClaudeAcpAgent settings", () => {
expect(setModelSpy).toHaveBeenCalledWith("claude-opus-4-6-1m");
expect(response.models.currentModelId).toBe("claude-opus-4-6-1m");
});
it("skips the initial setModel when the resolved value matches the SDK's model list verbatim", async () => {
// Covers the launcher case from PR #646: the launcher bakes the model into
// ANTHROPIC_MODEL, the SDK already starts on that model, and a second
// setModel call would be a redundant round-trip (and on some launcher
// setups, more fragile than launch-time selection).
const originalEnv = process.env.ANTHROPIC_MODEL;
process.env.ANTHROPIC_MODEL = "claude-opus-4-6";
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
const setModelSpy = vi.fn();
querySpy.mockImplementation(() => {
return {
initializationResult: async () => ({
models: [
{ value: "default", displayName: "Default", description: "" },
{ value: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6", description: "" },
{ value: "claude-opus-4-6", displayName: "Claude Opus 4.6", description: "" },
],
}),
setModel: setModelSpy,
supportedCommands: async () => [],
} as any;
});
try {
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
expect(setModelSpy).not.toHaveBeenCalled();
expect(response.models.currentModelId).toBe("claude-opus-4-6");
} finally {
if (originalEnv === undefined) {
delete process.env.ANTHROPIC_MODEL;
} else {
process.env.ANTHROPIC_MODEL = originalEnv;
}
}
});
it("still calls setModel when the allowlist synthesizes a value the SDK has not surfaced", async () => {
// The allowlist may rewrite a model's `value` to the user's literal ID
// (e.g., `claude-haiku-4-5`) even when the SDK only exposed an alias
// (`haiku`). In that case the SDK has not independently arrived at the
// user's preferred ID, so we must sync via setModel.
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-haiku-4-5"],
model: "claude-haiku-4-5",
}),
);
const projectDir = path.join(tempDir, "project");
await fs.promises.mkdir(projectDir, { recursive: true });
const setModelSpy = vi.fn();
querySpy.mockImplementation(() => {
return {
initializationResult: async () => ({
models: [
{ value: "default", displayName: "Default", description: "" },
{ value: "haiku", displayName: "Haiku", description: "Fast" },
],
}),
setModel: setModelSpy,
supportedCommands: async () => [],
} as any;
});
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent(createMockClient());
const response = await (agent as any).createSession({
cwd: projectDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
expect(setModelSpy).toHaveBeenCalledWith("claude-haiku-4-5");
expect(response.models.currentModelId).toBe("claude-haiku-4-5");
});
});

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, vi } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from "vitest";
import { spawn, spawnSync } from "child_process";
import {
Agent,
@@ -35,8 +35,16 @@ import {
type SDKMessageFilter,
} from "../acp-agent.js";
import { Pushable } from "../utils.js";
import { query, SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
import { deleteSession, query, SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
import { randomUUID } from "crypto";
vi.mock("@anthropic-ai/claude-agent-sdk", async (importOriginal) => {
const actual = await importOriginal<typeof import("@anthropic-ai/claude-agent-sdk")>();
return {
...actual,
deleteSession: vi.fn(),
};
});
import type {
BetaToolResultBlockParam,
BetaToolSearchToolResultBlockParam,
@@ -639,6 +647,8 @@ describe("tool conversions", () => {
],
stop_reason: null,
stop_sequence: null,
stop_details: null,
diagnostics: null,
usage: {
input_tokens: 6,
cache_creation_input_tokens: 326,
@@ -1533,6 +1543,9 @@ describe("stop reason propagation", () => {
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
@@ -1676,6 +1689,9 @@ describe("stop reason propagation", () => {
nextPendingOrder: 0,
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
const response = await agent.prompt({
@@ -1794,6 +1810,409 @@ describe("stop reason propagation", () => {
});
});
describe("assistant text fallback when streaming yields no text deltas", () => {
function createCapturingAgent() {
const notifications: SessionNotification[] = [];
const mockClient = {
sessionUpdate: async (n: SessionNotification) => {
notifications.push(n);
},
extNotification: async () => {},
} as unknown as AgentSideConnection;
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
return { agent, notifications };
}
function injectSession(agent: ClaudeAcpAgent, messages: any[]) {
const input = new Pushable<any>();
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const { value: userMessage, done } = await iter.next();
if (!done && userMessage) {
yield {
type: "user",
message: userMessage.message,
parent_tool_use_id: null,
uuid: userMessage.uuid,
session_id: "test-session",
isReplay: true,
};
}
yield* messages;
}
agent.sessions["test-session"] = {
query: messageGenerator() as any,
input,
cancelled: false,
cwd: "/test",
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
modes: { currentModeId: "default", availableModes: [] },
models: { currentModelId: "default", availableModels: [] },
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
configOptions: [],
promptRunning: false,
pendingMessages: new Map(),
nextPendingOrder: 0,
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
function makeResult() {
return {
type: "result" as const,
subtype: "success" as const,
stop_reason: null,
is_error: false,
result: "",
errors: [],
duration_ms: 0,
duration_api_ms: 0,
num_turns: 1,
total_cost_usd: 0,
usage: {
input_tokens: 0,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
modelUsage: {},
permission_denials: [],
uuid: randomUUID(),
session_id: "test-session",
};
}
function makeAssistantMessage(messageId: string, text: string) {
return makeAssistantMessageWithBlocks(messageId, [{ type: "text", text }]);
}
// Same as makeAssistantMessage but with arbitrary content blocks, so tests
// can reproduce gateways that split a single response (one message id) into
// several `assistant` messages each carrying a different block.
function makeAssistantMessageWithBlocks(messageId: string, content: any[]) {
return {
type: "assistant" as const,
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
message: {
id: messageId,
type: "message" as const,
role: "assistant" as const,
model: "claude-test",
content,
stop_reason: "end_turn",
stop_sequence: null,
usage: {
input_tokens: 0,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
},
};
}
it("emits the assembled text when no content_block_delta was streamed", async () => {
const { agent, notifications } = createCapturingAgent();
const messageId = "msg_no_stream";
const finalText = "final answer never streamed";
injectSession(agent, [
makeAssistantMessage(messageId, finalText),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const textChunks = notifications
.map((n) => n.update)
.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === finalText,
);
expect(textChunks.length).toBe(1);
});
it("does not re-emit text already streamed via content_block_delta", async () => {
const { agent, notifications } = createCapturingAgent();
const messageId = "msg_streamed";
const streamedText = "streamed answer";
injectSession(agent, [
{
type: "stream_event",
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
event: {
type: "message_start",
message: {
id: messageId,
type: "message",
role: "assistant",
model: "claude-test",
content: [],
stop_reason: null,
stop_sequence: null,
usage: {
input_tokens: 0,
output_tokens: 0,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
},
},
},
{
type: "stream_event",
parent_tool_use_id: null,
uuid: randomUUID(),
session_id: "test-session",
event: {
type: "content_block_delta",
index: 0,
delta: { type: "text_delta", text: streamedText },
},
},
makeAssistantMessage(messageId, streamedText),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const textChunks = notifications
.map((n) => n.update)
.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === streamedText,
);
// Only the streamed content_block_delta should produce a chunk; the
// assembled message must not duplicate it.
expect(textChunks.length).toBe(1);
});
it("fallback emits exactly once when the same assistant message is delivered twice", async () => {
// Regression test: the SDK occasionally delivers the same assembled
// assistant message twice (observed in production logs). Without
// recording the id into `textStreamedMessageIds` after the first
// fallback emit, the second delivery would re-fire the fallback and
// ACP clients would see duplicate text chunks. After the dedupe fix
// the second delivery must be a no-op.
const { agent, notifications } = createCapturingAgent();
const messageId = "msg_delivered_twice";
const finalText = "final answer delivered twice";
injectSession(agent, [
makeAssistantMessage(messageId, finalText),
makeAssistantMessage(messageId, finalText),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const textChunks = notifications
.map((n) => n.update)
.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === finalText,
);
expect(textChunks.length).toBe(1);
});
it("textStreamedMessageIds is cleared at the start of each new turn", async () => {
// Regression test: assistant message ids are turn-scoped. Without
// per-turn clear, the set would accumulate over the session's lifetime
// and a (rare) reuse of an id across turns would be misclassified as
// "already streamed", silently dropping the fallback emit. The two
// turns below use the same messageId; both should fallback-emit
// exactly once because the set is reset on each `prompt()` entry.
const { agent, notifications } = createCapturingAgent();
const messageId = "msg_reused_across_turns";
const finalText = "answer for both turns";
injectSession(agent, [
makeAssistantMessage(messageId, finalText),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
makeAssistantMessage(messageId, finalText),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const first = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "turn 1" }],
});
const second = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "turn 2" }],
});
expect(first.stopReason).toBe("end_turn");
expect(second.stopReason).toBe("end_turn");
const textChunks = notifications
.map((n) => n.update)
.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === finalText,
);
// Two turns each fallback-emit once.
expect(textChunks.length).toBe(2);
});
it("emits the text block when it arrives in a later same-id message than the thinking block", async () => {
// Production regression (OpenAI-compatible gpt-5 gateway): a single
// assistant response (one message id) is delivered as *separate*
// `assistant` messages — an empty `thinking` block first, then the real
// `text` block — both sharing the same id and neither streamed via
// content_block_delta. Deduping at message-id granularity flagged the id
// on the thinking block and then dropped the text, so the client saw the
// fallback fire but received an empty answer. The fallback must key on the
// block contents so the later text block is still emitted.
const { agent, notifications } = createCapturingAgent();
const messageId = "resp_split_blocks";
const finalText = "=== Ticket Review Summary === approved";
injectSession(agent, [
makeAssistantMessageWithBlocks(messageId, [
{ type: "thinking", thinking: "", signature: "sig" },
]),
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: finalText }]),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const textChunks = notifications
.map((n) => n.update)
.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === finalText,
);
expect(textChunks.length).toBe(1);
});
it("emits distinct text blocks that share a message id", async () => {
// A gateway may split a multi-block answer (thinking + text + a second
// text) across same-id messages. Every distinct text block must reach the
// client exactly once.
const { agent, notifications } = createCapturingAgent();
const messageId = "resp_multi_text";
const firstText = "我先直接执行审批并返回工单结果。";
const secondText = "Ticket approved.";
injectSession(agent, [
makeAssistantMessageWithBlocks(messageId, [
{ type: "thinking", thinking: "considering options", signature: "sig" },
]),
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: firstText }]),
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: secondText }]),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const updates = notifications.map((n) => n.update);
const firstChunks = updates.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === firstText,
);
const secondChunks = updates.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === secondText,
);
expect(firstChunks.length).toBe(1);
expect(secondChunks.length).toBe(1);
});
it("does not duplicate text when an incremental block is followed by a consolidated message", async () => {
// Some gateways deliver an incremental per-block `assistant` message and
// then a consolidated `assistant` message (same id) that repeats that
// block alongside others. The repeated block must not be emitted twice.
const { agent, notifications } = createCapturingAgent();
const messageId = "resp_consolidated";
const thinking = "step-by-step reasoning";
const finalText = "the answer";
injectSession(agent, [
makeAssistantMessageWithBlocks(messageId, [{ type: "text", text: finalText }]),
makeAssistantMessageWithBlocks(messageId, [
{ type: "thinking", thinking, signature: "sig" },
{ type: "text", text: finalText },
]),
makeResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
const response = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "go" }],
});
expect(response.stopReason).toBe("end_turn");
const updates = notifications.map((n) => n.update);
const textChunks = updates.filter(
(u) =>
u.sessionUpdate === "agent_message_chunk" &&
u.content.type === "text" &&
u.content.text === finalText,
);
const thoughtChunks = updates.filter(
(u) =>
u.sessionUpdate === "agent_thought_chunk" &&
u.content.type === "text" &&
u.content.text === thinking,
);
expect(textChunks.length).toBe(1);
expect(thoughtChunks.length).toBe(1);
});
});
describe("session/close", () => {
function createMockAgent() {
const mockClient = {
@@ -1834,6 +2253,9 @@ describe("session/close", () => {
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
return agent.sessions[sessionId]!;
}
@@ -1883,6 +2305,96 @@ describe("session/close", () => {
});
});
describe("session/delete", () => {
function createMockAgent() {
const mockClient = {
sessionUpdate: async () => {},
} as unknown as AgentSideConnection;
return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
}
function injectSession(agent: ClaudeAcpAgent, sessionId: string) {
function* empty() {}
const gen = Object.assign(empty(), { interrupt: vi.fn(), close: vi.fn() });
agent.sessions[sessionId] = {
query: gen as any,
input: new Pushable(),
cancelled: false,
cwd: "/test",
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
modes: { currentModeId: "default", availableModes: [] },
models: { currentModelId: "default", availableModels: [] },
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
configOptions: [],
promptRunning: false,
pendingMessages: new Map(),
nextPendingOrder: 0,
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
return agent.sessions[sessionId]!;
}
beforeEach(() => {
vi.mocked(deleteSession).mockReset();
vi.mocked(deleteSession).mockResolvedValue(undefined);
});
it("tears down the active session and deletes it from disk", async () => {
const agent = createMockAgent();
const session = injectSession(agent, "session-1");
const result = await agent.unstable_deleteSession({ sessionId: "session-1" });
expect(result).toEqual({});
expect(agent.sessions["session-1"]).toBeUndefined();
expect(session.query.interrupt).toHaveBeenCalled();
expect(session.settingsManager.dispose).toHaveBeenCalled();
expect(session.abortController.signal.aborted).toBe(true);
expect(deleteSession).toHaveBeenCalledWith("session-1");
});
it("deletes a session from disk that is not currently active", async () => {
const agent = createMockAgent();
const result = await agent.unstable_deleteSession({ sessionId: "not-active" });
expect(result).toEqual({});
expect(deleteSession).toHaveBeenCalledWith("not-active");
});
it("propagates errors from the SDK delete call", async () => {
const agent = createMockAgent();
vi.mocked(deleteSession).mockRejectedValueOnce(new Error("Session not found on disk"));
await expect(agent.unstable_deleteSession({ sessionId: "missing" })).rejects.toThrow(
"Session not found on disk",
);
});
it("does not affect other sessions when deleting one", async () => {
const agent = createMockAgent();
injectSession(agent, "session-a");
injectSession(agent, "session-b");
await agent.unstable_deleteSession({ sessionId: "session-a" });
expect(agent.sessions["session-a"]).toBeUndefined();
expect(agent.sessions["session-b"]).toBeDefined();
});
});
describe("getOrCreateSession param change detection", () => {
function createMockAgent() {
const mockClient = {
@@ -1930,6 +2442,9 @@ describe("getOrCreateSession param change detection", () => {
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
return agent.sessions[sessionId]!;
}
@@ -2164,6 +2679,9 @@ describe("usage_update computation", () => {
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
@@ -3063,6 +3581,9 @@ describe("emitRawSDKMessages", () => {
abortController: new AbortController(),
emitRawSDKMessages,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
@@ -3290,6 +3811,9 @@ describe("result origin handling", () => {
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
@@ -3411,3 +3935,363 @@ describe("result origin handling", () => {
expect(response.stopReason).toBe("max_tokens");
});
});
describe("memory_recall handling", () => {
function createMockAgentWithCapture() {
const updates: any[] = [];
const mockClient = {
sessionUpdate: async (notification: any) => {
updates.push(notification);
},
} as unknown as AgentSideConnection;
const agent = new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
return { agent, updates };
}
function injectSession(agent: ClaudeAcpAgent, messages: any[]) {
const input = new Pushable<any>();
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const { value: userMessage, done } = await iter.next();
if (!done && userMessage) {
yield {
type: "user",
message: userMessage.message,
parent_tool_use_id: null,
uuid: userMessage.uuid,
session_id: "test-session",
isReplay: true,
};
}
yield* messages;
}
agent.sessions["test-session"] = {
query: messageGenerator() as any,
input,
cancelled: false,
cwd: "/test",
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
modes: { currentModeId: "default", availableModes: [] },
models: { currentModelId: "default", availableModels: [] },
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
configOptions: [],
promptRunning: false,
pendingMessages: new Map(),
nextPendingOrder: 0,
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
}
function createResult() {
return {
type: "result" as const,
subtype: "success" as const,
stop_reason: "end_turn",
is_error: false,
result: "",
errors: [],
duration_ms: 0,
duration_api_ms: 0,
num_turns: 1,
total_cost_usd: 0,
usage: {
input_tokens: 10,
output_tokens: 5,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
modelUsage: {},
permission_denials: [],
uuid: randomUUID(),
session_id: "test-session",
};
}
it("emits a synthetic tool_call for select mode with one location per memory", async () => {
const { agent, updates } = createMockAgentWithCapture();
const recallUuid = randomUUID();
injectSession(agent, [
{
type: "system",
subtype: "memory_recall",
mode: "select",
memories: [
{ path: "/Users/test/.claude/memory/user_role.md", scope: "personal" },
{ path: "/Users/test/.claude/memory/feedback_testing.md", scope: "personal" },
{ path: "/Users/test/.claude/team/conventions.md", scope: "team" },
],
uuid: recallUuid,
session_id: "test-session",
},
createResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
const toolCall = updates.find((u: any) => u.update?.sessionUpdate === "tool_call");
expect(toolCall).toBeDefined();
expect(toolCall.update).toMatchObject({
sessionUpdate: "tool_call",
toolCallId: recallUuid,
title: "Recalled 3 memories",
kind: "read",
status: "completed",
locations: [
{ path: "/Users/test/.claude/memory/user_role.md" },
{ path: "/Users/test/.claude/memory/feedback_testing.md" },
{ path: "/Users/test/.claude/team/conventions.md" },
],
_meta: {
claudeCode: { toolName: "memory_recall", toolResponse: { mode: "select" } },
},
});
expect(toolCall.update.content).toBeUndefined();
});
it("uses singular 'memory' in title when exactly one entry", async () => {
const { agent, updates } = createMockAgentWithCapture();
injectSession(agent, [
{
type: "system",
subtype: "memory_recall",
mode: "select",
memories: [{ path: "/Users/test/.claude/memory/user_role.md", scope: "personal" }],
uuid: randomUUID(),
session_id: "test-session",
},
createResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
const toolCall = updates.find((u: any) => u.update?.sessionUpdate === "tool_call");
expect(toolCall.update.title).toBe("Recalled 1 memory");
});
it("emits synthesis content and no locations for synthesize mode", async () => {
const { agent, updates } = createMockAgentWithCapture();
injectSession(agent, [
{
type: "system",
subtype: "memory_recall",
mode: "synthesize",
memories: [
{
path: "<synthesis:/Users/test/.claude/memory>",
scope: "personal",
content: "The user prefers terse responses and writes Go.",
},
],
uuid: randomUUID(),
session_id: "test-session",
},
createResult(),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
await agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "hi" }] });
const toolCall = updates.find((u: any) => u.update?.sessionUpdate === "tool_call");
expect(toolCall).toBeDefined();
expect(toolCall.update.title).toBe("Recalled synthesized memory");
expect(toolCall.update.locations).toBeUndefined();
expect(toolCall.update.content).toEqual([
{
type: "content",
content: { type: "text", text: "The user prefers terse responses and writes Go." },
},
]);
expect(toolCall.update._meta.claudeCode.toolResponse).toEqual({ mode: "synthesize" });
});
});
describe("post-error recovery", () => {
function createMockAgent() {
const mockClient = {
sessionUpdate: async () => {},
} as unknown as AgentSideConnection;
return new ClaudeAcpAgent(mockClient, { log: () => {}, error: () => {} });
}
function createResultMessage(overrides: {
subtype: "success" | "error_during_execution";
stop_reason: string | null;
is_error: boolean;
result?: string;
errors?: string[];
}) {
return {
type: "result" as const,
subtype: overrides.subtype,
stop_reason: overrides.stop_reason,
is_error: overrides.is_error,
result: overrides.result ?? "",
errors: overrides.errors ?? [],
duration_ms: 0,
duration_api_ms: 0,
num_turns: 1,
total_cost_usd: 0,
usage: {
input_tokens: 10,
output_tokens: 5,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
modelUsage: {},
permission_denials: [],
uuid: randomUUID(),
session_id: "test-session",
};
}
// Two-turn generator: turn 1 yields the caller-supplied `firstTurn`
// messages (including a trailing idle that the drain must consume).
// Turn 2 yields a clean success + idle, used to verify the next prompt
// sees real messages rather than the stale idle.
function injectTwoTurnSession(agent: ClaudeAcpAgent, firstTurn: unknown[]) {
const input = new Pushable<any>();
const interrupt = vi.fn(async () => {});
const close = vi.fn();
async function* messageGenerator() {
const iter = input[Symbol.asyncIterator]();
const first = await iter.next();
if (!first.done && first.value) {
yield {
type: "user",
message: first.value.message,
parent_tool_use_id: null,
uuid: first.value.uuid,
session_id: "test-session",
isReplay: true,
};
}
yield* firstTurn;
const second = await iter.next();
if (!second.done && second.value) {
yield {
type: "user",
message: second.value.message,
parent_tool_use_id: null,
uuid: second.value.uuid,
session_id: "test-session",
isReplay: true,
};
}
yield createResultMessage({ subtype: "success", stop_reason: null, is_error: false });
yield { type: "system", subtype: "session_state_changed", state: "idle" };
}
const gen = Object.assign(messageGenerator(), { interrupt, close });
agent.sessions["test-session"] = {
query: gen as any,
input,
cancelled: false,
cwd: "/test",
sessionFingerprint: JSON.stringify({ cwd: "/test", mcpServers: [] }),
modes: { currentModeId: "default", availableModes: [] },
models: { currentModelId: "default", availableModels: [] },
modelInfos: [],
settingsManager: { dispose: vi.fn() } as any,
accumulatedUsage: {
inputTokens: 0,
outputTokens: 0,
cachedReadTokens: 0,
cachedWriteTokens: 0,
},
configOptions: [],
promptRunning: false,
pendingMessages: new Map(),
nextPendingOrder: 0,
abortController: new AbortController(),
emitRawSDKMessages: false,
contextWindowSize: 200000,
taskState: new Map(),
textStreamedMessageIds: new Set<string>(),
fallbackEmittedBlockKeys: new Set<string>(),
};
return { interrupt };
}
it("drains a failed turn's trailing idle so the next prompt is not short-circuited", async () => {
const agent = createMockAgent();
const { interrupt } = injectTwoTurnSession(agent, [
createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: true,
result: "boom",
}),
// Trailing idle from the failed turn. Without draining, the next
// prompt's first query.next() would consume this and short-circuit
// to end_turn with zero usage (issue #654).
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
await expect(
agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
}),
).rejects.toThrow();
expect(interrupt).toHaveBeenCalled();
const second = await agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "second" }],
});
expect(second.stopReason).toBe("end_turn");
expect(second.usage?.inputTokens).toBe(10);
expect(second.usage?.outputTokens).toBe(5);
});
it("cancels all queued pending prompts when a turn errors", async () => {
const agent = createMockAgent();
injectTwoTurnSession(agent, [
createResultMessage({
subtype: "success",
stop_reason: "end_turn",
is_error: true,
result: "boom",
}),
{ type: "system", subtype: "session_state_changed", state: "idle" },
]);
// Simulate two prompts already queued behind the running turn. Both
// resolvers should fire with `true` (cancelled) when the running
// prompt errors, and the map should be cleared.
const session = agent.sessions["test-session"];
let resolveA!: (cancelled: boolean) => void;
let resolveB!: (cancelled: boolean) => void;
const pendingA = new Promise<boolean>((r) => (resolveA = r));
const pendingB = new Promise<boolean>((r) => (resolveB = r));
session.pendingMessages.set("uuid-a", { resolve: resolveA, order: 0 });
session.pendingMessages.set("uuid-b", { resolve: resolveB, order: 1 });
await expect(
agent.prompt({
sessionId: "test-session",
prompt: [{ type: "text", text: "first" }],
}),
).rejects.toThrow();
await expect(pendingA).resolves.toBe(true);
await expect(pendingB).resolves.toBe(true);
expect(session.pendingMessages.size).toBe(0);
});
});

View File

@@ -76,4 +76,33 @@ describe("additionalRoots", () => {
});
expect(capturedOptions!.additionalDirectories).toEqual(["/workspace/shared", "", root]);
});
it("prefers the official ACP additionalDirectories field over _meta.additionalRoots", async () => {
await agent.newSession({
cwd: "/test",
mcpServers: [],
additionalDirectories: ["/from/official"],
_meta: { additionalRoots: ["/from/meta"] },
});
expect(capturedOptions!.additionalDirectories).toEqual(["/from/official"]);
});
it("merges official ACP additionalDirectories with claudeCode SDK additionalDirectories", async () => {
await agent.newSession({
cwd: "/test",
mcpServers: [],
additionalDirectories: ["/from/official"],
_meta: { claudeCode: { options: { additionalDirectories: ["/from/sdk"] } } },
});
expect(capturedOptions!.additionalDirectories).toEqual(["/from/sdk", "/from/official"]);
});
it("falls back to _meta.additionalRoots when the official field is omitted", async () => {
await agent.newSession({
cwd: "/test",
mcpServers: [],
_meta: { additionalRoots: ["/from/meta"] },
});
expect(capturedOptions!.additionalDirectories).toEqual(["/from/meta"]);
});
});

View File

@@ -53,6 +53,9 @@ describe("authorization", () => {
expect(initializeResponse.authMethods).not.toContainEqual(
expect.objectContaining({ id: "gateway" }),
);
expect(initializeResponse.authMethods).not.toContainEqual(
expect.objectContaining({ id: "gateway-bedrock" }),
);
});
it("gateway auth offered when client advertises auth._meta.gateway capability", async () => {
@@ -65,11 +68,20 @@ describe("authorization", () => {
} as any,
});
expect(initializeResponse.authMethods).toContainEqual(
expect.objectContaining({ id: "gateway" }),
expect.objectContaining({
id: "gateway",
_meta: { gateway: { protocol: "anthropic" } },
}),
);
expect(initializeResponse.authMethods).toContainEqual(
expect.objectContaining({
id: "gateway-bedrock",
_meta: { gateway: { protocol: "bedrock" } },
}),
);
});
it("uses gateway env after gateway auth", async () => {
it("uses gateway env after anthropic gateway auth", async () => {
const [agent, mockQuery] = await createAgentMock();
const initializeResponse = await agent.initialize({
@@ -105,7 +117,7 @@ describe("authorization", () => {
expect.objectContaining({
options: expect.objectContaining({
env: expect.objectContaining({
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_AUTH_TOKEN: " ",
ANTHROPIC_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "x-api-key: test",
userEnv: "userEnv",
@@ -115,6 +127,42 @@ describe("authorization", () => {
);
});
it("uses gateway env after gateway auth", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({
protocolVersion: 1,
clientCapabilities: {
auth: { terminal: true, _meta: { gateway: true } },
} as any,
});
await agent.authenticate({
methodId: "gateway-bedrock",
_meta: {
gateway: { baseUrl: "https://gateway.example", headers: { "custom-header": "test" } },
},
});
await agent.newSession({
cwd: "testRoot",
mcpServers: [],
});
expect(mockQuery).toHaveBeenCalledWith(
expect.objectContaining({
options: expect.objectContaining({
env: expect.objectContaining({
CLAUDE_CODE_USE_BEDROCK: "1",
AWS_BEARER_TOKEN_BEDROCK: " ",
ANTHROPIC_BEDROCK_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "custom-header: test",
}),
}),
}),
);
});
it("hide claude authentication without terminal-auth", async () => {
const [agent] = await createAgentMock();
vi.stubGlobal("process", { ...process, argv: ["--hide-claude-auth"] });

View File

@@ -65,8 +65,9 @@ const MOCK_CONFIG_OPTIONS = [
description: "Available effort levels for this model",
type: "select",
category: "effort",
currentValue: "high",
currentValue: "default",
options: [
{ value: "default", name: "Default" },
{ value: "low", name: "Low" },
{ value: "medium", name: "Medium" },
{ value: "high", name: "High" },
@@ -445,7 +446,7 @@ describe("session config options", () => {
(o: any) => o.id === "effort",
);
expect(effortOption).toBeUndefined();
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: undefined });
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
});
it("clamps effort in config_option_update when new model has different supported levels", async () => {
@@ -483,8 +484,8 @@ describe("session config options", () => {
(o: any) => o.id === "effort",
);
expect(effortOption).toBeDefined();
expect(effortOption.currentValue).toBe("high");
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "high" });
expect(effortOption.currentValue).toBe("default");
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
});
it("preserves effort in config_option_update when new model supports same level", async () => {
@@ -557,6 +558,31 @@ describe("session config options", () => {
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "low" });
});
it("calls applyFlagSettings with null effortLevel for 'default'", async () => {
// Set effort to a non-default value first
const session = (agent as unknown as { sessions: Record<string, any> }).sessions[SESSION_ID];
const effortOpt = session.configOptions.find((o: any) => o.id === "effort");
if (effortOpt) effortOpt.currentValue = "high";
await agent.setSessionConfigOption({
sessionId: SESSION_ID,
configId: "effort",
value: "default",
});
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
// The SDK's applyFlagSettings travels over a JSON pipe and only clears a
// flag-layer key when an explicit `null` is sent — `undefined` is
// dropped during JSON.stringify, which would leave the previous effort
// override in place. Round-trip the call args through JSON to make sure
// the key actually reaches the SDK.
const calls = applyFlagSettingsSpy.mock.calls;
const lastCallArgs = calls[calls.length - 1]?.[0];
const serialized = JSON.parse(JSON.stringify(lastCallArgs));
expect(serialized).toHaveProperty("effortLevel", null);
});
it("updates effort currentValue in returned configOptions", async () => {
const response = await agent.setSessionConfigOption({
sessionId: SESSION_ID,
@@ -663,7 +689,7 @@ describe("session config options", () => {
value: "claude-sonnet-4-6",
});
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: undefined });
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
});
it("adds effort option when switching to a model that supports effort", async () => {
@@ -696,8 +722,8 @@ describe("session config options", () => {
const effortOption = response.configOptions.find((o) => o.id === "effort");
expect(effortOption).toBeDefined();
// No previous effort, so defaults to "high" (the API default)
expect(effortOption?.currentValue).toBe("high");
// No previous effort, so defaults to "default" (no effort override)
expect(effortOption?.currentValue).toBe("default");
});
it("clamps effort to valid value when new model has different supported levels", async () => {
@@ -731,10 +757,10 @@ describe("session config options", () => {
const effortOption = response.configOptions.find((o) => o.id === "effort");
expect(effortOption).toBeDefined();
// "max" is not in sonnet's levels, so should fall back to "high" (the API default)
expect(effortOption?.currentValue).toBe("high");
// SDK should be told about the clamped value
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: "high" });
// "max" is not in sonnet's levels, so should fall back to "default" (no effort override)
expect(effortOption?.currentValue).toBe("default");
// SDK should be told to clear the effort override
expect(applyFlagSettingsSpy).toHaveBeenCalledWith({ effortLevel: null });
});
it("preserves effort value when new model supports the same level", async () => {

View File

@@ -53,6 +53,57 @@ describe("SettingsManager", () => {
expect(settings.model).toBe("claude-3-5-haiku");
});
it("should expose availableModels from settings", async () => {
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
await fs.promises.writeFile(
path.join(claudeDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-haiku-4-5", "claude-opus-4-7[1m]"],
}),
);
settingsManager = new SettingsManager(tempDir);
await settingsManager.initialize();
const settings = settingsManager.getSettings();
expect(settings.availableModels).toEqual(["claude-haiku-4-5", "claude-opus-4-7[1m]"]);
});
it("should union and dedupe availableModels across sources", async () => {
// Per Claude Code docs: "When `availableModels` is set at multiple
// levels, such as user settings and project settings, arrays are
// merged and deduplicated."
// https://code.claude.com/docs/en/model-config#merge-behavior
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
await fs.promises.writeFile(
path.join(claudeDir, "settings.json"),
JSON.stringify({
availableModels: ["claude-haiku-4-5", "claude-opus-4-7[1m]"],
}),
);
await fs.promises.writeFile(
path.join(claudeDir, "settings.local.json"),
JSON.stringify({
// claude-opus-4-7[1m] overlaps with project; should be deduped.
availableModels: ["claude-opus-4-7[1m]", "claude-sonnet-4-6[1m]"],
}),
);
settingsManager = new SettingsManager(tempDir);
await settingsManager.initialize();
const settings = settingsManager.getSettings();
expect(settings.availableModels).toEqual([
"claude-haiku-4-5",
"claude-opus-4-7[1m]",
"claude-sonnet-4-6[1m]",
]);
});
it("should merge permissions.defaultMode with later sources taking precedence", async () => {
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
@@ -90,4 +141,66 @@ describe("SettingsManager", () => {
expect(settings.permissions?.defaultMode).toBe("plan");
});
});
describe("escalating defaultMode trust filter", () => {
// The SDK's filterEscalatingDefaultMode drops escalating values
// (bypassPermissions / auto / acceptEdits) that came from a repo-committed
// tier (.claude/settings.json), preventing a checked-in file from
// silently escalating permissions. Local (.claude/settings.local.json)
// and user-tier sources are not committed by convention, so escalating
// values from those tiers are preserved.
it.each(["bypassPermissions", "auto", "acceptEdits"] as const)(
"drops %s when set in project-tier settings",
async (mode) => {
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
await fs.promises.writeFile(
path.join(claudeDir, "settings.json"),
JSON.stringify({ permissions: { defaultMode: mode } }),
);
settingsManager = new SettingsManager(tempDir);
await settingsManager.initialize();
expect(settingsManager.getSettings().permissions?.defaultMode).toBeUndefined();
},
);
it.each(["plan", "dontAsk"] as const)(
"preserves non-escalating %s from project-tier settings",
async (mode) => {
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
await fs.promises.writeFile(
path.join(claudeDir, "settings.json"),
JSON.stringify({ permissions: { defaultMode: mode } }),
);
settingsManager = new SettingsManager(tempDir);
await settingsManager.initialize();
expect(settingsManager.getSettings().permissions?.defaultMode).toBe(mode);
},
);
it("preserves escalating defaultMode when it comes from local-tier settings", async () => {
// settings.local.json is git-ignored by convention, so the trust
// filter does not apply.
const claudeDir = path.join(tempDir, ".claude");
await fs.promises.mkdir(claudeDir, { recursive: true });
await fs.promises.writeFile(
path.join(claudeDir, "settings.local.json"),
JSON.stringify({ permissions: { defaultMode: "acceptEdits" } }),
);
settingsManager = new SettingsManager(tempDir);
await settingsManager.initialize();
expect(settingsManager.getSettings().permissions?.defaultMode).toBe("acceptEdits");
});
});
});

View File

@@ -14,8 +14,14 @@ import { toAcpNotifications, ToolUseCache, Logger } from "../acp-agent.js";
import {
toolUpdateFromToolResult,
createPostToolUseHook,
createTaskHook,
toolInfoFromToolUse,
planEntries,
applyTaskCreate,
applyTaskUpdate,
parseTaskCreateOutput,
taskStateToPlanEntries,
TaskState,
} from "../tools.js";
describe("rawOutput in tool call updates", () => {
@@ -1541,3 +1547,404 @@ describe("toAcpNotifications - TodoWrite with undefined input regression", () =>
expect(planUpdates).toHaveLength(1);
});
});
describe("parseTaskCreateOutput", () => {
it("parses JSON-string content", () => {
const parsed = parseTaskCreateOutput(JSON.stringify({ task: { id: "1", subject: "X" } }));
expect(parsed).toEqual({ task: { id: "1", subject: "X" } });
});
it("parses array-of-text-block content", () => {
const parsed = parseTaskCreateOutput([
{ type: "text", text: JSON.stringify({ task: { id: "2", subject: "Y" } }) },
]);
expect(parsed).toEqual({ task: { id: "2", subject: "Y" } });
});
it("returns undefined for non-JSON content", () => {
expect(parseTaskCreateOutput("not json")).toBeUndefined();
expect(parseTaskCreateOutput([{ type: "text", text: "not json" }])).toBeUndefined();
});
it("returns undefined when task.id is missing", () => {
expect(parseTaskCreateOutput(JSON.stringify({ task: { subject: "X" } }))).toBeUndefined();
});
});
describe("applyTaskCreate / applyTaskUpdate", () => {
it("creates an entry on TaskCreate when both input and output are present", () => {
const state: TaskState = new Map();
applyTaskCreate(
state,
{ subject: "Write tests", description: "Cover Task* flow", activeForm: "Writing tests" },
{ task: { id: "1", subject: "Write tests" } },
);
expect(state.get("1")).toEqual({
subject: "Write tests",
status: "pending",
activeForm: "Writing tests",
description: "Cover Task* flow",
});
});
it("is a no-op when the output has no task ID", () => {
const state: TaskState = new Map();
applyTaskCreate(state, { subject: "X", description: "Y" }, undefined);
expect(state.size).toBe(0);
});
it("updates fields by task ID and keeps insertion order in plan entries", () => {
const state: TaskState = new Map();
applyTaskCreate(state, { subject: "A", description: "" }, { task: { id: "1", subject: "A" } });
applyTaskCreate(state, { subject: "B", description: "" }, { task: { id: "2", subject: "B" } });
applyTaskUpdate(state, { taskId: "1", status: "in_progress" });
expect(taskStateToPlanEntries(state)).toEqual([
{ content: "A", status: "in_progress", priority: "medium" },
{ content: "B", status: "pending", priority: "medium" },
]);
});
it("removes entries when status is 'deleted'", () => {
const state: TaskState = new Map();
applyTaskCreate(state, { subject: "A", description: "" }, { task: { id: "1", subject: "A" } });
applyTaskUpdate(state, { taskId: "1", status: "deleted" });
expect(state.size).toBe(0);
});
it("creates a placeholder entry when TaskUpdate carries a subject for an unseen task", () => {
const state: TaskState = new Map();
applyTaskUpdate(state, { taskId: "5", subject: "Late arrival", status: "in_progress" });
expect(state.get("5")).toEqual({
subject: "Late arrival",
status: "in_progress",
activeForm: undefined,
description: undefined,
});
});
it("skips TaskUpdate for an unseen task when no subject is available", () => {
const state: TaskState = new Map();
applyTaskUpdate(state, { taskId: "5", status: "in_progress" });
// Without a subject we'd render an empty-content plan entry, so the
// update is dropped instead of synthesizing a blank placeholder.
expect(state.has("5")).toBe(false);
});
});
describe("toAcpNotifications - Task* tools", () => {
const mockClient = {} as AgentSideConnection;
const mockLogger: Logger = { log: () => {}, error: () => {} };
it("suppresses tool_call for TaskCreate/TaskUpdate/TaskList/TaskGet on tool_use", () => {
const toolUseCache: ToolUseCache = {};
const taskState: TaskState = new Map();
const notifications = toAcpNotifications(
[
{ type: "tool_use", id: "1", name: "TaskCreate", input: { subject: "A", description: "" } },
{
type: "tool_use",
id: "2",
name: "TaskUpdate",
input: { taskId: "1", status: "in_progress" },
},
{ type: "tool_use", id: "3", name: "TaskList", input: {} },
{ type: "tool_use", id: "4", name: "TaskGet", input: { taskId: "1" } },
] as any,
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(notifications).toHaveLength(0);
expect(taskState.size).toBe(0);
});
it("emits a plan snapshot after a TaskCreate tool_result and accumulates state", () => {
const toolUseCache: ToolUseCache = {};
const taskState: TaskState = new Map();
toAcpNotifications(
[
{
type: "tool_use",
id: "1",
name: "TaskCreate",
input: { subject: "First", description: "" },
},
] as any,
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
const created = toAcpNotifications(
[
{
type: "tool_result",
tool_use_id: "1",
content: JSON.stringify({ task: { id: "1", subject: "First" } }),
is_error: false,
},
] as any,
"user",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(created).toHaveLength(1);
expect(created[0].update).toMatchObject({
sessionUpdate: "plan",
entries: [{ content: "First", status: "pending", priority: "medium" }],
});
// A second TaskCreate accumulates rather than replacing.
toAcpNotifications(
[
{
type: "tool_use",
id: "2",
name: "TaskCreate",
input: { subject: "Second", description: "" },
},
] as any,
"assistant",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
const second = toAcpNotifications(
[
{
type: "tool_result",
tool_use_id: "2",
content: JSON.stringify({ task: { id: "2", subject: "Second" } }),
is_error: false,
},
] as any,
"user",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(second[0].update).toMatchObject({
sessionUpdate: "plan",
entries: [
{ content: "First", status: "pending", priority: "medium" },
{ content: "Second", status: "pending", priority: "medium" },
],
});
});
it("emits a plan snapshot reflecting status changes after a TaskUpdate tool_result", () => {
const toolUseCache: ToolUseCache = {};
const taskState: TaskState = new Map([["1", { subject: "First", status: "pending" as const }]]);
toolUseCache["update-1"] = {
type: "tool_use",
id: "update-1",
name: "TaskUpdate",
input: { taskId: "1", status: "completed" },
};
const notifications = toAcpNotifications(
[
{
type: "tool_result",
tool_use_id: "update-1",
content: JSON.stringify({
success: true,
taskId: "1",
updatedFields: ["status"],
}),
is_error: false,
},
] as any,
"user",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(notifications).toHaveLength(1);
expect(notifications[0].update).toMatchObject({
sessionUpdate: "plan",
entries: [{ content: "First", status: "completed", priority: "medium" }],
});
});
it("suppresses TaskList and TaskGet tool_result without touching task state", () => {
const toolUseCache: ToolUseCache = {
"list-1": { type: "tool_use", id: "list-1", name: "TaskList", input: {} },
"get-1": { type: "tool_use", id: "get-1", name: "TaskGet", input: { taskId: "1" } },
};
const taskState: TaskState = new Map([
["1", { subject: "Existing", status: "in_progress" as const }],
]);
const notifications = toAcpNotifications(
[
{ type: "tool_result", tool_use_id: "list-1", content: "...", is_error: false },
{ type: "tool_result", tool_use_id: "get-1", content: "...", is_error: false },
] as any,
"user",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(notifications).toHaveLength(0);
expect(taskState.get("1")).toEqual({ subject: "Existing", status: "in_progress" });
});
it("does not apply TaskCreate/TaskUpdate when the tool_result reports an error", () => {
const toolUseCache: ToolUseCache = {
"create-1": {
type: "tool_use",
id: "create-1",
name: "TaskCreate",
input: { subject: "A", description: "" },
},
};
const taskState: TaskState = new Map();
const notifications = toAcpNotifications(
[
{
type: "tool_result",
tool_use_id: "create-1",
content: "task creation failed",
is_error: true,
},
] as any,
"user",
"test-session",
toolUseCache,
mockClient,
mockLogger,
{ taskState },
);
expect(notifications).toHaveLength(0);
expect(taskState.size).toBe(0);
});
});
describe("createTaskHook", () => {
it("registers a task on TaskCreated and fires onChange", async () => {
const taskState: TaskState = new Map();
let changes = 0;
const hook = createTaskHook({
taskState,
onChange: async () => {
changes++;
},
});
await hook(
{
hook_event_name: "TaskCreated",
task_id: "t-1",
task_subject: "Investigate flaky test",
task_description: "Repro and fix",
} as any,
undefined,
{ signal: new AbortController().signal },
);
expect(taskState.get("t-1")).toEqual({
subject: "Investigate flaky test",
status: "pending",
description: "Repro and fix",
});
expect(changes).toBe(1);
});
it("does not clobber an existing entry on TaskCreated", async () => {
const taskState: TaskState = new Map([
["t-1", { subject: "Investigate flaky test", status: "in_progress" as const }],
]);
let changes = 0;
const hook = createTaskHook({
taskState,
onChange: async () => {
changes++;
},
});
await hook(
{
hook_event_name: "TaskCreated",
task_id: "t-1",
task_subject: "Investigate flaky test",
} as any,
undefined,
{ signal: new AbortController().signal },
);
expect(taskState.get("t-1")?.status).toBe("in_progress");
expect(changes).toBe(0);
});
it("marks a task completed on TaskCompleted", async () => {
const taskState: TaskState = new Map([
["t-1", { subject: "Investigate flaky test", status: "in_progress" as const }],
]);
let changes = 0;
const hook = createTaskHook({
taskState,
onChange: async () => {
changes++;
},
});
await hook(
{
hook_event_name: "TaskCompleted",
task_id: "t-1",
task_subject: "Investigate flaky test",
} as any,
undefined,
{ signal: new AbortController().signal },
);
expect(taskState.get("t-1")?.status).toBe("completed");
expect(changes).toBe(1);
});
it("is a no-op for unrelated hook events", async () => {
const taskState: TaskState = new Map();
let changes = 0;
const hook = createTaskHook({
taskState,
onChange: async () => {
changes++;
},
});
await hook({ hook_event_name: "PostToolUse", tool_name: "Read" } as any, undefined, {
signal: new AbortController().signal,
});
expect(taskState.size).toBe(0);
expect(changes).toBe(0);
});
});

View File

@@ -14,6 +14,9 @@ import {
FileWriteInput,
GlobInput,
GrepInput,
TaskCreateInput,
TaskCreateOutput,
TaskUpdateInput,
TodoWriteInput,
WebFetchInput,
WebSearchInput,
@@ -367,6 +370,40 @@ export function toolInfoFromToolUse(
};
}
case "TaskCreate": {
const input = toolUse.input as TaskCreateInput | undefined;
return {
title: input?.subject ? `Create task: ${input.subject}` : "Create task",
kind: "think",
content: [],
};
}
case "TaskUpdate": {
const input = toolUse.input as TaskUpdateInput | undefined;
return {
title: input?.subject ? `Update task: ${input.subject}` : "Update task",
kind: "think",
content: [],
};
}
case "TaskList": {
return {
title: "List tasks",
kind: "think",
content: [],
};
}
case "TaskGet": {
return {
title: "Get task",
kind: "think",
content: [],
};
}
case "ExitPlanMode": {
const planInput = toolUse.input as { plan?: string } | undefined;
return {
@@ -669,6 +706,103 @@ export function planEntries(input: { todos: ClaudePlanEntry[] } | undefined): Pl
}));
}
/**
* Per-session task list accumulated from Task* tool calls (TaskCreate /
* TaskUpdate). The headless/SDK session emits these as incremental tool
* calls keyed by task ID, replacing the snapshot-style TodoWrite tool.
* Iteration order is insertion order (Map semantics), matching the order
* tasks are created.
*/
export type TaskEntry = {
subject: string;
status: "pending" | "in_progress" | "completed";
activeForm?: string;
description?: string;
};
export type TaskState = Map<string, TaskEntry>;
/**
* Best-effort parse of a TaskCreate tool_result content into the structured
* TaskCreateOutput. The SDK delivers tool outputs either as a string or as
* an array of TextBlockParam-like blocks containing JSON text; try both.
*/
export function parseTaskCreateOutput(content: unknown): TaskCreateOutput | undefined {
const tryParse = (text: string): TaskCreateOutput | undefined => {
try {
const parsed = JSON.parse(text);
if (
parsed &&
typeof parsed === "object" &&
parsed.task &&
typeof parsed.task.id === "string"
) {
return parsed as TaskCreateOutput;
}
} catch {
// ignore
}
return undefined;
};
if (typeof content === "string") {
return tryParse(content);
}
if (Array.isArray(content)) {
for (const block of content) {
if (block && typeof block === "object" && "type" in block && block.type === "text") {
const text = (block as { text?: unknown }).text;
if (typeof text === "string") {
const parsed = tryParse(text);
if (parsed) return parsed;
}
}
}
}
return undefined;
}
export function applyTaskCreate(
state: TaskState,
input: TaskCreateInput | undefined,
output: TaskCreateOutput | undefined,
): void {
const taskId = output?.task?.id;
if (!taskId || !input) return;
state.set(taskId, {
subject: input.subject,
status: "pending",
activeForm: input.activeForm,
description: input.description,
});
}
export function applyTaskUpdate(state: TaskState, input: TaskUpdateInput | undefined): void {
if (!input?.taskId) return;
if (input.status === "deleted") {
state.delete(input.taskId);
return;
}
const existing = state.get(input.taskId);
// Without a subject from either the existing entry or the update payload,
// we'd produce a plan entry with empty `content` — drop the update.
const subject = input.subject ?? existing?.subject;
if (!subject) return;
state.set(input.taskId, {
subject,
status: input.status ?? existing?.status ?? "pending",
activeForm: input.activeForm ?? existing?.activeForm,
description: input.description ?? existing?.description,
});
}
export function taskStateToPlanEntries(state: TaskState): PlanEntry[] {
return Array.from(state.values()).map((task) => ({
content: task.subject,
status: task.status,
priority: "medium",
}));
}
export function markdownEscape(text: string): string {
let escape = "```";
for (const [m] of text.matchAll(/^```+/gm)) {
@@ -797,3 +931,38 @@ export const createPostToolUseHook =
}
return { continue: true };
};
/**
* Hook callback for `TaskCreated` / `TaskCompleted` events. The SDK fires
* these for both user-facing TaskCreate tool calls and subagent task
* creation, giving us `task_id` + `task_subject` without having to parse
* tool_result payloads.
*
* Populating `taskState` from the hook means a later `TaskUpdate` (which
* typically only carries `taskId` + `status`) finds an existing entry with
* a real subject, instead of synthesizing a placeholder with empty content.
*/
export const createTaskHook =
(options: { taskState: TaskState; onChange?: () => Promise<void> }): HookCallback =>
async (input): Promise<{ continue: boolean }> => {
const taskId =
"task_id" in input && typeof input.task_id === "string" ? input.task_id : undefined;
if (!taskId) return { continue: true };
if (input.hook_event_name === "TaskCreated") {
if (!input.task_subject) return { continue: true };
if (options.taskState.has(taskId)) return { continue: true };
options.taskState.set(taskId, {
subject: input.task_subject,
status: "pending",
description: input.task_description,
});
if (options.onChange) await options.onChange();
} else if (input.hook_event_name === "TaskCompleted") {
const existing = options.taskState.get(taskId);
if (!existing || existing.status === "completed") return { continue: true };
options.taskState.set(taskId, { ...existing, status: "completed" });
if (options.onChange) await options.onChange();
}
return { continue: true };
};

View File

@@ -2,9 +2,7 @@
import { Readable, Writable } from "node:stream";
import { WritableStream, ReadableStream } from "node:stream/web";
import { readFileSync } from "node:fs";
import { Logger } from "./acp-agent.js";
import { ClaudeCodeSettings, getManagedSettingsPath } from "./settings.js";
// Useful for bridging push-based and async-iterator-based code.
export class Pushable<T> implements AsyncIterable<T> {
@@ -89,19 +87,3 @@ export function unreachable(value: never, logger: Logger = console) {
export function sleep(time: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}
export function loadManagedSettings(): ClaudeCodeSettings | null {
try {
return JSON.parse(readFileSync(getManagedSettingsPath(), "utf8")) as ClaudeCodeSettings;
} catch {
return null;
}
}
export function applyEnvironmentSettings(settings: ClaudeCodeSettings): void {
if (settings.env) {
for (const [key, value] of Object.entries(settings.env)) {
process.env[key] = value;
}
}
}