fix: update to @anthropic-ai/claude-agent-sdk 0.3.323 (#1612)

## Summary

Updates `@anthropic-ai/claude-agent-sdk` from 0.3.322 to 0.3.323, which
bundles Claude Code 2.1.323. The new version needs two adapter fixes,
both included here:

- the session index reads titles by the SDK's new rule;
- the heartbeat of a long-running hook no longer lands on a tool call.

Also bumps these dev dependencies: `@anthropic-ai/sdk` 1.7.0 → 1.8.0,
`@tsconfig/node22` 22.0.6 → 22.0.7 and `prettier` 3.9.10 → 3.10.1.

`npm update` brings the in-range lockfile bumps: `rolldown` 1.2.18 →
1.2.19, `vite` 8.3.7 → 8.3.8, `hono` 4.13.17 → 4.13.18,
`@hono/node-server` 2.1.6 → 2.1.8, and patch releases of
`@babel/runtime`, `brace-expansion`, `encodeurl`,
`@jridgewell/resolve-uri` and `@rolldown/pluginutils`. `rolldown` 1.2.19
lists its wasm32 binding as an optional dependency, so the lockfile
gains `@rolldown/binding-wasm32-wasi`, `@napi-rs/wasm-runtime`,
`@emnapi/core` and `@emnapi/runtime` (2.0.0-beta.2),
`@emnapi/wasi-threads`, `@tybys/wasm-util` and `tslib`. All are dev-only
and optional.

TypeScript stays at 6.0.3: 7.0.2 is out, but `@typescript-eslint/parser`
8.72.2 still requires `typescript <6.1.0`.

## Session titles

`getSessionInfo` and `listSessions` of SDK 0.3.323 read titles
differently, and neither changelog says so. The session index mirrors
the SDK's rule (`sdkTitles` in `src/session-index/transcript-scan.ts`),
so it has to follow.

- Before: the SDK took the last `"customTitle":"…"` text of the tail or
the head, wherever it stood.
- Now: it parses each line that names the field, from the last one back,
and takes the top-level string of the record. For `customTitle` and
`aiTitle` the record must be of type `custom-title` or `ai-title`.
`lastPrompt` and `summary` may be on a record of any type.

What changes for a client:

- A `customTitle` or `lastPrompt` inside a message is no title. A tool
result that shows a transcript no longer renames the session.
- A `custom-title` record that the start of the 64 KB tail window cuts
is not read. The old rule still found its title text.
- A `custom-title` record appended to a line whose write was cut off is
read.

Checked against the real SDK: a differential run of `sdkTitles` against
`getSessionInfo` over 1,800 generated transcripts gave no mismatch. The
old rule mismatched on 371 of 600. The run is not part of this PR; the
tests in `src/tests/v1/session-index.test.ts` compare each case above
with `getSessionInfo`.

## Hook heartbeats

Since Claude Code 2.1.323, a hook that runs long sends a `tool_progress`
beat every 30 seconds. Recorded from the real CLI:

```json
{
  "type": "tool_progress",
  "tool_use_id": "hook-PreToolUse-toolu_01X…-heartbeat-0",
  "tool_name": "PreToolUse",
  "parent_tool_use_id": "toolu_01X…",
  "elapsed_time_seconds": 30,
  "heartbeat": true
}
```

For a PreToolUse or PostToolUse hook, `parent_tool_use_id` is the tool
call. For a UserPromptSubmit or Stop hook it is an id that names no tool
call. The PostToolUse beats come before the tool result.

Both cores send a beat under an unknown id to the call that
`parent_tool_use_id` names. So without a change, a slow PreToolUse or
PostToolUse hook:

- marked the tool call `in_progress` before it ran, on v1 and v2;
- restarted its elapsed time with the hook's (30 → 1 → 2 → 3 → 30 in the
scenario);
- named the call `PreToolUse` in `_meta.claudeCode.toolName` for a plain
or Zed client.

The adapter now drops these beats. This matches how it already ignores
`hook_started`, `hook_progress` and `hook_response`: hooks have no ACP
surface. The rule that picks the tool call of a beat was written twice,
in `src/v1/acp-agent.ts` and `src/v2/stream.ts`. It is now one function,
`progressToolCallId` in `src/tool-calls/renderer.ts`, which holds the
new rule too.

The `tool-progress` scenario carries a PreToolUse and a PostToolUse
beat. Its recordings for the plain, Zed, AIR and v2 clients are
unchanged, and all four fail without the fix.

## Changelog review

Read the SDK changelog for 0.3.323 and the Claude Code changelog for
2.1.323. No other entry needs a code change:

- **Types.** Doc text and new optional members only, plus one new
`SDKStartupFailureReason` (`managed_settings_not_delivered`).
`startupFailureError` treats a reason it does not list as an internal
error with the CLI's message.
- **Context window of an unknown model id**, 200K → 1M. The adapter
reads the window from Claude Code (`getContextUsage`) at session
creation and at every model switch, so it reports the new value. Its own
200K is only the placeholder until that read lands.
- **`canUseTool` suggestions** no longer hold a Bash allow rule for a
write to `.git` or `.claude`. The adapter then offers no "always allow"
option.
- **`init.plugin_errors`** also lists load errors of plugins that
managed settings enable. The adapter only logs them.
- **`forkSession` with `upToMessageId`** and **`getSessionMessages`**
compaction fixes. Picked up as they are: the adapter has no workaround
for the old behavior.
- **A subagent's WebSearch result can carry `tool_use_result`.** The
code reads only fields that this result does not have. One comment in
`src/async-tasks.ts` said that no subagent result carries one; it now
says Bash results.
- **A permission check that fails after a PreToolUse hook allowed the
call** goes to `canUseTool`, which the adapter always provides.

## Not in this PR

Left for follow-ups, because each is its own change:

- The 200K placeholder for the context window. 1M is now the more likely
value, and with it the "1m" text heuristic has nothing left to do.
- `matchDeniedModels()` (alpha). The adapter's own `isDeniedModel`
disagrees with it on 23 of 875 model and entry pairs.
- `supersedes` on the retry of a response whose tool call could not be
parsed. The adapter does not act on `supersedes`.
- `machinePolicy.didNotLoad` (alpha) of `resolveSettings()`.

## Testing

- `npm run build`, `npm run check`, `npm run test:run` (4,121 tests
pass)
- Real CLI: one short session with slow hooks and
`CLAUDE_CODE_HOOK_HEARTBEAT_INTERVAL_MS=1000`, to record the heartbeat
frames and their order
- Real CLI: `getContextUsage` for unknown and known model ids, without a
prompt
This commit is contained in:
Ben Brandt
2026-11-13 13:56:39 +01:00
committed by GitHub
parent 4804917a6f
commit ddee24ef8f
9 changed files with 430 additions and 189 deletions

372
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "2.6.0",
"@anthropic-ai/claude-agent-sdk": "0.3.322",
"@anthropic-ai/claude-agent-sdk": "0.3.323",
"diff": "9.0.0",
"zod": "4.6.7"
},
@@ -18,9 +18,9 @@
"claude-agent-acp": "dist/index.js"
},
"devDependencies": {
"@anthropic-ai/sdk": "1.7.0",
"@anthropic-ai/sdk": "1.8.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.6",
"@tsconfig/node22": "22.0.7",
"@types/node": "26.6.6",
"@typescript-eslint/eslint-plugin": "8.72.2",
"@typescript-eslint/parser": "8.72.2",
@@ -29,7 +29,7 @@
"eslint": "10.14.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.14.0",
"prettier": "3.9.10",
"prettier": "3.10.1",
"typescript": "6.0.3",
"vitest": "5.1.0"
},
@@ -53,22 +53,22 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.322.tgz",
"integrity": "sha512-r88ij7R3SPV2j3BtFuavgrbWZNE8SVVS42AOQ1KxlS+QnSwNNnUhmVXV3Es2dniZySBYX8ENebL43ffhiB0azw==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.323.tgz",
"integrity": "sha512-GXIczMzzceNWB+bIImK59hWOQoBWZtSw+AESPc/qbAWmmHM8Wrqb5KvTwWZ07CmjyC/KcPC47nOLxxyHlWqKPA==",
"license": "SEE LICENSE IN README.md",
"engines": {
"node": ">=18.14.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.322",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.322",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.322"
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.323",
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.323",
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.323",
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.323",
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.323",
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.323",
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.323",
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.323"
},
"peerDependencies": {
"@anthropic-ai/sdk": ">=0.93.0",
@@ -77,9 +77,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.322.tgz",
"integrity": "sha512-LoKkldW2zkLfaOLQD44mL1Ot4Aup8dBIo356LEeO3v2vF7JVNhMllQIXhhGpbYRz57QaYsGvOe0Kb4SbssHwcg==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.323.tgz",
"integrity": "sha512-hWkXHsSMIv+78duw3mDi0bndCpWquGuMrCX6C7G9LzEtpcd+ZvzI5ZE4E6SwqX1bSy8rxhEg+hVJSTk4ABoxpA==",
"cpu": [
"arm64"
],
@@ -90,9 +90,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.322.tgz",
"integrity": "sha512-rTvLS9G1OzjMIaSXrJglQtmRtVcaRmMbJQC+ND02+atCA463ac7Ty/Wc6tyzBGk8zFFGKWnfAhKtLZTNg91eMg==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.323.tgz",
"integrity": "sha512-JHtMk+FBRQPvH046dUIda6s6bza8CRQkaAdy96JwV1zqgi3mtlAg5yXFxPA/QNoiHz5Ul225wCZr9lR7gbwkMg==",
"cpu": [
"x64"
],
@@ -103,9 +103,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.322.tgz",
"integrity": "sha512-H1v33+MxB5JMPlnJLZffiOQ+6muxyA7tgHPyYQ+So4TH7uRhaPcjv2X03CUo5U9gXleO6u+X8ngNNDRFtfUaSg==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.323.tgz",
"integrity": "sha512-ONhCJapk9UxpaZ3HY1GTZezMoiQ6Ab7fIubS+dJeXIyj8aQjGQALHQoEl9D1Hrr+q5ppZPOAt0hg69x8aGbTBA==",
"cpu": [
"arm64"
],
@@ -119,9 +119,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.322.tgz",
"integrity": "sha512-zvgvVA/Mxpu1QttICZ1DGDlmgGo62HdVgKg6WbDGDQ3dgxtRC/kpCIepjtGEL8O1iO5NRxQ4mv8okLwCFcyrLQ==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.323.tgz",
"integrity": "sha512-N+ZTBRQj8Ew3/eLCZE7JADceyN6Yi1CncJAdzknF5RL9dckIujv4B7C9neBNOXdo+bGFEysjPcUbVsaRkPguKQ==",
"cpu": [
"arm64"
],
@@ -135,9 +135,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.322.tgz",
"integrity": "sha512-M/i7uV5wQf1kvjiPJr2KF5PeA9BpftUpMTtwE+DpQ0Tj+scthQXSMsEhDvau/Rh/MfFOc+UHED4tltignmA5+Q==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.323.tgz",
"integrity": "sha512-9M0rnLcCa9sJdZoRCPbjyMOv3raoJm39u348t2ozCgE35Y9e/jGgxy+rG2jfRTaCBILDDrR57ClRQVRlpNeLpQ==",
"cpu": [
"x64"
],
@@ -151,9 +151,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.322.tgz",
"integrity": "sha512-jprUZMcQ3XcpoxwKRQ0fi3qAM1zYcytmNGhlD0gWDknbbSu8di5jLU7231ihJrCr4gbbtDu3FiFUd6w9EBEjfw==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.323.tgz",
"integrity": "sha512-QMaFszyJraPnZyJTy6K9vr1wVA/4MIjWSFcaByGv/x3Hk/F7smsMnqpuATO6M6bRsev+IVPj0p6rsdpYAdt1yA==",
"cpu": [
"x64"
],
@@ -167,9 +167,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.322.tgz",
"integrity": "sha512-o5sPmBKT4NOglODC45wSTFmjD1AQIboC9NdGFQFOTa8kMF3bok+YqpB0DF81RmMDf3iR5tieonZvOXdYm1TDjQ==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.323.tgz",
"integrity": "sha512-FS+eYSMU/x8Hmy98zypoOw9/+jMgtGmBEyYmE+jJUCdb3a4k1pwcx08XGici/hnBR3xiKK65ifxwVg2oNyZocQ==",
"cpu": [
"arm64"
],
@@ -180,9 +180,9 @@
]
},
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
"version": "0.3.322",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.322.tgz",
"integrity": "sha512-gONOkYx76of3p73opgWAT9gqpV7PRL8+az3vKoWXLc4xaDW6vhZF7fixpCnLDeUmPWxjXhnQxf30WGpKneTzNw==",
"version": "0.3.323",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.323.tgz",
"integrity": "sha512-mW2h1o0Kt92AKZBr05AwE3izJTAYpWNUurOppPdzH/hditbT1zwc0tAJ3p+G+XMkqUWenJNbpunVG0utfQ2hmA==",
"cpu": [
"x64"
],
@@ -193,9 +193,9 @@
]
},
"node_modules/@anthropic-ai/sdk": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.7.0.tgz",
"integrity": "sha512-Z+0c1iHQozlt4ymMHapPXEXfFnSbPEWGnSKbkavt+qeyaMQ3Sd6QNxfQj7vyhHYqiqYadC9shrXub8ISKf7xjQ==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-1.8.0.tgz",
"integrity": "sha512-yDOArk2+Qpd/djEKmZjrv7JZ+pn5iBHARJ9PAdvfhGf4H5wNqTgY/2K0/yDKdQdm8G8hrNQchMlBAHpPK7yBfg==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api": "^1.4.1",
@@ -215,9 +215,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.29.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.13.tgz",
"integrity": "sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ==",
"version": "7.29.14",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.14.tgz",
"integrity": "sha512-Y5tqevQaUAZhhj82BpYMNC1ssvLQTVAfwKdm8iy8qwmhvC2/CpcAu8OC+QEtjM7pMk7knx83u1Qj8gzkicyelw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -247,6 +247,43 @@
"keyv": "^5.6.0"
}
},
"node_modules/@emnapi/core": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-beta.2.tgz",
"integrity": "sha512-MzCVoWtcg4Pm2B3je5mMvYMvj3MmBKw+fU7vbsJc9/iT8ip6/eYIHo/Dsm/mr+wsHB6SEyJDuxB09Yy6r/8+4A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/wasi-threads": "2.2.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-beta.2.tgz",
"integrity": "sha512-JcrSPzPvZuNZvJqPTF9Fw25LbOJ8avzFVlaf2grmH4Cku0oGP47tRgqPZadcfj3YoZMHkFVnCYFbQM8I+i5QEw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.2.0.tgz",
"integrity": "sha512-JP7OYH38Q7+pyTAe14raZHLQIK7BDKbdwji+Xi3pcd/6QkJYQH3SX4wu6EplGrGJWRepoNzedfm0G3B9Z4e9Rw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
@@ -363,9 +400,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.6.tgz",
"integrity": "sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.8.tgz",
"integrity": "sha512-jdf7FHh6A1lzjpHHQUgxsg2GeER2vMetnAOfeActzcoli6jamlK+NAwL6Dsr5Fl3BS3tYUQzCdtZjU8Qxv7bEA==",
"license": "MIT",
"peer": true,
"engines": {
@@ -442,9 +479,9 @@
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.3.tgz",
"integrity": "sha512-lhTtjtoEArWBjismtfS761S9Hlyn+nIZqOZVYoZ1Xn82PMNMVnk+jj/NhC4fAXGTs7ydONcJiR5vyQBTMDu27g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -534,6 +571,29 @@
}
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.6.tgz",
"integrity": "sha512-mRyDmEw9/fCDXCGfL1sbgIw/GUK0PVxRW4YwgPN61F3LQ+fSyoBFarJVzgGyhF6kRzEQZ0FejDqm4WLXSeqQmw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.3"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1 || 2.0.0-beta.2",
"@emnapi/runtime": "^1.7.1 || 2.0.0-beta.2"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.10.0.tgz",
@@ -555,9 +615,9 @@
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz",
"integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.19.tgz",
"integrity": "sha512-e+Ebe0WT+9tbXmNKPIiC8YrWAHYwug3m6i4p3rZGGulfZri1jtI4HslT+OZnFf/OIHowWF71DTlHwXBP2SiA7A==",
"cpu": [
"arm"
],
@@ -573,9 +633,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz",
"integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.19.tgz",
"integrity": "sha512-hOombrTJ+szt2ciEzS7oCA78lMPBgF5UMQWCJIyNfnrI7GLl8HYoFqCnpL2gcPOaXMqryGCU5ExdQKyDDcC+yA==",
"cpu": [
"arm64"
],
@@ -591,9 +651,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz",
"integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.19.tgz",
"integrity": "sha512-XoMA2pwCKGTQzUjPgtRwkedH4WmXhdIPKT6l6oOxd1PUbNodGI6DcBekgN7EC//+x3pfBYX/qL1gNmZAqivw5g==",
"cpu": [
"arm64"
],
@@ -609,9 +669,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz",
"integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.19.tgz",
"integrity": "sha512-zo8EsSSwMZCY/e+fRnGaX02YGgvJD0yvhNi/9hpGPA2MJ9ZF8dPCw75UGfpI6OpQ/myR/dIYmogRvqiZD0TrXg==",
"cpu": [
"x64"
],
@@ -627,9 +687,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz",
"integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.19.tgz",
"integrity": "sha512-O8ISAOfI1nj7AuBPd30FSF47z7f7lzlk5poQVGgiikvQuGEBFYvW8ki+UEnT+YJPnNGjQzn304klt4O9FUE7mA==",
"cpu": [
"x64"
],
@@ -645,9 +705,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz",
"integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.19.tgz",
"integrity": "sha512-/lYCba5NO0yq4S0jGMH8QTBtkvQxoXjoQWzF5BcWp9MjB9jxVRD1GQDNofkVjcOj30DWyhLIN/4Oe60JKzaONw==",
"cpu": [
"arm"
],
@@ -663,9 +723,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz",
"integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.19.tgz",
"integrity": "sha512-pm/apFMBnSbFb3O6nG5pHO1iqHhZrjGDFEjSm+ot9J17XXPvH9L90y5h+jgfHCE+Qdlsvy8qKEcU4VKxgjD+vA==",
"cpu": [
"arm64"
],
@@ -684,9 +744,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz",
"integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.19.tgz",
"integrity": "sha512-K1SmQp3FFichn8oqrNIwvFoZ5wcvjC7goBzpaMNvLf9FZc+QB/QvLmnpJZgAAGHiD980tP8FZZnXk2K94rvlxg==",
"cpu": [
"arm64"
],
@@ -705,9 +765,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz",
"integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.19.tgz",
"integrity": "sha512-I0xwD57jP/3A7+5UPZin2w8Y7XHEmEM5zqpYZOcQ45MlNW6tUJUziymV3yRr7rL4vwyfEcRsRksC4a7Kc08p5Q==",
"cpu": [
"ppc64"
],
@@ -726,9 +786,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz",
"integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.19.tgz",
"integrity": "sha512-BrtEHT24SNXj9HiXqy+s/kXiSiQGt6EKCPUlRirAoIioVxKGnbXBUEkbZASAei7iZJB0/Ynm+0JP00Ipx7AnSw==",
"cpu": [
"s390x"
],
@@ -747,9 +807,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz",
"integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.19.tgz",
"integrity": "sha512-rm75/9DkHKeU2WnA8w4X94WGYgLlDCFCfkQC+4bguVJmC/jvDRv2B+2wLOYhyE0fVuv7//Ckk39OoOufyCu76Q==",
"cpu": [
"x64"
],
@@ -768,9 +828,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz",
"integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.19.tgz",
"integrity": "sha512-irh/4+iYFM4GAzpluDErgjz89bj6NDtqqMAG/6O1LG38Ed3NpyebzCpcVNkFW8uhy+/3/BocUvnXDR33CXWe9A==",
"cpu": [
"x64"
],
@@ -789,9 +849,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz",
"integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.19.tgz",
"integrity": "sha512-+KWTWNoPt+CfSq94CiceWu3yPE+QzkaG+phPyz9G60tmZLwfQkcA4AeksafzvJAw4pvg1mcd5a1TkUqVv0s4JA==",
"cpu": [
"arm64"
],
@@ -806,10 +866,30 @@
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.19.tgz",
"integrity": "sha512-7EH+skOsl5qpWnK0vzamgJFhOv1cwxQpsxm8tI883K9fur5c27p8q87fzEO6BbBPabA2/+s/zR63/pLc137iOg==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/core": "2.0.0-beta.2",
"@emnapi/runtime": "2.0.0-beta.2",
"@napi-rs/wasm-runtime": "1.2.6"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz",
"integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.19.tgz",
"integrity": "sha512-3Q1Yg1Q7tEvBWoZf5rFvG/GKZQPgwfru+p4wBajDfJFBs0SHCpL++dfTjW9gyAKd1T7ZpIYvdaUax7Pa/IhFVQ==",
"cpu": [
"arm64"
],
@@ -825,9 +905,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz",
"integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.19.tgz",
"integrity": "sha512-y/sqRRc6d7hECzOFc5n3LSeqctmIp9mMjY1Kq4s14uLPd/LZ4z7i1zrW6fMtp2mPeuQIC4e6ULMcwQzMomqQag==",
"cpu": [
"x64"
],
@@ -843,9 +923,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.0.tgz",
"integrity": "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.1.tgz",
"integrity": "sha512-glFIJDvLNDtJhT9qrMFWLPbvS+skxgt4L0K8Ei2YtnhPysAJnsI7nyxSJxKj4ADeKX0OLjC4ntRkwpLOoS3rwA==",
"dev": true,
"license": "MIT",
"peer": true
@@ -857,12 +937,24 @@
"license": "MIT"
},
"node_modules/@tsconfig/node22": {
"version": "22.0.6",
"resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.6.tgz",
"integrity": "sha512-/5thavHAnWDZwH2H6eEn/T8pBEBhtuKaWGMslsj82kJfvzQgOgEMK7X4goBbIUjgVtAzPTtM9Ml64LA0uxO6Iw==",
"version": "22.0.7",
"resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.7.tgz",
"integrity": "sha512-xnYM5q+HTeLm+jRBIB7Mu61OyOmNpgzPmJuhwPODSyITDPIWVK2TXxn/+O5fJ4n/P0u/gPsgSyPw/Dl3Cz/L4g==",
"dev": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
"integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -1313,9 +1405,9 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"version": "5.0.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.13.tgz",
"integrity": "sha512-Vh3dmFbc4wZeox7M9I/0L9GfFd83Su4+yCck/OF9Gi/P/dAHn0RT7X4K9R7gMNMhDck3DvhR6QXsnQdjjgwNvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1543,9 +1635,9 @@
"peer": true
},
"node_modules/encodeurl": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz",
"integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.3.tgz",
"integrity": "sha512-mIteYONiEfc4xWf2F1WXovh1c8npzS00vNeiUPdwFF+QlVJni+gNvpQltLMQhob8rew/bWWc/bGTuUr0EUGt0w==",
"license": "MIT",
"peer": true,
"engines": {
@@ -2270,9 +2362,9 @@
}
},
"node_modules/hono": {
"version": "4.13.17",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.17.tgz",
"integrity": "sha512-vZIcbkvjOR5hHRWERbGkr8fGfF0GGM1ooYkuH448iO3b2AdVEr3AXZSw4Z3Kn+1L5z0CVoVeyV29VGYmFh05kQ==",
"version": "4.13.18",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.18.tgz",
"integrity": "sha512-H9q/E1XizaWstJxUW881dmLxEj+t/TrGGnyxINkm4+sK0qUwsMtFdo2BWI9Vs3cIS19MU6b6iCijoUoEf4f3DA==",
"license": "MIT",
"peer": true,
"engines": {
@@ -3155,9 +3247,9 @@
}
},
"node_modules/prettier": {
"version": "3.9.10",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
"integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.10.1.tgz",
"integrity": "sha512-70vp3ZUYoPlFbF9uiTdXHUUCGEIp7d4+6p+8DYbkm2jP7x/3i/paXVF6dopSI3zd/CEitrZN1rc4i85xWmmXlg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3275,9 +3367,9 @@
}
},
"node_modules/rolldown": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz",
"integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.19.tgz",
"integrity": "sha512-QIiV1J9d0pzN4jeeTH4sjPy0AZ6cAy2cG0ExeoE1+U8Rvj+hR3gYkokx0YbeUNGlZqiQJJ2utcETe0oKXwzsuA==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -3292,21 +3384,22 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.18",
"@rolldown/binding-android-arm64": "1.2.18",
"@rolldown/binding-darwin-arm64": "1.2.18",
"@rolldown/binding-darwin-x64": "1.2.18",
"@rolldown/binding-freebsd-x64": "1.2.18",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.18",
"@rolldown/binding-linux-arm64-gnu": "1.2.18",
"@rolldown/binding-linux-arm64-musl": "1.2.18",
"@rolldown/binding-linux-ppc64-gnu": "1.2.18",
"@rolldown/binding-linux-s390x-gnu": "1.2.18",
"@rolldown/binding-linux-x64-gnu": "1.2.18",
"@rolldown/binding-linux-x64-musl": "1.2.18",
"@rolldown/binding-openharmony-arm64": "1.2.18",
"@rolldown/binding-win32-arm64-msvc": "1.2.18",
"@rolldown/binding-win32-x64-msvc": "1.2.18"
"@rolldown/binding-android-arm-eabi": "1.2.19",
"@rolldown/binding-android-arm64": "1.2.19",
"@rolldown/binding-darwin-arm64": "1.2.19",
"@rolldown/binding-darwin-x64": "1.2.19",
"@rolldown/binding-freebsd-x64": "1.2.19",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.19",
"@rolldown/binding-linux-arm64-gnu": "1.2.19",
"@rolldown/binding-linux-arm64-musl": "1.2.19",
"@rolldown/binding-linux-ppc64-gnu": "1.2.19",
"@rolldown/binding-linux-s390x-gnu": "1.2.19",
"@rolldown/binding-linux-x64-gnu": "1.2.19",
"@rolldown/binding-linux-x64-musl": "1.2.19",
"@rolldown/binding-openharmony-arm64": "1.2.19",
"@rolldown/binding-wasm32-wasi": "1.2.19",
"@rolldown/binding-win32-arm64-msvc": "1.2.19",
"@rolldown/binding-win32-x64-msvc": "1.2.19"
}
},
"node_modules/router": {
@@ -3601,6 +3694,15 @@
"typescript": ">=4.8.4"
}
},
"node_modules/tslib": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
"integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
"dev": true,
"license": "0BSD",
"optional": true,
"peer": true
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -3699,9 +3801,9 @@
}
},
"node_modules/vite": {
"version": "8.3.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz",
"integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==",
"version": "8.3.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.8.tgz",
"integrity": "sha512-LzOn3h9rG/0vhLdXrV4yJ4QFA9eEoqCl+Yx+kGJd7aB9w6nfNgVHKSYwLPZUHrz4ZGa+t+kXjV9itvcHDLQouA==",
"dev": true,
"license": "MIT",
"peer": true,

View File

@@ -69,14 +69,14 @@
},
"dependencies": {
"@agentclientprotocol/sdk": "2.6.0",
"@anthropic-ai/claude-agent-sdk": "0.3.322",
"@anthropic-ai/claude-agent-sdk": "0.3.323",
"diff": "9.0.0",
"zod": "4.6.7"
},
"devDependencies": {
"@anthropic-ai/sdk": "1.7.0",
"@anthropic-ai/sdk": "1.8.0",
"@eslint/js": "10.0.1",
"@tsconfig/node22": "22.0.6",
"@tsconfig/node22": "22.0.7",
"@types/node": "26.6.6",
"@typescript-eslint/eslint-plugin": "8.72.2",
"@typescript-eslint/parser": "8.72.2",
@@ -85,7 +85,7 @@
"eslint": "10.14.0",
"eslint-config-prettier": "10.1.8",
"globals": "17.14.0",
"prettier": "3.9.10",
"prettier": "3.10.1",
"typescript": "6.0.3",
"vitest": "5.1.0"
}

View File

@@ -879,7 +879,7 @@ function recordPublished(task: AsyncTask, fields: Record<string, unknown>): void
* its tool result. The text of a tool result never gives a task id: a
* foreground command can print any text.
*
* Claude Code sends no `tool_use_result` with a subagent's tool results. For
* Claude Code sends no `tool_use_result` with a subagent's Bash results. For
* those, `startedTaskOf` gives the background `local_bash` task whose
* `task_started` named the Bash call, which comes before the result.
*/

View File

@@ -550,7 +550,55 @@ function stringFields(text: string, key: string): string[] {
return values;
}
const lastField = (text: string, key: string) => stringFields(text, key).at(-1) || undefined;
/** The string `field` of the JSON record `line`, which is of `type` when one
* is given. `null` when the line is no JSON. */
function recordField(line: string, field: string, type?: string): string | null | undefined {
let record: unknown;
try {
record = JSON.parse(line);
} catch {
return null;
}
if (typeof record !== "object" || record === null) return undefined;
if (type !== undefined && (record as Entry).type !== type) return undefined;
const value = (record as Entry)[field];
return typeof value === "string" ? value : undefined;
}
/**
* The top-level string `field` of the last record of `text` that has one,
* as the SDK reads a session's metadata since 0.3.323: it parses each line
* that names the field, from the last one back. A `"customTitle":"…"` inside
* a message, such as a tool result that shows a transcript, is no title.
*
* With `type`, only a record of that `type` counts, and a line that names
* the type but is no JSON is read from its last `{"type":"<type>"` that does
* not follow `:`, `,` or `[`: a record appended to a line whose write was cut
* off. The record that the start of the tail window cuts has no such start
* in the window, and is not read.
*
* Undefined when the field of that record is empty: older records are not
* searched.
*/
function lastRecordField(text: string, field: string, type?: string): string | undefined {
const fieldMarker = `"${field}":`;
const typeMarkers = type === undefined ? undefined : [`"type":"${type}"`, `"type": "${type}"`];
const lines = text.split("\n");
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i]!;
if (!line.includes(fieldMarker)) continue;
if (typeMarkers && !typeMarkers.some((marker) => line.includes(marker))) continue;
let value = recordField(line, field, type);
if (value === null && typeMarkers) {
const start = line.lastIndexOf(`{${typeMarkers[0]}`);
if (start > 0 && !":,[".includes(line[start - 1]!)) {
value = recordField(line.slice(start), field, type);
}
}
if (typeof value === "string") return value || undefined;
}
return undefined;
}
/** Whether the first record of the transcript is a sidechain one, which the
* SDK never lists. */
@@ -560,9 +608,10 @@ export function isSidechainTranscript(head: string): boolean {
return first.includes('"isSidechain":true') || first.includes('"isSidechain": true');
}
/** The last `customTitle` of the tail, as the SDK reads it. */
/** The title of the last `custom-title` record of the tail, as the SDK reads
* it (see {@link lastRecordField}). */
export function tailCustomTitle({ tail, sdkTail }: HeadTail): string | undefined {
return lastField(sdkTail ?? tail, "customTitle");
return lastRecordField(sdkTail ?? tail, "customTitle", "custom-title");
}
/** The last non-blank top-level `agentName` of the records of `text`, the
@@ -607,22 +656,23 @@ export function sdkTitles(
): SdkTitles {
const tail = sdkTail ?? wholeLines;
const customTitle =
lastField(tail, "customTitle") ??
lastRecordField(tail, "customTitle", "custom-title") ??
sidecarTitle ??
lastField(head, "customTitle") ??
lastField(tail, "aiTitle") ??
lastField(head, "aiTitle");
lastRecordField(head, "customTitle", "custom-title") ??
lastRecordField(tail, "aiTitle", "ai-title") ??
lastRecordField(head, "aiTitle", "ai-title");
return { customTitle, summary: customTitle ?? generatedTitle({ head, tail }) };
}
/** The title of a transcript without a custom title, in the SDK's order: AI
* title, last prompt, summary, first prompt. */
* title, last prompt, summary, first prompt. The last prompt and the summary
* are the field of a record of any type. */
export function generatedTitle({ head, tail }: HeadTail): string | undefined {
return (
lastField(tail, "aiTitle") ??
lastField(head, "aiTitle") ??
lastField(tail, "lastPrompt") ??
lastField(tail, "summary") ??
lastRecordField(tail, "aiTitle", "ai-title") ??
lastRecordField(head, "aiTitle", "ai-title") ??
lastRecordField(tail, "lastPrompt") ??
lastRecordField(tail, "summary") ??
(firstPrompt(head) || mediaPrompt(head) || undefined)
);
}

View File

@@ -16,6 +16,22 @@ import {
const bashInput = { command: "ls -la", description: "List files" };
/**
* The heartbeat of a PreToolUse or PostToolUse hook that runs long, as Claude
* Code sends it every 30 seconds since 2.1.323: `tool_name` is the hook
* event, and `parent_tool_use_id` is the tool call the hook runs for.
*/
const hookHeartbeat = (event: "PreToolUse" | "PostToolUse", toolUseId: string, uuid: string) => ({
type: "tool_progress",
tool_use_id: `hook-${event}-${toolUseId}-heartbeat-0`,
tool_name: event,
parent_tool_use_id: toolUseId,
elapsed_time_seconds: 30,
heartbeat: true,
uuid,
session_id: activeSdkSessionId(),
});
export const SCENARIOS: Scenario[] = [
{
name: "session-setup",
@@ -1118,6 +1134,9 @@ export const SCENARIOS: Scenario[] = [
yield* assistantTurn("msg_progress", [
{ type: "tool_use", id: "toolu_slow", name: "Bash", input: { command: "make" } },
]);
// The heartbeats of its hooks report no progress of the tool: the
// client gets nothing for them.
yield hookHeartbeat("PreToolUse", "toolu_slow", "00000000-0000-4000-8000-00000000b004");
for (const seconds of [1, 2, 3]) {
yield {
type: "tool_progress",
@@ -1129,6 +1148,7 @@ export const SCENARIOS: Scenario[] = [
session_id: activeSdkSessionId(),
};
}
yield hookHeartbeat("PostToolUse", "toolu_slow", "00000000-0000-4000-8000-00000000b005");
yield toolResult("toolu_slow", "built", {
structured: { stdout: "built", stderr: "", interrupted: false, isImage: false },
});

View File

@@ -5025,12 +5025,74 @@ describe("the pages of one list", () => {
});
describe("titles by the SDK's rule", () => {
it("finds a custom title whose record starts before the tail window", async () => {
/** The title that the SDK itself reports for the transcript `id`. */
const sdkSummary = async (id: string) =>
(
await vi
.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
"@anthropic-ai/claude-agent-sdk",
)
.then((module) => module.getSessionInfo(id, { dir: workspace }))
)?.summary;
/** A tool result record with `toolUseResult`, as a Read of a transcript leaves it. */
const toolResult = (id: string, toolUseResult: object) =>
JSON.stringify({
type: "user",
sessionId: id,
uuid: randomUUID(),
message: {
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "ok" }],
},
toolUseResult,
});
it.each<[string, (id: string) => string, string]>([
[
"a customTitle inside a message is no title",
(id) => toolResult(id, { type: "custom-title", customTitle: "Inside a message" }),
"First prompt",
],
[
"a lastPrompt inside a message is no title",
(id) => toolResult(id, { lastPrompt: "Inside a message" }),
"First prompt",
],
[
"a custom-title record appended to a line whose write was cut off is the title",
(id) =>
'{"type":"assistant","message":{"role":"assist' +
JSON.stringify({
type: "custom-title",
customTitle: "After a cut-off write",
sessionId: id,
}),
"After a cut-off write",
],
[
"an empty custom title hides the one before it",
(id) =>
[{ customTitle: "Before" }, { customTitle: "" }]
.map((title) => JSON.stringify({ type: "custom-title", ...title, sessionId: id }))
.join("\n"),
"First prompt",
],
])("%s", async (_name, appended, expected) => {
const session = await writeTranscript({ prompt: "First prompt", trailer: [] });
await fs.appendFile(session.file, `${appended(session.id)}\n`);
expect(await sdkSummary(session.id)).toBe(expected);
const { agent } = await indexAgent();
const page = await agent.listSessions({ cwd: workspace });
expect(page.sessions.map((s) => s.title)).toEqual([expected]);
});
it("does not read a custom title whose record the start of the tail window cuts", async () => {
const id = randomUUID();
// Past the head window.
let filler = assistantRecord(id, null);
while (filler.length < 70_000) filler += "\n" + assistantRecord(id, null);
// The cut line of the last 64 KB, which the SDK still searches.
// The cut line of the last 64 KB: the SDK reads whole records only.
const title = JSON.stringify({
type: "custom-title",
padding: "p".repeat(10_000),
@@ -5052,14 +5114,9 @@ describe("titles by the SDK's rule", () => {
expect(start).toBeLessThan(window);
expect(start + title.indexOf('"customTitle"')).toBeGreaterThan(window);
expect(start).toBeGreaterThan(64 * 1024);
const sdk = await vi
.importActual<typeof import("@anthropic-ai/claude-agent-sdk")>(
"@anthropic-ai/claude-agent-sdk",
)
.then((module) => module.getSessionInfo(id, { dir: workspace }));
expect(sdk!.summary).toBe("Across the boundary");
expect(await sdkSummary(id)).toBe("First prompt");
const { agent } = await indexAgent();
const page = await agent.listSessions({ cwd: workspace });
expect(page.sessions.map((s) => s.title)).toEqual(["Across the boundary"]);
expect(page.sessions.map((s) => s.title)).toEqual(["First prompt"]);
});
});

View File

@@ -41,6 +41,38 @@ export function isFinalToolCallStatus(status: ToolCallStatus | null | undefined)
return status === "completed" || status === "failed" || status === V2_CANCELLED;
}
/**
* The tool call that a `tool_progress` beat reports on, of the calls the
* client holds (`held`), or undefined for a beat to drop.
*
* Not every beat reports under the id of a call the client has seen: a tool's
* heartbeat derives `<tool_use_id>-heartbeat-<n>`, and the `agent_api_retry`
* beats behind `subagentRetry` report under `agent_<assistant_message_id>`.
* Forwarding those ids leaves the client resolving a call it was never told
* about (#851). The SDK stamps `parent_tool_use_id` with the id of the
* executing tool on them, so they go to that call. A beat under the id of a
* held call keeps it: a subagent's `bash_progress`, whose parent is the Agent
* call that spawned it.
*
* Dropped:
* - a beat for a call the client does not hold: never announced, or returned
* (an id leaves `held` at its tool result, so a late beat cannot reopen it);
* - the heartbeat of a hook that runs long (`hook-<event>-<id>-heartbeat-<n>`,
* since Claude Code 2.1.323). Its `tool_name` is the hook event and its
* elapsed time is the hook's. For a PreToolUse or PostToolUse hook its
* `parent_tool_use_id` is the tool call, which is not running then: sent on,
* it would name the call `PreToolUse` and restart its elapsed time.
*/
export function progressToolCallId(
beat: { tool_use_id: string; parent_tool_use_id: string | null },
held: ReadonlySet<string>,
): string | undefined {
if (held.has(beat.tool_use_id)) return beat.tool_use_id;
if (beat.tool_use_id.startsWith("hook-")) return undefined;
const parent = beat.parent_tool_use_id;
return parent !== null && held.has(parent) ? parent : undefined;
}
/** The `_meta` of a tool call report. */
export type ToolUpdateMeta = {
claudeCode?: {

View File

@@ -336,6 +336,7 @@ import { ClientCapabilities as ToolCallClientCapabilities } from "../tool-calls/
import {
AcpToolCallRenderer,
isFinalToolCallStatus,
progressToolCallId,
type ToolCallUpdate,
} from "../tool-calls/renderer.js";
import { messageLoopYielder, Pushable, raceTimeoutAndAbort, unreachable } from "../utils.js";
@@ -6904,25 +6905,8 @@ export class ClaudeAcpAgent {
break;
}
case "tool_progress": {
// Not every beat reports under the id of a tool call the client has
// seen: heartbeats derive `<tool_use_id>-heartbeat-<n>`, and the
// `agent_api_retry` beats behind `subagentRetry` report under
// `agent_<assistant_message_id>`. Forwarding those verbatim leaves the
// client resolving an id it has never been told about (the same trap
// `ensureToolCallEmitted` documents for #851). The SDK stamps
// `parent_tool_use_id` with the executing tool's real id whenever the
// beat doesn't carry one of its own, so fall back to it rather than
// pattern-matching each synthetic id shape. Beats that do report a real
// id (a subagent's `bash_progress`, whose parent is the spawning Agent
// call) keep resolving to that id.
const toolCallId = session.emittedToolCalls.has(message.tool_use_id)
? message.tool_use_id
: message.parent_tool_use_id;
// Ids leave `emittedToolCalls` at `tool_result`, so this also stops a
// beat that races past completion from reopening a finished call.
if (toolCallId === null || !session.emittedToolCalls.has(toolCallId)) {
break;
}
const toolCallId = progressToolCallId(message, session.emittedToolCalls);
if (toolCallId === undefined) break;
// The beat is in the subagent whose Agent call is
// `parent_tool_use_id`, unless it reports on that Agent call
// itself: the Agent call's own heartbeat falls back to its id, and

View File

@@ -40,7 +40,7 @@ import type { AcpSessionNotification } from "../acp-subagents.js";
import { isCompactSummaryMessage, replayCompactSummary } from "../context-compaction.js";
import { ClientCapabilities } from "../tool-calls/client-capabilities.js";
import { ToolCallFieldTracker } from "../tool-calls/field-tracker.js";
import { AcpToolCallRenderer, V2_CANCELLED } from "../tool-calls/renderer.js";
import { AcpToolCallRenderer, progressToolCallId, V2_CANCELLED } from "../tool-calls/renderer.js";
import { changedTaskPlanEntries, unregisterHookCallback, type TaskState } from "../tools.js";
import { recordStreamedDelta, unstreamedRemainder, type StreamedBlock } from "../streamed-text.js";
import { replayClear, type V2UpdateTranslator } from "./update-translator.js";
@@ -269,16 +269,12 @@ export class SessionOutput {
}
/**
* A tool's progress beat, on its tool call: the beat's own `tool_use_id`
* when the client holds that call, else the call it runs in
* (`parent_tool_use_id`), such as the Agent call of a subagent's tool or
* a heartbeat's derived id. A beat for a call that returned is dropped.
* A tool's progress beat, on the tool call it reports on (see
* {@link progressToolCallId}, which also says which beats are dropped).
*/
toolProgress(message: SDKToolProgressMessage): void {
const toolCallId = this.emittedToolCalls.has(message.tool_use_id)
? message.tool_use_id
: message.parent_tool_use_id;
if (toolCallId === null || !this.emittedToolCalls.has(toolCallId)) return;
const toolCallId = progressToolCallId(message, this.emittedToolCalls);
if (toolCallId === undefined) return;
// A subagent's beat, on its own session, names the Agent call that runs
// it. A tool call is never its own parent.
const parentToolUseId =