From ddee24ef8fd0abc0401fb32d567914030fbf6944 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 13 Nov 2026 13:56:39 +0100 Subject: [PATCH] fix: update to @anthropic-ai/claude-agent-sdk 0.3.323 (#1612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- package-lock.json | 372 +++++++++++++++++---------- package.json | 8 +- src/async-tasks.ts | 2 +- src/session-index/transcript-scan.ts | 74 +++++- src/tests/acp-scenarios/scenarios.ts | 20 ++ src/tests/v1/session-index.test.ts | 75 +++++- src/tool-calls/renderer.ts | 32 +++ src/v1/acp-agent.ts | 22 +- src/v2/stream.ts | 14 +- 9 files changed, 430 insertions(+), 189 deletions(-) diff --git a/package-lock.json b/package-lock.json index 546bff09..9924fa5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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, diff --git a/package.json b/package.json index a15751ac..eab74001 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/src/async-tasks.ts b/src/async-tasks.ts index b54e0e39..56ecb267 100644 --- a/src/async-tasks.ts +++ b/src/async-tasks.ts @@ -879,7 +879,7 @@ function recordPublished(task: AsyncTask, fields: Record): 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. */ diff --git a/src/session-index/transcript-scan.ts b/src/session-index/transcript-scan.ts index bdcc144e..02ede40b 100644 --- a/src/session-index/transcript-scan.ts +++ b/src/session-index/transcript-scan.ts @@ -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":""` 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) ); } diff --git a/src/tests/acp-scenarios/scenarios.ts b/src/tests/acp-scenarios/scenarios.ts index 9980b926..1959c6f0 100644 --- a/src/tests/acp-scenarios/scenarios.ts +++ b/src/tests/acp-scenarios/scenarios.ts @@ -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 }, }); diff --git a/src/tests/v1/session-index.test.ts b/src/tests/v1/session-index.test.ts index 9d9642ef..449f2ce4 100644 --- a/src/tests/v1/session-index.test.ts +++ b/src/tests/v1/session-index.test.ts @@ -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( + "@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( - "@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"]); }); }); diff --git a/src/tool-calls/renderer.ts b/src/tool-calls/renderer.ts index d95c538c..9a984629 100644 --- a/src/tool-calls/renderer.ts +++ b/src/tool-calls/renderer.ts @@ -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 `-heartbeat-`, and the `agent_api_retry` + * beats behind `subagentRetry` report under `agent_`. + * 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---heartbeat-`, + * 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 | 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?: { diff --git a/src/v1/acp-agent.ts b/src/v1/acp-agent.ts index 81733b50..95736e2d 100644 --- a/src/v1/acp-agent.ts +++ b/src/v1/acp-agent.ts @@ -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 `-heartbeat-`, and the - // `agent_api_retry` beats behind `subagentRetry` report under - // `agent_`. 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 diff --git a/src/v2/stream.ts b/src/v2/stream.ts index 449c5cfd..f2feb33a 100644 --- a/src/v2/stream.ts +++ b/src/v2/stream.ts @@ -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 =