From a81655a0c4b40ddfac0229355cd73e76c7c2f1a9 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:09:52 +0100 Subject: [PATCH 01/23] feat(claude-code): Claude Code as an iii worker New Node worker that drives headless Claude Code turns over the iii bus. - claude::run / claude::start / claude::stop / claude::status / claude::sessions::list, plus run::start_and_wait so the canonical brain contract (console, acp) drives Claude Code unchanged - AgentEvent subset (message_complete, function_execution_start/end, turn_end, agent_end) streamed onto agent::events via stream::set - in-process MCP bridge exposes engine functions_list / functions_info / trigger to the running turn - session resume via engine state scope claude_sessions (iii session_id to Claude session id mapping) - optional approval_gate config routes tool permissions through policy::check_permissions, fail-closed - deploy: bundle with esbuild single-file build; claude_executable config plus PATH fallback covers the bundled SDK losing its native CLI - vitest unit tests for payload parsing, event mapping, config, executable resolution --- README.md | 1 + claude-code/.gitignore | 1 + claude-code/README.md | 90 + claude-code/biome.json | 46 + claude-code/config.yaml | 27 + claude-code/iii.worker.yaml | 17 + claude-code/package.json | 39 + claude-code/pnpm-lock.yaml | 2954 +++++++++++++++++++++++++ claude-code/scripts/build-bundle.mjs | 64 + claude-code/skills/SKILL.md | 66 + claude-code/src/bridge.ts | 63 + claude-code/src/config.ts | 36 + claude-code/src/events.ts | 30 + claude-code/src/executable.ts | 26 + claude-code/src/index.ts | 38 + claude-code/src/map.ts | 119 + claude-code/src/run.ts | 296 +++ claude-code/src/state.ts | 33 + claude-code/src/types.ts | 88 + claude-code/tests/config.test.ts | 44 + claude-code/tests/executable.test.ts | 34 + claude-code/tests/map.test.ts | 129 ++ claude-code/tests/run-payload.test.ts | 54 + claude-code/tsconfig.json | 15 + claude-code/vitest.config.ts | 10 + 25 files changed, 4320 insertions(+) create mode 100644 claude-code/.gitignore create mode 100644 claude-code/README.md create mode 100644 claude-code/biome.json create mode 100644 claude-code/config.yaml create mode 100644 claude-code/iii.worker.yaml create mode 100644 claude-code/package.json create mode 100644 claude-code/pnpm-lock.yaml create mode 100644 claude-code/scripts/build-bundle.mjs create mode 100644 claude-code/skills/SKILL.md create mode 100644 claude-code/src/bridge.ts create mode 100644 claude-code/src/config.ts create mode 100644 claude-code/src/events.ts create mode 100644 claude-code/src/executable.ts create mode 100644 claude-code/src/index.ts create mode 100644 claude-code/src/map.ts create mode 100644 claude-code/src/run.ts create mode 100644 claude-code/src/state.ts create mode 100644 claude-code/src/types.ts create mode 100644 claude-code/tests/config.test.ts create mode 100644 claude-code/tests/executable.test.ts create mode 100644 claude-code/tests/map.test.ts create mode 100644 claude-code/tests/run-payload.test.ts create mode 100644 claude-code/tsconfig.json create mode 100644 claude-code/vitest.config.ts diff --git a/README.md b/README.md index 0a2bd8580..e9c73ee83 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ npx skills add iii-hq/iii --all |---|---|---| | [`acp`](acp/) | Rust | Agent Client Protocol surface — stdio JSON-RPC, exposes iii agents as ACP sessions. | | [`harness`](harness/) | Node | TS port of the iii harness stack — bundles `harness` (provider registry + credentials/settings/permissions via the `configuration` worker), `turn-orchestrator`, `approval-gate`, `session`, `hook-fanout`, `models-catalog`, the `provider-*` workers, `llm-budget`, and `context-compaction` as one pnpm monorepo. See [`harness/README.md`](harness/README.md). | +| [`claude-code`](claude-code/) | Node | Claude Code as an iii brain — `claude::*` runs headless Claude Code turns, streams AgentEvent frames onto `agent::events`, and bridges the engine catalog back to Claude over in-process MCP. | | [`database`](database/) | Rust | PostgreSQL, MySQL, and SQLite client — query, execute, transactions, prepared statements, and change feeds. | | [`iii-directory`](iii-directory/) | Rust | Engine introspection (functions / triggers / workers), workers-registry proxy, and filesystem-backed skill + prompt reader. | | [`iii-lsp`](iii-lsp/) | Rust | Language Server for iii function ids, trigger configs, and worker discovery. Autocomplete / hover across JS/TS, Python, Rust. | diff --git a/claude-code/.gitignore b/claude-code/.gitignore new file mode 100644 index 000000000..43370fa91 --- /dev/null +++ b/claude-code/.gitignore @@ -0,0 +1 @@ +*.tsbuildinfo diff --git a/claude-code/README.md b/claude-code/README.md new file mode 100644 index 000000000..a59bcd425 --- /dev/null +++ b/claude-code/README.md @@ -0,0 +1,90 @@ +# claude-code + +Claude Code as an iii worker. `claude::run` executes a headless Claude Code turn (file edits, shell, web, MCP tools) against any directory on the host and returns the result over the iii bus. Every turn streams AgentEvent frames onto `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. + +The integration is bidirectional. While iii drives Claude Code through `claude::*`, an in-process MCP bridge hands Claude three tools (`mcp__iii__functions_list`, `mcp__iii__functions_info`, `mcp__iii__trigger`) that expose the live engine catalog, so a running Claude Code turn can call any function registered by any worker on the bus. + +## Install + +```bash +iii worker add claude-code +``` + +Requires the `claude` CLI on the host (the Agent SDK shells out to it) and either `ANTHROPIC_API_KEY` in the worker environment or an existing `claude` login. + +## Skills + +Install the `claude-code` agent skill for Claude Code, Cursor, and 30+ other agents: + +```bash +npx skills add iii-hq/workers --skill claude-code +``` + +## Quickstart + +Start the engine, then run a turn from any SDK: + +```ts +import { registerWorker } from 'iii-sdk'; + +const iii = registerWorker('ws://127.0.0.1:49134', { workerName: 'demo' }); + +const res = await iii.trigger({ + function_id: 'claude::run', + payload: { + prompt: 'Add a /health endpoint to server.ts and run the tests', + cwd: '/path/to/repo', + permission_mode: 'acceptEdits', + }, + timeout_ms: 600_000, +}); +// { session_id, claude_session_id, result, stop_reason, usage, total_cost_usd } +``` + +Call `claude::run` again with the returned `session_id` to continue the same conversation: the worker maps iii session ids to Claude Code session ids in engine state and resumes automatically. + +Long turns: use `claude::start` to return immediately, then watch `agent::events` (group_id = your session_id) for `message_complete`, `function_execution_start/end`, and `turn_end` frames. `claude::stop` interrupts a live run, `claude::status` reads a point-in-time view, `claude::sessions::list` enumerates past sessions. + +## Functions + +| Function | Purpose | +| --- | --- | +| `claude::run` | Run one turn, wait, return the final result | +| `claude::start` | Fire-and-forget turn; progress arrives on `agent::events` | +| `claude::stop` | Interrupt a live run | +| `claude::status` | Session state, live flag, usage, cost | +| `claude::sessions::list` | All sessions this worker has run | +| `run::start_and_wait` | Canonical brain contract alias for `claude::run` | + +`claude::run` accepts either a bare `prompt` string or the brain-contract `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `system_prompt`, `append_system_prompt`, `permission_mode`, `allowed_tools`, `disallowed_tools`, and `max_turns` overrides. + +## Configuration + +```yaml +engine_url: ws://127.0.0.1:49134 + +defaults: + model: "" # empty = Claude Code default + permission_mode: acceptEdits # default | acceptEdits | plan | bypassPermissions + max_turns: 50 + cwd: "" # default working directory for runs + +expose_iii_bridge: true # give Claude the mcp__iii__* tools +approval_gate: false # route tool permissions through policy::check_permissions +events_stream: agent::events +claude_executable: "" # path to the claude CLI; empty = SDK default resolution +``` + +With `approval_gate: true` and the harness worker installed, every Claude Code tool call is checked against `policy::check_permissions` before it executes, fail-closed when the gate is unreachable, so the same YAML permission rules and console approval flow that govern native harness turns govern Claude Code. + +## How it maps + +| Claude Code | iii | +| --- | --- | +| SDK `query()` turn | `claude::run` invocation | +| assistant message | `message_complete` frame on `agent::events` | +| tool_use / tool_result | `function_execution_start` / `function_execution_end` frames | +| final result | `turn_end` + `agent_end` frames, function return value | +| session resume | engine state scope `claude_sessions`, keyed by iii session_id | +| permission prompt | `canUseTool` -> `policy::check_permissions` (optional) | +| MCP tools | `mcp__iii__trigger` -> any registered iii function | diff --git a/claude-code/biome.json b/claude-code/biome.json new file mode 100644 index 000000000..722303d0f --- /dev/null +++ b/claude-code/biome.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "root": false, + "vcs": { "enabled": false, "clientKind": "git" }, + "files": { + "ignoreUnknown": false, + "includes": ["**", "!!**/dist"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "off" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "warn" + }, + "style": { + "useImportType": "off", + "useNodejsImportProtocol": "error" + }, + "complexity": { + "noForEach": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "always" + } + } +} diff --git a/claude-code/config.yaml b/claude-code/config.yaml new file mode 100644 index 000000000..07d216cde --- /dev/null +++ b/claude-code/config.yaml @@ -0,0 +1,27 @@ +engine_url: ws://127.0.0.1:49134 + +defaults: + model: "" + permission_mode: acceptEdits + max_turns: 50 + cwd: "" + append_system_prompt: "" + allowed_tools: [] + disallowed_tools: [] + +# Expose the iii bus to Claude Code as in-process MCP tools +# (mcp__iii__trigger / mcp__iii__functions_list / mcp__iii__functions_info). +expose_iii_bridge: true + +# Route Claude Code permission prompts through policy::check_permissions +# (harness approval gate). Fail-closed when the gate is unreachable. +# Requires the harness worker; leave false for standalone use. +approval_gate: false + +# Stream AgentEvent frames here, grouped by session_id. The console and +# acp worker both read this stream. +events_stream: agent::events + +# Path to the Claude Code CLI binary. Empty = Agent SDK default resolution; +# set to an absolute path (or "claude") when running the single-file bundle. +claude_executable: "" diff --git a/claude-code/iii.worker.yaml b/claude-code/iii.worker.yaml new file mode 100644 index 000000000..acdc7d8d7 --- /dev/null +++ b/claude-code/iii.worker.yaml @@ -0,0 +1,17 @@ +iii: v1 +name: claude-code +language: javascript +deploy: bundle +manifest: package.json +description: Claude Code as an iii worker — claude::* functions run headless Claude Code turns, stream AgentEvent frames onto agent::events, and expose the live engine catalog back to Claude through an in-process MCP bridge. + +runtime: + kind: javascript + +scripts: + install: npm install && npm run build + start: node dist/index.js + +dependencies: + iii-state: "^0.17.0" + iii-stream: "^0.17.0" diff --git a/claude-code/package.json b/claude-code/package.json new file mode 100644 index 000000000..b03fb4163 --- /dev/null +++ b/claude-code/package.json @@ -0,0 +1,39 @@ +{ + "name": "claude-code", + "version": "0.1.0", + "private": true, + "description": "Claude Code as an iii worker: headless turns over the iii bus, AgentEvent frames on agent::events, and an MCP bridge back into the engine catalog.", + "license": "Apache-2.0", + "type": "module", + "engines": { + "node": ">=22" + }, + "packageManager": "pnpm@10.18.2", + "scripts": { + "build": "tsc -b", + "build:bundle": "node scripts/build-bundle.mjs", + "typecheck": "tsc -b --noEmit", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "test": "vitest run", + "start": "node dist/index.js", + "dev": "tsx src/index.ts" + }, + "bin": { + "claude-code": "./dist/index.js" + }, + "dependencies": { + "@anthropic-ai/claude-agent-sdk": "^0.3.173", + "iii-sdk": "^0.19.2", + "yaml": "^2.6.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.4.10", + "@types/node": "^22.10.0", + "esbuild": "^0.25.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0", + "vitest": "^3.0.0" + } +} diff --git a/claude-code/pnpm-lock.yaml b/claude-code/pnpm-lock.yaml new file mode 100644 index 000000000..d9399b913 --- /dev/null +++ b/claude-code/pnpm-lock.yaml @@ -0,0 +1,2954 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@anthropic-ai/claude-agent-sdk': + specifier: ^0.3.173 + version: 0.3.173(@anthropic-ai/sdk@0.104.1(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + iii-sdk: + specifier: ^0.19.2 + version: 0.19.2 + yaml: + specifier: ^2.6.0 + version: 2.9.0 + zod: + specifier: ^4.0.0 + version: 4.4.3 + devDependencies: + '@biomejs/biome': + specifier: 2.4.10 + version: 2.4.10 + '@types/node': + specifier: ^22.10.0 + version: 22.19.21 + esbuild: + specifier: ^0.25.0 + version: 0.25.12 + tsx: + specifier: ^4.19.0 + version: 4.22.4 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^3.0.0 + version: 3.2.6(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0) + +packages: + + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.173': + resolution: {integrity: sha512-McW1toJ4Qdo/i7bHnsiFMm2AtyCiK/5V90WgL7M9ZO9llrJr3riGRdBlRGHvWnS7rKuv5ttj4/SuBkLoL9o75A==} + cpu: [arm64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.173': + resolution: {integrity: sha512-m2Oh1pQ69IUg6DSH6n1nAAAtRq+G7J2B/CKTbTfDAEmg5t0lzakZV9l28GZrlP21xl+PIg71dkiJ5u94KYgpRw==} + cpu: [x64] + os: [darwin] + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.173': + resolution: {integrity: sha512-uu8MnPwFBc9ayFg5c94aHaqJVLS51oHNVxHwud4nK27GliP5WoME3F7pmm1N/Jyy2ry2E2CLNnsAm5l3zemfYA==} + cpu: [arm64] + os: [linux] + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.173': + resolution: {integrity: sha512-Vb64WJOD2D9tKn/i0ErmLbO6I1187xTwL/kxEANjg4L1FGQnvdYBnZ6J6jU6+x8UgcuIi82imHROQp80gbPW2w==} + cpu: [arm64] + os: [linux] + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.173': + resolution: {integrity: sha512-ofKxyp/N8+LLSrClt+dJo0laCHDzmBgmN8+Q+zabJ54Jqc1KXee68UsziE7kLCqGbOSY7rsIK9d22T1uQyZkUw==} + cpu: [x64] + os: [linux] + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.173': + resolution: {integrity: sha512-0l9L5O+Uiw8gq9mu2NqGSYcSmX8RJMAh9GkGacTJqJbkfHCiFxqZmWBWODOt6HP7PTFCV+yMzQK/xJQjnag/jw==} + cpu: [x64] + os: [linux] + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.173': + resolution: {integrity: sha512-aulIrBYjFDm+S5kl4CxC8A3KUiTDKLHoKV46Mat64E+skGEyBkC7WzZAGbpGKB2y8KZo1WbrwJTGefgyHMpDhw==} + cpu: [arm64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.173': + resolution: {integrity: sha512-HjGkfDlNLI3Kh9NIUfevJ3SZY8Xv3td4zx5Cz3YE0VPrrjIkRvdEv9K6WTjT94tlS0TUXQS/kFT+NCmoGXuvZQ==} + cpu: [x64] + os: [win32] + + '@anthropic-ai/claude-agent-sdk@0.3.173': + resolution: {integrity: sha512-BsdL223y7vCUJA9uBW9osSrhufvwIT+J94IBkh83v+wjyjoBIwLXREdacFabair70bGNdtkw6cWCaYNThSQg7A==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@anthropic-ai/sdk': '>=0.93.0' + '@modelcontextprotocol/sdk': ^1.29.0 + zod: ^4.0.0 + + '@anthropic-ai/sdk@0.104.1': + resolution: {integrity: sha512-gGACa/+IaiXzRRmF96aOhamoBgapKRBiFWbmmTFP8aMkpaEcuStF+Q61bjo4vPxBM7gqWJNZqsngslRdnLHv0Q==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.4.10': + resolution: {integrity: sha512-xxA3AphFQ1geij4JTHXv4EeSTda1IFn22ye9LdyVPoJU19fNVl0uzfEuhsfQ4Yue/0FaLs2/ccVi4UDiE7R30w==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.10': + resolution: {integrity: sha512-vuzzI1cWqDVzOMIkYyHbKqp+AkQq4K7k+UCXWpkYcY/HDn1UxdsbsfgtVpa40shem8Kax4TLDLlx8kMAecgqiw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.10': + resolution: {integrity: sha512-14fzASRo+BPotwp7nWULy2W5xeUyFnTaq1V13Etrrxkrih+ez/2QfgFm5Ehtf5vSjtgx/IJycMMpn5kPd5ZNaA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.10': + resolution: {integrity: sha512-WrJY6UuiSD/Dh+nwK2qOTu8kdMDlLV3dLMmychIghHPAysWFq1/DGC1pVZx8POE3ZkzKR3PUUnVrtZfMfaJjyQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.10': + resolution: {integrity: sha512-7MH1CMW5uuxQ/s7FLST63qF8B3Hgu2HRdZ7tA1X1+mk+St4JOuIrqdhIBnnyqeyWJNI+Bww7Es5QZ0wIc1Cmkw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.10': + resolution: {integrity: sha512-kDTi3pI6PBN6CiczsWYOyP2zk0IJI08EWEQyDMQWW221rPaaEz6FvjLhnU07KMzLv8q3qSuoB93ua6inSQ55Tw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.10': + resolution: {integrity: sha512-tZLvEEi2u9Xu1zAqRjTcpIDGVtldigVvzug2fTuPG0ME/g8/mXpRPcNgLB22bGn6FvLJpHHnqLnwliOu8xjYrg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.10': + resolution: {integrity: sha512-umwQU6qPzH+ISTf/eHyJ/QoQnJs3V9Vpjz2OjZXe9MVBZ7prgGafMy7yYeRGnlmDAn87AKTF3Q6weLoMGpeqdQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.10': + resolution: {integrity: sha512-aW/JU5GuyH4uxMrNYpoC2kjaHlyJGLgIa3XkhPEZI0uKhZhJZU8BuEyJmvgzSPQNGozBwWjC972RaNdcJ9KyJg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@iii-dev/observability@0.19.2': + resolution: {integrity: sha512-FsAAzELzRKUA5h8CyLgsB/k0rlN3ZQeg9BVKBsxx8ubF6tNwdLS53bhDFEy9jE7Q5kDgx/AFRVdqB7pUANgChA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@opentelemetry/api-logs@0.57.2': + resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation@0.57.2': + resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.57.2': + resolution: {integrity: sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@1.30.1': + resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.57.2': + resolution: {integrity: sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.30.1': + resolution: {integrity: sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.30.1': + resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@1.30.1': + resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@rollup/rollup-android-arm-eabi@4.61.1': + resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.61.1': + resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.61.1': + resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.61.1': + resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.61.1': + resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.61.1': + resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.61.1': + resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.61.1': + resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.61.1': + resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.61.1': + resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.61.1': + resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.61.1': + resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.61.1': + resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.61.1': + resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + cpu: [x64] + os: [win32] + + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.19.21': + resolution: {integrity: sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==} + + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + + '@vitest/expect@3.2.6': + resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} + + '@vitest/mocker@3.2.6': + resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.6': + resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} + + '@vitest/runner@3.2.6': + resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} + + '@vitest/snapshot@3.2.6': + resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} + + '@vitest/spy@3.2.6': + resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + + '@vitest/utils@3.2.6': + resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.12.25: + resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + iii-sdk@0.19.2: + resolution: {integrity: sha512-HgMKHxmrOZNxNvJGwMvLsQ7UoifIpv1Av46gCs4vbBjSWDRQiM/wCNsTlZ/QUdSiZOXs6FjIxSLf3kuL1H3bLw==} + + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + protobufjs@7.6.3: + resolution: {integrity: sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.61.1: + resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.6: + resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.6 + '@vitest/ui': 3.2.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.173': + optional: true + + '@anthropic-ai/claude-agent-sdk@0.3.173(@anthropic-ai/sdk@0.104.1(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.104.1(zod@4.4.3) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) + zod: 4.4.3 + optionalDependencies: + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.173 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.173 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.173 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.173 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.173 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.173 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.173 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.173 + + '@anthropic-ai/sdk@0.104.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + optionalDependencies: + zod: 4.4.3 + + '@babel/runtime@7.29.7': {} + + '@biomejs/biome@2.4.10': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.10 + '@biomejs/cli-darwin-x64': 2.4.10 + '@biomejs/cli-linux-arm64': 2.4.10 + '@biomejs/cli-linux-arm64-musl': 2.4.10 + '@biomejs/cli-linux-x64': 2.4.10 + '@biomejs/cli-linux-x64-musl': 2.4.10 + '@biomejs/cli-win32-arm64': 2.4.10 + '@biomejs/cli-win32-x64': 2.4.10 + + '@biomejs/cli-darwin-arm64@2.4.10': + optional: true + + '@biomejs/cli-darwin-x64@2.4.10': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.10': + optional: true + + '@biomejs/cli-linux-arm64@2.4.10': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.10': + optional: true + + '@biomejs/cli-linux-x64@2.4.10': + optional: true + + '@biomejs/cli-win32-arm64@2.4.10': + optional: true + + '@biomejs/cli-win32-x64@2.4.10': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.28.0': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.28.0': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.28.0': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.28.0': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.28.0': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@esbuild/win32-x64@0.28.0': + optional: true + + '@hono/node-server@1.19.14(hono@4.12.25)': + dependencies: + hono: 4.12.25 + + '@iii-dev/observability@0.19.2': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.25) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.0 + express: 5.2.1 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.25 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/api-logs@0.57.2': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.57.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.15.0 + require-in-the-middle: 7.5.2 + semver: 7.8.4 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-transformer@0.57.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) + protobufjs: 7.6.3 + + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-logs@0.57.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.57.2 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-metrics@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) + semver: 7.8.4 + + '@opentelemetry/semantic-conventions@1.28.0': {} + + '@opentelemetry/semantic-conventions@1.41.1': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@rollup/rollup-android-arm-eabi@4.61.1': + optional: true + + '@rollup/rollup-android-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-x64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.61.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.61.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.61.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.61.1': + optional: true + + '@stablelib/base64@1.0.1': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@22.19.21': + dependencies: + undici-types: 6.21.0 + + '@types/shimmer@1.2.0': {} + + '@vitest/expect@3.2.6': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.6(vite@7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 3.2.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0) + + '@vitest/pretty-format@3.2.6': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.6': + dependencies: + '@vitest/utils': 3.2.6 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.6': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.6': + dependencies: + '@vitest/pretty-format': 3.2.6 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + assertion-error@2.0.1: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.2 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + cjs-module-lexer@1.4.3: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + etag@1.8.1: {} + + eventsource-parser@3.1.0: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.0 + + expect-type@1.3.0: {} + + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-sha256@1.3.0: {} + + fast-uri@3.1.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.12.25: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + iii-sdk@0.19.2: + dependencies: + '@iii-dev/observability': 0.19.2 + '@opentelemetry/api': 1.9.1 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + import-in-the-middle@1.15.0: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + + inherits@2.0.4: {} + + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.3: {} + + js-tokens@9.0.1: {} + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + long@5.3.2: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + module-details-from-path@1.0.4: {} + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + protobufjs@7.6.3: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 22.19.21 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.2: + dependencies: + side-channel: 1.1.1 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + require-from-string@2.0.2: {} + + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.61.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.61.1 + '@rollup/rollup-android-arm64': 4.61.1 + '@rollup/rollup-darwin-arm64': 4.61.1 + '@rollup/rollup-darwin-x64': 4.61.1 + '@rollup/rollup-freebsd-arm64': 4.61.1 + '@rollup/rollup-freebsd-x64': 4.61.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 + '@rollup/rollup-linux-arm-musleabihf': 4.61.1 + '@rollup/rollup-linux-arm64-gnu': 4.61.1 + '@rollup/rollup-linux-arm64-musl': 4.61.1 + '@rollup/rollup-linux-loong64-gnu': 4.61.1 + '@rollup/rollup-linux-loong64-musl': 4.61.1 + '@rollup/rollup-linux-ppc64-gnu': 4.61.1 + '@rollup/rollup-linux-ppc64-musl': 4.61.1 + '@rollup/rollup-linux-riscv64-gnu': 4.61.1 + '@rollup/rollup-linux-riscv64-musl': 4.61.1 + '@rollup/rollup-linux-s390x-gnu': 4.61.1 + '@rollup/rollup-linux-x64-gnu': 4.61.1 + '@rollup/rollup-linux-x64-musl': 4.61.1 + '@rollup/rollup-openbsd-x64': 4.61.1 + '@rollup/rollup-openharmony-arm64': 4.61.1 + '@rollup/rollup-win32-arm64-msvc': 4.61.1 + '@rollup/rollup-win32-ia32-msvc': 4.61.1 + '@rollup/rollup-win32-x64-gnu': 4.61.1 + '@rollup/rollup-win32-x64-msvc': 4.61.1 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + semver@7.8.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shimmer@1.2.1: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + supports-preserve-symlinks-flag@1.0.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + toidentifier@1.0.1: {} + + ts-algebra@2.0.0: {} + + tsx@4.22.4: + dependencies: + esbuild: 0.28.0 + optionalDependencies: + fsevents: 2.3.3 + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + vite-node@3.2.4(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.61.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.19.21 + fsevents: 2.3.3 + tsx: 4.22.4 + yaml: 2.9.0 + + vitest@3.2.6(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/pretty-format': 3.2.6 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.5(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@22.19.21)(tsx@4.22.4)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.21 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + yaml@2.9.0: {} + + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/claude-code/scripts/build-bundle.mjs b/claude-code/scripts/build-bundle.mjs new file mode 100644 index 000000000..fdb473449 --- /dev/null +++ b/claude-code/scripts/build-bundle.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +/** + * Single-file ESM bundle for claude-code (`dist/bundle/index.mjs`). + * + * Mirrors harness/scripts/build-bundle.mjs: + * + * 1. iii-sdk reads its own version at module-init via + * `createRequire(import.meta.url)("../package.json")` + * which resolves relative to the bundle path at runtime. The + * `inlinePackageJson` plugin rewrites that call to a literal object. + * + * 2. `@anthropic-ai/claude-agent-sdk` spawns the Claude Code CLI as a + * subprocess; the `claude_executable` config option (or a `claude` + * binary on PATH) covers the bundled case where the SDK cannot + * resolve its own vendored entrypoint. + */ + +import { readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { build } from 'esbuild'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const root = join(__dirname, '..'); + +/** @type {import('esbuild').Plugin} */ +const inlinePackageJson = { + name: 'iii-inline-sdk-package-json', + setup(b) { + b.onLoad({ filter: /iii-sdk[\\/]dist[\\/]index\.mjs$/ }, async (args) => { + const [source, pkg] = await Promise.all([ + readFile(args.path, 'utf8'), + readFile(join(root, 'node_modules/iii-sdk/package.json'), 'utf8'), + ]); + const { version } = JSON.parse(pkg); + const replaced = source.replace( + /createRequire\(\s*import\.meta\.url\s*\)\s*\(\s*"\.\.\/package\.json"\s*\)/g, + JSON.stringify({ version }), + ); + return { contents: replaced, loader: 'js' }; + }); + }, +}; + +await build({ + entryPoints: [join(root, 'src/index.ts')], + bundle: true, + platform: 'node', + target: 'node22', + format: 'esm', + outfile: join(root, 'dist/bundle/index.mjs'), + legalComments: 'none', + external: ['fsevents'], + banner: { + js: "import{createRequire as __iiiCR}from'module';const require=__iiiCR(import.meta.url);", + }, + define: { + 'process.env.NODE_ENV': '"production"', + }, + plugins: [inlinePackageJson], + logLevel: 'info', +}); diff --git a/claude-code/skills/SKILL.md b/claude-code/skills/SKILL.md new file mode 100644 index 000000000..3c058ad2d --- /dev/null +++ b/claude-code/skills/SKILL.md @@ -0,0 +1,66 @@ +--- +name: claude-code +description: >- + Run headless Claude Code turns over the iii bus — file edits, shell, web, + and MCP tools against any host directory — with AgentEvent streaming, + session resume, and an MCP bridge that lets Claude call any iii function. +--- + +# claude-code + +The claude-code worker turns Claude Code into an iii brain. `claude::run` +executes one headless Claude Code turn (the full agent: file edits, shell +commands, web fetch, MCP tools) in a configured working directory and returns +the final result, token usage, and cost over the bus. Every turn streams +AgentEvent frames onto `agent::events` keyed by session id, so the iii console +and the acp worker render Claude Code turns like any native harness turn. The +worker also registers `run::start_and_wait`, so anything built to drive the +canonical brain contract can drive Claude Code unchanged. + +The integration is bidirectional. Each turn carries an in-process MCP bridge +exposing `mcp__iii__functions_list`, `mcp__iii__functions_info`, and +`mcp__iii__trigger`, so the running Claude turn can discover and invoke any +function registered on the engine — shell, database, storage, or your own +workers. Requires the `claude` CLI on the host with an existing login or +`ANTHROPIC_API_KEY` in the worker environment. + +## When to Use + +- Delegate a whole coding task ("add an endpoint and run the tests") to a + full agent from any iii worker or trigger, instead of orchestrating + individual `coder::*` / `shell::*` calls yourself. +- Continue a conversation across calls: pass the same `session_id` and the + worker resumes the underlying Claude Code session with full context. +- Run long agentic jobs in the background with `claude::start` and watch + `agent::events` for `message_complete`, `function_execution_start/end`, + and `turn_end` frames; interrupt with `claude::stop`. +- Let an LLM turn reach the rest of your backend: Claude calls + `mcp__iii__trigger` to hit any bus function mid-turn. + +## Boundaries + +- Spawns the host `claude` CLI per turn — needs Claude Code installed and + authenticated; not available inside a bare container without it. +- Tool execution happens inside Claude Code's own sandbox and permission + model (`permission_mode`, `allowed_tools`, `disallowed_tools`), not the + engine's; set `approval_gate: true` to route every tool call through + `policy::check_permissions` (fail-closed, needs the harness worker). +- One live run per session id; a second `claude::run` for a busy session + waits on the engine queue rather than merging into the live turn. +- Emits the AgentEvent subset (`message_complete`, + `function_execution_start/end`, `turn_end`, `agent_end`) — no + token-by-token `message_update` deltas. + +## Functions + +- `claude::run` — run one Claude Code turn and wait; accepts `prompt` or + brain-contract `messages`, plus `model`, `cwd`, `permission_mode`, + `allowed_tools`, `max_turns` overrides; returns + `{session_id, result, usage, total_cost_usd}`. +- `claude::start` — same payload, returns `{session_id, started}` + immediately; progress arrives on `agent::events`. +- `claude::stop` — interrupt the live run for a session. +- `claude::status` — point-in-time session view: live flag, status, turns, + usage, cost. +- `claude::sessions::list` — every session this worker has run. +- `run::start_and_wait` — canonical brain entrypoint backed by Claude Code. diff --git a/claude-code/src/bridge.ts b/claude-code/src/bridge.ts new file mode 100644 index 000000000..4ddc4717d --- /dev/null +++ b/claude-code/src/bridge.ts @@ -0,0 +1,63 @@ +/** + * In-process MCP server handed to every Claude Code run. Exposes the whole + * iii bus to the model as three tools: + * + * mcp__iii__functions_list — engine::functions::list (discover the catalog) + * mcp__iii__functions_info — engine::functions::info (one function's schema) + * mcp__iii__trigger — invoke any registered iii function + * + * This is what makes the worker bidirectional: iii drives Claude Code via + * claude::run, and Claude Code drives iii back through this bridge. + */ + +import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'; +import type { ISdk } from 'iii-sdk'; +import { z } from 'zod'; + +function asText(value: unknown) { + return { + content: [{ type: 'text' as const, text: JSON.stringify(value ?? null, null, 2) }], + }; +} + +export function makeIiiBridge(iii: ISdk) { + return createSdkMcpServer({ + name: 'iii', + version: '0.1.0', + tools: [ + tool( + 'functions_list', + 'List every function currently registered on the iii engine.', + {}, + async () => + asText(await iii.trigger({ function_id: 'engine::functions::list', payload: {} })), + ), + tool( + 'functions_info', + 'Get the description and input schema of one iii function.', + { function_id: z.string() }, + async ({ function_id }) => + asText( + await iii.trigger({ function_id: 'engine::functions::info', payload: { function_id } }), + ), + ), + tool( + 'trigger', + 'Invoke an iii function with a JSON payload and return its result.', + { + function_id: z.string(), + payload: z.record(z.string(), z.unknown()).default({}), + timeout_ms: z.number().int().positive().optional(), + }, + async ({ function_id, payload, timeout_ms }) => + asText( + await iii.trigger({ + function_id, + payload, + ...(timeout_ms ? { timeoutMs: timeout_ms } : {}), + }), + ), + ), + ], + }); +} diff --git a/claude-code/src/config.ts b/claude-code/src/config.ts new file mode 100644 index 000000000..31cd860e9 --- /dev/null +++ b/claude-code/src/config.ts @@ -0,0 +1,36 @@ +import { readFile } from 'node:fs/promises'; +import { parse } from 'yaml'; +import { z } from 'zod'; + +const ConfigSchema = z.object({ + engine_url: z.string().default('ws://127.0.0.1:49134'), + defaults: z + .object({ + model: z.string().default(''), + permission_mode: z + .enum(['default', 'acceptEdits', 'plan', 'bypassPermissions']) + .default('acceptEdits'), + max_turns: z.number().int().positive().default(50), + cwd: z.string().default(''), + append_system_prompt: z.string().default(''), + allowed_tools: z.array(z.string()).default([]), + disallowed_tools: z.array(z.string()).default([]), + }) + .prefault({}), + expose_iii_bridge: z.boolean().default(true), + approval_gate: z.boolean().default(false), + events_stream: z.string().default('agent::events'), + claude_executable: z.string().default(''), +}); + +export type Config = z.infer; + +export async function loadConfig(path: string): Promise { + let raw: unknown = {}; + try { + raw = parse(await readFile(path, 'utf8')) ?? {}; + } catch { + // missing config file falls back to defaults + } + return ConfigSchema.parse(raw); +} diff --git a/claude-code/src/events.ts b/claude-code/src/events.ts new file mode 100644 index 000000000..ac4b41d88 --- /dev/null +++ b/claude-code/src/events.ts @@ -0,0 +1,30 @@ +/** + * Emit AgentEvent frames via the engine's stream builtin. Same per-process + * epoch + per-session monotonic sequence scheme as the harness emitter, so + * item_ids never collide across restarts. + */ + +import { randomUUID } from 'node:crypto'; +import type { ISdk } from 'iii-sdk'; +import type { AgentEvent } from './types.js'; + +const PROCESS_EPOCH = randomUUID(); +const seqBySession = new Map(); + +export function makeEmitter(iii: ISdk, streamName: string) { + return async function emit(session_id: string, event: AgentEvent): Promise { + const seq = seqBySession.get(session_id) ?? 0; + seqBySession.set(session_id, seq + 1); + const item_id = `${session_id}-${PROCESS_EPOCH}-${seq.toString().padStart(8, '0')}`; + try { + await iii.trigger({ + function_id: 'stream::set', + payload: { stream_name: streamName, group_id: session_id, item_id, data: event }, + }); + } catch (err) { + console.warn(`stream::set failed for ${session_id}: ${String(err)}`); + } + }; +} + +export type Emit = ReturnType; diff --git a/claude-code/src/executable.ts b/claude-code/src/executable.ts new file mode 100644 index 000000000..4ed5b3116 --- /dev/null +++ b/claude-code/src/executable.ts @@ -0,0 +1,26 @@ +/** + * Resolve the Claude Code CLI binary for the Agent SDK. The npm-installed + * SDK ships a native CLI as an optional platform dependency, but the + * single-file bundle (`deploy: bundle`) cannot carry it — so when the + * operator has not pinned `claude_executable` in config.yaml, fall back to + * the `claude` binary on PATH. + */ + +import { accessSync, constants } from 'node:fs'; +import { delimiter, join } from 'node:path'; + +export function resolveClaudeExecutable(configured: string): string { + if (configured) return configured; + const path = process.env.PATH ?? ''; + for (const dir of path.split(delimiter)) { + if (!dir) continue; + const candidate = join(dir, 'claude'); + try { + accessSync(candidate, constants.X_OK); + return candidate; + } catch { + // keep scanning + } + } + return ''; +} diff --git a/claude-code/src/index.ts b/claude-code/src/index.ts new file mode 100644 index 000000000..4aef36623 --- /dev/null +++ b/claude-code/src/index.ts @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/** + * Worker bootstrap: connect to the engine, register claude::* functions, + * wait for SIGINT/SIGTERM. Mirrors the binary-worker lifecycle from + * iii-hq/workers (parse flags, registerWorker, register, shutdown). + */ + +import { parseArgs } from 'node:util'; +import { registerWorker } from 'iii-sdk'; +import { loadConfig } from './config.js'; +import { makeEmitter } from './events.js'; +import { resolveClaudeExecutable } from './executable.js'; +import { register } from './run.js'; + +const { values } = parseArgs({ + options: { + config: { type: 'string', default: './config.yaml' }, + url: { type: 'string' }, + }, + strict: false, +}); + +const cfg = await loadConfig(String(values.config)); +cfg.claude_executable = resolveClaudeExecutable(cfg.claude_executable); +const url = values.url ? String(values.url) : cfg.engine_url; + +const iii = registerWorker(url, { workerName: 'claude-code' }); +const emit = makeEmitter(iii, cfg.events_stream); +register(iii, cfg, emit); + +console.log(`claude-code worker connected to ${url}`); + +const shutdown = async () => { + await iii.shutdown?.(); + process.exit(0); +}; +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/claude-code/src/map.ts b/claude-code/src/map.ts new file mode 100644 index 000000000..b71cefc54 --- /dev/null +++ b/claude-code/src/map.ts @@ -0,0 +1,119 @@ +/** + * Map Claude Agent SDK messages to the AgentEvent wire subset. One Claude + * Code "turn" (claude::run call) becomes: + * + * assistant msg -> message_complete (+ function_execution_start per tool_use) + * user msg w/ tool_result -> function_execution_end + * result msg -> turn_end + agent_end + */ + +import type { + AgentMessage, + AssistantMessage, + ContentBlock, + FunctionResultMessage, + Usage, +} from './types.js'; + +type SdkContentBlock = { + type: string; + text?: string; + thinking?: string; + id?: string; + name?: string; + input?: unknown; + tool_use_id?: string; + content?: unknown; + is_error?: boolean; +}; + +export type ToolCallIndex = Map; + +export function toolFunctionId(name: string): string { + return name.startsWith('mcp__') + ? name.replace(/^mcp__/, '').replace(/__/g, '::') + : `claude-code::${name}`; +} + +export function mapAssistantContent( + blocks: SdkContentBlock[], + calls: ToolCallIndex, +): ContentBlock[] { + const out: ContentBlock[] = []; + for (const b of blocks) { + if (b.type === 'text' && b.text) out.push({ type: 'text', text: b.text }); + else if (b.type === 'thinking' && b.thinking) out.push({ type: 'thinking', text: b.thinking }); + else if (b.type === 'tool_use' && b.id && b.name) { + const function_id = toolFunctionId(b.name); + calls.set(b.id, { function_id, started_at: Date.now() }); + out.push({ type: 'function_call', id: b.id, function_id, arguments: b.input ?? {} }); + } + } + return out; +} + +export function mapToolResultContent(raw: unknown): ContentBlock[] { + if (typeof raw === 'string') return [{ type: 'text', text: raw }]; + if (Array.isArray(raw)) { + return raw + .filter((b): b is SdkContentBlock => typeof b === 'object' && b !== null) + .map((b) => ({ + type: 'text' as const, + text: b.type === 'text' ? (b.text ?? '') : JSON.stringify(b), + })); + } + return [{ type: 'text', text: JSON.stringify(raw ?? null) }]; +} + +export function makeAssistantMessage( + content: ContentBlock[], + model: string, + usage: Usage | null, + stop_reason = 'end', +): AssistantMessage { + return { + role: 'assistant', + content, + stop_reason, + error_message: null, + usage, + model, + provider: 'claude-code', + timestamp: Date.now(), + }; +} + +export function makeFunctionResult( + function_call_id: string, + function_id: string, + content: ContentBlock[], + is_error: boolean, +): FunctionResultMessage { + return { + role: 'function_result', + function_call_id, + function_id, + content, + details: null, + is_error, + timestamp: Date.now(), + }; +} + +export function mapUsage(raw: unknown): Usage | null { + if (typeof raw !== 'object' || raw === null) return null; + const u = raw as Record; + return { + input_tokens: u.input_tokens ?? 0, + output_tokens: u.output_tokens ?? 0, + cache_read_tokens: u.cache_read_input_tokens, + cache_write_tokens: u.cache_creation_input_tokens, + }; +} + +export function lastAssistant(messages: AgentMessage[]): AgentMessage { + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === 'assistant') return messages[i]; + } + return messages[messages.length - 1]; +} diff --git a/claude-code/src/run.ts b/claude-code/src/run.ts new file mode 100644 index 000000000..a35d109c9 --- /dev/null +++ b/claude-code/src/run.ts @@ -0,0 +1,296 @@ +/** + * claude::* function registrations. `claude::run` accepts either a bare + * `prompt` string or the canonical brain-contract shape (`messages` array of + * role/content-block messages), so anything that can drive + * `run::start_and_wait` — the acp worker, the console, another worker — can + * drive Claude Code unchanged. + */ + +import { randomUUID } from 'node:crypto'; +import { query, type Options, type PermissionResult } from '@anthropic-ai/claude-agent-sdk'; +import type { ISdk } from 'iii-sdk'; +import { z } from 'zod'; +import { makeIiiBridge } from './bridge.js'; +import type { Config } from './config.js'; +import type { Emit } from './events.js'; +import { + lastAssistant, + makeAssistantMessage, + makeFunctionResult, + mapAssistantContent, + mapToolResultContent, + mapUsage, + type ToolCallIndex, +} from './map.js'; +import { listSessions, loadSession, saveSession } from './state.js'; +import type { AgentMessage, FunctionResultMessage, SessionRecord } from './types.js'; + +const ContentBlockSchema = z.object({ type: z.string() }).passthrough(); +const MessageSchema = z.object({ + role: z.string(), + content: z.union([z.string(), z.array(ContentBlockSchema)]), +}); + +const RunPayloadSchema = z.object({ + session_id: z.string().optional(), + prompt: z.string().optional(), + messages: z.array(MessageSchema).optional(), + model: z.string().optional(), + cwd: z.string().optional(), + system_prompt: z.string().optional(), + append_system_prompt: z.string().optional(), + permission_mode: z.enum(['default', 'acceptEdits', 'plan', 'bypassPermissions']).optional(), + allowed_tools: z.array(z.string()).optional(), + disallowed_tools: z.array(z.string()).optional(), + max_turns: z.number().int().positive().optional(), + timeout_ms: z.number().int().positive().optional(), +}); + +export type RunPayload = z.infer; +export { RunPayloadSchema }; + +type LiveRun = { interrupt: () => Promise }; +const live = new Map(); + +export function extractPrompt(payload: RunPayload): string { + if (payload.prompt) return payload.prompt; + const users = (payload.messages ?? []).filter((m) => m.role === 'user'); + const last = users[users.length - 1]; + if (!last) throw new Error('claude::run requires `prompt` or a user message in `messages`'); + if (typeof last.content === 'string') return last.content; + return last.content + .map((b) => ('text' in b && typeof b.text === 'string' ? b.text : '')) + .filter(Boolean) + .join('\n'); +} + +function gatedCanUseTool(iii: ISdk, session_id: string) { + return async (toolName: string, input: Record): Promise => { + try { + const res = await iii.trigger({ + function_id: 'policy::check_permissions', + payload: { session_id, function_id: `claude-code::${toolName}`, args: input }, + timeoutMs: 5_000, + }); + const decision = res?.decision ?? res?.behavior ?? 'deny'; + if (decision === 'allow') return { behavior: 'allow', updatedInput: input }; + return { behavior: 'deny', message: `denied by approval gate (${decision})` }; + } catch { + return { behavior: 'deny', message: 'approval gate unreachable (fail-closed)' }; + } + }; +} + +export async function executeRun( + iii: ISdk, + cfg: Config, + emit: Emit, + payload: RunPayload, +): Promise> { + const session_id = payload.session_id ?? randomUUID(); + const prompt = extractPrompt(payload); + const prior = await loadSession(iii, session_id); + const d = cfg.defaults; + + const record: SessionRecord = prior ?? { + session_id, + claude_session_id: null, + cwd: payload.cwd ?? d.cwd, + model: payload.model ?? d.model, + status: 'working', + turns: 0, + total_cost_usd: 0, + usage: null, + updated_at_ms: Date.now(), + }; + record.status = 'working'; + record.updated_at_ms = Date.now(); + await saveSession(iii, record); + + const append = payload.append_system_prompt ?? d.append_system_prompt; + const options: Options = { + ...(record.model ? { model: record.model } : {}), + ...(record.cwd ? { cwd: record.cwd } : {}), + ...(prior?.claude_session_id ? { resume: prior.claude_session_id } : {}), + maxTurns: payload.max_turns ?? d.max_turns, + permissionMode: payload.permission_mode ?? d.permission_mode, + allowedTools: payload.allowed_tools ?? d.allowed_tools, + disallowedTools: payload.disallowed_tools ?? d.disallowed_tools, + systemPrompt: payload.system_prompt + ? payload.system_prompt + : { type: 'preset', preset: 'claude_code', ...(append ? { append } : {}) }, + settingSources: [], + ...(cfg.claude_executable ? { pathToClaudeCodeExecutable: cfg.claude_executable } : {}), + ...(cfg.expose_iii_bridge ? { mcpServers: { iii: makeIiiBridge(iii) } } : {}), + ...(cfg.approval_gate ? { canUseTool: gatedCanUseTool(iii, session_id) } : {}), + }; + + const q = query({ prompt, options }); + live.set(session_id, { interrupt: () => q.interrupt() }); + + const transcript: AgentMessage[] = []; + const pendingResults: FunctionResultMessage[] = []; + const calls: ToolCallIndex = new Map(); + let resultText = ''; + let stopReason = 'end'; + let isError = false; + + try { + for await (const msg of q) { + if (msg.type === 'system' && msg.subtype === 'init') { + record.claude_session_id = msg.session_id; + await saveSession(iii, record); + } else if (msg.type === 'assistant') { + const usage = mapUsage(msg.message.usage); + const content = mapAssistantContent(msg.message.content as never, calls); + const assistant = makeAssistantMessage(content, msg.message.model ?? record.model, usage); + transcript.push(assistant); + await emit(session_id, { type: 'message_complete', message: assistant }); + for (const block of content) { + if (block.type === 'function_call') { + await emit(session_id, { + type: 'function_execution_start', + function_call_id: block.id, + function_id: block.function_id, + args: block.arguments, + }); + } + } + } else if (msg.type === 'user') { + const blocks = Array.isArray(msg.message.content) ? msg.message.content : []; + for (const b of blocks as unknown as Array>) { + if (b.type !== 'tool_result') continue; + const callId = String(b.tool_use_id ?? ''); + const call = calls.get(callId); + const content = mapToolResultContent(b.content); + const fr = makeFunctionResult( + callId, + call?.function_id ?? 'claude-code::unknown', + content, + b.is_error === true, + ); + transcript.push(fr); + pendingResults.push(fr); + await emit(session_id, { + type: 'function_execution_end', + function_call_id: callId, + function_id: fr.function_id, + result: { content, details: null }, + is_error: fr.is_error, + duration_ms: call ? Date.now() - call.started_at : 0, + }); + } + } else if (msg.type === 'result') { + record.claude_session_id = msg.session_id ?? record.claude_session_id; + record.turns += msg.num_turns ?? 1; + record.total_cost_usd += msg.total_cost_usd ?? 0; + record.usage = mapUsage(msg.usage); + isError = msg.is_error === true; + stopReason = msg.subtype === 'success' ? 'end' : msg.subtype; + resultText = 'result' in msg && typeof msg.result === 'string' ? msg.result : ''; + } + } + } catch (err) { + isError = true; + stopReason = 'error'; + resultText = String(err); + } finally { + live.delete(session_id); + } + + record.status = isError ? 'error' : 'done'; + record.updated_at_ms = Date.now(); + await saveSession(iii, record); + + if (transcript.length === 0) { + transcript.push( + makeAssistantMessage( + [{ type: 'text', text: resultText }], + record.model, + record.usage, + stopReason, + ), + ); + } + await emit(session_id, { + type: 'turn_end', + message: lastAssistant(transcript), + function_results: pendingResults, + }); + await emit(session_id, { type: 'agent_end', messages: transcript }); + + return { + session_id, + claude_session_id: record.claude_session_id, + result: resultText, + stop_reason: stopReason, + is_error: isError, + num_turns: record.turns, + total_cost_usd: record.total_cost_usd, + usage: record.usage, + }; +} + +export function register(iii: ISdk, cfg: Config, emit: Emit): void { + iii.registerFunction( + 'claude::run', + async (payload: unknown) => executeRun(iii, cfg, emit, RunPayloadSchema.parse(payload ?? {})), + { + description: + 'Run one Claude Code turn and wait for the result. Accepts `prompt` or brain-contract `messages`; streams AgentEvent frames onto agent::events and returns {session_id, result, usage, total_cost_usd}.', + }, + ); + + iii.registerFunction( + 'claude::start', + async (payload: unknown) => { + const parsed = RunPayloadSchema.parse(payload ?? {}); + const session_id = parsed.session_id ?? randomUUID(); + void executeRun(iii, cfg, emit, { ...parsed, session_id }).catch((err) => + console.error(`claude::start background run failed for ${session_id}: ${String(err)}`), + ); + return { session_id, started: true }; + }, + { + description: + 'Start a Claude Code turn and return immediately; watch agent::events (group_id = session_id) for progress and turn_end.', + }, + ); + + iii.registerFunction( + 'claude::stop', + async (payload: unknown) => { + const { session_id } = z.object({ session_id: z.string() }).parse(payload ?? {}); + const run = live.get(session_id); + if (!run) return { session_id, stopped: false, reason: 'no live run' }; + await run.interrupt(); + return { session_id, stopped: true }; + }, + { description: 'Interrupt a live Claude Code run for a session.' }, + ); + + iii.registerFunction( + 'claude::status', + async (payload: unknown) => { + const { session_id } = z.object({ session_id: z.string() }).parse(payload ?? {}); + const record = await loadSession(iii, session_id); + return { session_id, live: live.has(session_id), record }; + }, + { description: 'Point-in-time status of a Claude Code session.' }, + ); + + iii.registerFunction( + 'claude::sessions::list', + async () => ({ sessions: await listSessions(iii) }), + { description: 'List every Claude Code session this worker has run.' }, + ); + + iii.registerFunction( + 'run::start_and_wait', + async (payload: unknown) => executeRun(iii, cfg, emit, RunPayloadSchema.parse(payload ?? {})), + { + description: + 'Canonical iii brain entrypoint backed by Claude Code: run a turn for {session_id, messages} and return when it ends.', + }, + ); +} diff --git a/claude-code/src/state.ts b/claude-code/src/state.ts new file mode 100644 index 000000000..f5f423cbb --- /dev/null +++ b/claude-code/src/state.ts @@ -0,0 +1,33 @@ +/** + * Session registry on engine state. Scope `claude_sessions`, key = iii + * session_id. Maps iii sessions to Claude Code session ids so `claude::run` + * with the same session_id resumes the underlying Claude conversation. + */ + +import type { ISdk } from 'iii-sdk'; +import type { SessionRecord } from './types.js'; + +const SCOPE = 'claude_sessions'; + +export async function loadSession(iii: ISdk, session_id: string): Promise { + const res = await iii.trigger({ + function_id: 'state::get', + payload: { scope: SCOPE, key: session_id }, + }); + return res && typeof res === 'object' && 'session_id' in res ? res : null; +} + +export async function saveSession(iii: ISdk, record: SessionRecord): Promise { + await iii.trigger({ + function_id: 'state::set', + payload: { scope: SCOPE, key: record.session_id, value: record }, + }); +} + +export async function listSessions(iii: ISdk): Promise { + const res = await iii.trigger({ + function_id: 'state::list', + payload: { scope: SCOPE }, + }); + return Array.isArray(res) ? res : []; +} diff --git a/claude-code/src/types.ts b/claude-code/src/types.ts new file mode 100644 index 000000000..d99097a30 --- /dev/null +++ b/claude-code/src/types.ts @@ -0,0 +1,88 @@ +/** + * Wire types for the AgentEvent subset this worker emits onto the + * `agent::events` stream. Mirrors harness/src/types/* in iii-hq/workers so + * the console and acp worker render Claude Code turns like any other brain. + */ + +export type TextContent = { type: 'text'; text: string }; +export type ThinkingContent = { type: 'thinking'; text: string; signature?: string }; +export type FunctionCallContent = { + type: 'function_call'; + id: string; + function_id: string; + arguments: unknown; +}; +export type FunctionResultContent = { + type: 'function_result'; + function_call_id: string; + content: ContentBlock[]; + is_error?: boolean; +}; +export type ContentBlock = + | TextContent + | ThinkingContent + | FunctionCallContent + | FunctionResultContent; + +export type Usage = { + input_tokens: number; + output_tokens: number; + cache_read_tokens?: number; + cache_write_tokens?: number; +}; + +export type AssistantMessage = { + role: 'assistant'; + content: ContentBlock[]; + stop_reason: string; + error_message?: string | null; + usage?: Usage | null; + model: string; + provider: string; + timestamp: number; +}; + +export type UserMessage = { role: 'user'; content: ContentBlock[]; timestamp: number }; + +export type FunctionResultMessage = { + role: 'function_result'; + function_call_id: string; + function_id: string; + content: ContentBlock[]; + details: unknown; + is_error: boolean; + timestamp: number; +}; + +export type AgentMessage = UserMessage | AssistantMessage | FunctionResultMessage; + +export type AgentEvent = + | { type: 'agent_end'; messages: AgentMessage[] } + | { type: 'turn_end'; message: AgentMessage; function_results: FunctionResultMessage[] } + | { type: 'message_complete'; message: AgentMessage; body_streamed?: boolean } + | { + type: 'function_execution_start'; + function_call_id: string; + function_id: string; + args: unknown; + } + | { + type: 'function_execution_end'; + function_call_id: string; + function_id: string; + result: { content: ContentBlock[]; details: unknown }; + is_error: boolean; + duration_ms: number; + }; + +export type SessionRecord = { + session_id: string; + claude_session_id: string | null; + cwd: string; + model: string; + status: 'working' | 'done' | 'error'; + turns: number; + total_cost_usd: number; + usage: Usage | null; + updated_at_ms: number; +}; diff --git a/claude-code/tests/config.test.ts b/claude-code/tests/config.test.ts new file mode 100644 index 000000000..9beb12080 --- /dev/null +++ b/claude-code/tests/config.test.ts @@ -0,0 +1,44 @@ +import { mkdtemp, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { loadConfig } from '../src/config.js'; + +describe('loadConfig', () => { + it('returns full defaults when the file is missing', async () => { + const cfg = await loadConfig('/nonexistent/config.yaml'); + expect(cfg.engine_url).toBe('ws://127.0.0.1:49134'); + expect(cfg.defaults.permission_mode).toBe('acceptEdits'); + expect(cfg.defaults.max_turns).toBe(50); + expect(cfg.expose_iii_bridge).toBe(true); + expect(cfg.approval_gate).toBe(false); + expect(cfg.events_stream).toBe('agent::events'); + expect(cfg.claude_executable).toBe(''); + }); + + it('merges a partial file over defaults', async () => { + const dir = await mkdtemp(join(tmpdir(), 'claude-code-config-')); + const path = join(dir, 'config.yaml'); + await writeFile( + path, + [ + 'engine_url: ws://10.0.0.1:49134', + 'defaults:', + ' permission_mode: plan', + 'approval_gate: true', + ].join('\n'), + ); + const cfg = await loadConfig(path); + expect(cfg.engine_url).toBe('ws://10.0.0.1:49134'); + expect(cfg.defaults.permission_mode).toBe('plan'); + expect(cfg.defaults.max_turns).toBe(50); + expect(cfg.approval_gate).toBe(true); + }); + + it('rejects an invalid permission mode', async () => { + const dir = await mkdtemp(join(tmpdir(), 'claude-code-config-')); + const path = join(dir, 'config.yaml'); + await writeFile(path, 'defaults:\n permission_mode: yolo\n'); + await expect(loadConfig(path)).rejects.toThrow(); + }); +}); diff --git a/claude-code/tests/executable.test.ts b/claude-code/tests/executable.test.ts new file mode 100644 index 000000000..c1f37a191 --- /dev/null +++ b/claude-code/tests/executable.test.ts @@ -0,0 +1,34 @@ +import { chmodSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { resolveClaudeExecutable } from '../src/executable.js'; + +describe('resolveClaudeExecutable', () => { + const originalPath = process.env.PATH; + + beforeEach(() => { + process.env.PATH = ''; + }); + + afterEach(() => { + process.env.PATH = originalPath; + }); + + it('returns the configured path untouched', () => { + expect(resolveClaudeExecutable('/opt/claude')).toBe('/opt/claude'); + }); + + it('finds an executable claude on PATH', () => { + const dir = mkdtempSync(join(tmpdir(), 'claude-exe-')); + const bin = join(dir, 'claude'); + writeFileSync(bin, '#!/bin/sh\n'); + chmodSync(bin, 0o755); + process.env.PATH = dir; + expect(resolveClaudeExecutable('')).toBe(bin); + }); + + it('returns empty when nothing is found', () => { + expect(resolveClaudeExecutable('')).toBe(''); + }); +}); diff --git a/claude-code/tests/map.test.ts b/claude-code/tests/map.test.ts new file mode 100644 index 000000000..a86a5a671 --- /dev/null +++ b/claude-code/tests/map.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from 'vitest'; +import { + lastAssistant, + makeAssistantMessage, + makeFunctionResult, + mapAssistantContent, + mapToolResultContent, + mapUsage, + type ToolCallIndex, + toolFunctionId, +} from '../src/map.js'; +import type { AgentMessage } from '../src/types.js'; + +describe('toolFunctionId', () => { + it('namespaces built-in Claude Code tools', () => { + expect(toolFunctionId('Bash')).toBe('claude-code::Bash'); + expect(toolFunctionId('Edit')).toBe('claude-code::Edit'); + }); + + it('maps MCP tool names back to bus-style ids', () => { + expect(toolFunctionId('mcp__iii__trigger')).toBe('iii::trigger'); + expect(toolFunctionId('mcp__iii__functions_list')).toBe('iii::functions_list'); + }); +}); + +describe('mapAssistantContent', () => { + it('maps text, thinking, and tool_use blocks and indexes calls', () => { + const calls: ToolCallIndex = new Map(); + const blocks = [ + { type: 'text', text: 'hello' }, + { type: 'thinking', thinking: 'hmm' }, + { type: 'tool_use', id: 'toolu_1', name: 'Bash', input: { command: 'ls' } }, + ]; + const out = mapAssistantContent(blocks, calls); + expect(out).toEqual([ + { type: 'text', text: 'hello' }, + { type: 'thinking', text: 'hmm' }, + { + type: 'function_call', + id: 'toolu_1', + function_id: 'claude-code::Bash', + arguments: { command: 'ls' }, + }, + ]); + expect(calls.get('toolu_1')?.function_id).toBe('claude-code::Bash'); + }); + + it('drops empty and unknown blocks', () => { + const calls: ToolCallIndex = new Map(); + expect(mapAssistantContent([{ type: 'text' }, { type: 'mystery' }], calls)).toEqual([]); + }); +}); + +describe('mapToolResultContent', () => { + it('wraps strings as one text block', () => { + expect(mapToolResultContent('ok')).toEqual([{ type: 'text', text: 'ok' }]); + }); + + it('flattens block arrays to text', () => { + expect(mapToolResultContent([{ type: 'text', text: 'a' }, { type: 'image' }])).toEqual([ + { type: 'text', text: 'a' }, + { type: 'text', text: '{"type":"image"}' }, + ]); + }); + + it('stringifies anything else', () => { + expect(mapToolResultContent({ ok: true })).toEqual([{ type: 'text', text: '{"ok":true}' }]); + expect(mapToolResultContent(undefined)).toEqual([{ type: 'text', text: 'null' }]); + }); +}); + +describe('mapUsage', () => { + it('maps SDK usage fields onto the wire shape', () => { + expect( + mapUsage({ + input_tokens: 10, + output_tokens: 5, + cache_read_input_tokens: 100, + cache_creation_input_tokens: 7, + }), + ).toEqual({ + input_tokens: 10, + output_tokens: 5, + cache_read_tokens: 100, + cache_write_tokens: 7, + }); + }); + + it('returns null for non-objects', () => { + expect(mapUsage(null)).toBeNull(); + expect(mapUsage('x')).toBeNull(); + }); +}); + +describe('message constructors', () => { + it('builds an assistant message with provider claude-code', () => { + const msg = makeAssistantMessage([{ type: 'text', text: 'hi' }], 'claude-opus-4-8', null); + expect(msg.role).toBe('assistant'); + expect(msg.provider).toBe('claude-code'); + expect(msg.stop_reason).toBe('end'); + }); + + it('builds a function_result message', () => { + const fr = makeFunctionResult( + 'toolu_1', + 'claude-code::Bash', + [{ type: 'text', text: 'ok' }], + false, + ); + expect(fr.role).toBe('function_result'); + expect(fr.function_call_id).toBe('toolu_1'); + expect(fr.is_error).toBe(false); + }); +}); + +describe('lastAssistant', () => { + it('returns the last assistant message', () => { + const a1 = makeAssistantMessage([{ type: 'text', text: 'one' }], 'm', null); + const a2 = makeAssistantMessage([{ type: 'text', text: 'two' }], 'm', null); + const fr = makeFunctionResult('id', 'fn', [], false); + const messages: AgentMessage[] = [a1, a2, fr]; + expect(lastAssistant(messages)).toBe(a2); + }); + + it('falls back to the final message when no assistant exists', () => { + const fr = makeFunctionResult('id', 'fn', [], false); + expect(lastAssistant([fr])).toBe(fr); + }); +}); diff --git a/claude-code/tests/run-payload.test.ts b/claude-code/tests/run-payload.test.ts new file mode 100644 index 000000000..67d013eed --- /dev/null +++ b/claude-code/tests/run-payload.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; +import { extractPrompt, RunPayloadSchema } from '../src/run.js'; + +describe('RunPayloadSchema', () => { + it('accepts a bare prompt', () => { + const p = RunPayloadSchema.parse({ prompt: 'hi' }); + expect(p.prompt).toBe('hi'); + }); + + it('accepts the brain-contract messages shape', () => { + const p = RunPayloadSchema.parse({ + session_id: 's1', + messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }], + }); + expect(p.messages).toHaveLength(1); + }); + + it('rejects an invalid permission mode', () => { + expect(() => RunPayloadSchema.parse({ prompt: 'x', permission_mode: 'yolo' })).toThrow(); + }); +}); + +describe('extractPrompt', () => { + it('prefers the prompt field', () => { + expect(extractPrompt(RunPayloadSchema.parse({ prompt: 'direct' }))).toBe('direct'); + }); + + it('joins text blocks from the last user message', () => { + const p = RunPayloadSchema.parse({ + messages: [ + { role: 'user', content: [{ type: 'text', text: 'first' }] }, + { role: 'assistant', content: [{ type: 'text', text: 'reply' }] }, + { + role: 'user', + content: [ + { type: 'text', text: 'line one' }, + { type: 'text', text: 'line two' }, + ], + }, + ], + }); + expect(extractPrompt(p)).toBe('line one\nline two'); + }); + + it('accepts plain-string message content', () => { + const p = RunPayloadSchema.parse({ messages: [{ role: 'user', content: 'plain' }] }); + expect(extractPrompt(p)).toBe('plain'); + }); + + it('throws when no user message exists', () => { + const p = RunPayloadSchema.parse({ messages: [{ role: 'assistant', content: 'x' }] }); + expect(() => extractPrompt(p)).toThrow(); + }); +}); diff --git a/claude-code/tsconfig.json b/claude-code/tsconfig.json new file mode 100644 index 000000000..9a3b09b03 --- /dev/null +++ b/claude-code/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "declaration": false, + "sourceMap": false, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/claude-code/vitest.config.ts b/claude-code/vitest.config.ts new file mode 100644 index 000000000..1c1ef7836 --- /dev/null +++ b/claude-code/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.test.ts'], + environment: 'node', + globals: false, + testTimeout: 10000, + }, +}); From 890c8c111f8c1cecb44d39d21f43d1aac980acde Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:23:48 +0100 Subject: [PATCH 02/23] feat(claude-code): pure API pass-through, raw claude::events stream, opt-in bus bridge - options field on claude::run forwards any Agent SDK Options key verbatim (forkSession, includePartialMessages, fallbackModel, addDirs, mcpServers, ...) - every SDK message mirrors verbatim onto the claude::events stream (system/init, assistant, user, result, stream_event partials), alongside the translated AgentEvent view on agent::events - expose_iii_bridge now defaults to false: the worker is a plain Claude Code API surface unless the operator opts into the mcp__iii__* bus tools; user MCP servers pass through options - zero-to-turn quickstart in README (install engine, worker add, iii) --- claude-code/README.md | 40 +++++++++++++++++++++++++++----- claude-code/config.yaml | 10 ++++++-- claude-code/skills/SKILL.md | 18 +++++++------- claude-code/src/config.ts | 3 ++- claude-code/src/events.ts | 4 +--- claude-code/src/index.ts | 3 ++- claude-code/src/run.ts | 29 ++++++++++++++++++----- claude-code/tests/config.test.ts | 3 ++- 8 files changed, 82 insertions(+), 28 deletions(-) diff --git a/claude-code/README.md b/claude-code/README.md index a59bcd425..2a424ac82 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -1,8 +1,8 @@ # claude-code -Claude Code as an iii worker. `claude::run` executes a headless Claude Code turn (file edits, shell, web, MCP tools) against any directory on the host and returns the result over the iii bus. Every turn streams AgentEvent frames onto `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. +Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web, MCP). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. -The integration is bidirectional. While iii drives Claude Code through `claude::*`, an in-process MCP bridge hands Claude three tools (`mcp__iii__functions_list`, `mcp__iii__functions_info`, `mcp__iii__trigger`) that expose the live engine catalog, so a running Claude Code turn can call any function registered by any worker on the bus. +Optionally the integration runs bidirectional: with `expose_iii_bridge: true` an in-process MCP bridge hands Claude three tools (`mcp__iii__functions_list`, `mcp__iii__functions_info`, `mcp__iii__trigger`) that expose the live engine catalog, so a running Claude Code turn can call any function registered by any worker on the bus. Off by default; the worker is a pure Claude Code API surface unless the operator opts in. ## Install @@ -22,7 +22,15 @@ npx skills add iii-hq/workers --skill claude-code ## Quickstart -Start the engine, then run a turn from any SDK: +From zero to a Claude Code turn over the bus: + +```bash +curl -fsSL https://install.iii.dev/iii/main/install.sh | sh +iii worker add claude-code +iii # starts the engine + worker +``` + +Then talk to it like any other function: from the console chat, from `iii trigger claude::run`, or from any SDK: ```ts import { registerWorker } from 'iii-sdk'; @@ -58,6 +66,24 @@ Long turns: use `claude::start` to return immediately, then watch `agent::events `claude::run` accepts either a bare `prompt` string or the brain-contract `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `system_prompt`, `append_system_prompt`, `permission_mode`, `allowed_tools`, `disallowed_tools`, and `max_turns` overrides. +### Raw API pass-through + +The named fields above cover the common path; everything else the Agent SDK accepts goes through the `options` field untouched (camelCase, exactly as in the SDK): + +```jsonc +{ + "prompt": "...", + "options": { + "forkSession": true, + "includePartialMessages": true, + "fallbackModel": "claude-sonnet-4-6", + "addDirs": ["/another/repo"] + } +} +``` + +And the full output side is available raw: every message Claude Code emits (`system/init`, `assistant`, `user`, `result`, and `stream_event` token deltas when `includePartialMessages` is set) is mirrored verbatim onto the `claude::events` stream, group_id = session_id. Consumers that want the exact Claude Code wire format read `claude::events`; consumers that want harness-shaped frames read `agent::events`. Same turn, two views. + ## Configuration ```yaml @@ -69,9 +95,10 @@ defaults: max_turns: 50 cwd: "" # default working directory for runs -expose_iii_bridge: true # give Claude the mcp__iii__* tools +expose_iii_bridge: false # opt-in: give Claude the mcp__iii__* bus tools approval_gate: false # route tool permissions through policy::check_permissions -events_stream: agent::events +events_stream: agent::events # translated AgentEvent frames +raw_events_stream: claude::events # verbatim Claude Code messages claude_executable: "" # path to the claude CLI; empty = SDK default resolution ``` @@ -82,9 +109,10 @@ With `approval_gate: true` and the harness worker installed, every Claude Code t | Claude Code | iii | | --- | --- | | SDK `query()` turn | `claude::run` invocation | +| every SDK message, verbatim | `claude::events` stream frame | | assistant message | `message_complete` frame on `agent::events` | | tool_use / tool_result | `function_execution_start` / `function_execution_end` frames | | final result | `turn_end` + `agent_end` frames, function return value | | session resume | engine state scope `claude_sessions`, keyed by iii session_id | | permission prompt | `canUseTool` -> `policy::check_permissions` (optional) | -| MCP tools | `mcp__iii__trigger` -> any registered iii function | +| MCP tools | `options.mcpServers` pass-through; opt-in `mcp__iii__trigger` bus bridge | diff --git a/claude-code/config.yaml b/claude-code/config.yaml index 07d216cde..f1f2064a6 100644 --- a/claude-code/config.yaml +++ b/claude-code/config.yaml @@ -9,9 +9,11 @@ defaults: allowed_tools: [] disallowed_tools: [] -# Expose the iii bus to Claude Code as in-process MCP tools +# Optional: expose the iii bus to Claude Code as in-process MCP tools # (mcp__iii__trigger / mcp__iii__functions_list / mcp__iii__functions_info). -expose_iii_bridge: true +# Off by default: the worker is a pure Claude Code API surface unless the +# operator opts in. Bring-your-own MCP servers pass through options.mcpServers. +expose_iii_bridge: false # Route Claude Code permission prompts through policy::check_permissions # (harness approval gate). Fail-closed when the gate is unreachable. @@ -22,6 +24,10 @@ approval_gate: false # acp worker both read this stream. events_stream: agent::events +# Raw Claude Code messages (exact Agent SDK shapes: system/init, assistant, +# user, result, stream_event) mirrored verbatim here, grouped by session_id. +raw_events_stream: claude::events + # Path to the Claude Code CLI binary. Empty = Agent SDK default resolution; # set to an absolute path (or "claude") when running the single-file bundle. claude_executable: "" diff --git a/claude-code/skills/SKILL.md b/claude-code/skills/SKILL.md index 3c058ad2d..6055a56ae 100644 --- a/claude-code/skills/SKILL.md +++ b/claude-code/skills/SKILL.md @@ -17,12 +17,13 @@ and the acp worker render Claude Code turns like any native harness turn. The worker also registers `run::start_and_wait`, so anything built to drive the canonical brain contract can drive Claude Code unchanged. -The integration is bidirectional. Each turn carries an in-process MCP bridge -exposing `mcp__iii__functions_list`, `mcp__iii__functions_info`, and -`mcp__iii__trigger`, so the running Claude turn can discover and invoke any -function registered on the engine — shell, database, storage, or your own -workers. Requires the `claude` CLI on the host with an existing login or -`ANTHROPIC_API_KEY` in the worker environment. +With `expose_iii_bridge: true` the integration runs bidirectional: each turn +carries an in-process MCP bridge exposing `mcp__iii__functions_list`, +`mcp__iii__functions_info`, and `mcp__iii__trigger`, so the running Claude +turn can discover and invoke any function registered on the engine — shell, +database, storage, or your own workers. Off by default. Requires the `claude` +CLI on the host with an existing login or `ANTHROPIC_API_KEY` in the worker +environment. ## When to Use @@ -34,8 +35,9 @@ workers. Requires the `claude` CLI on the host with an existing login or - Run long agentic jobs in the background with `claude::start` and watch `agent::events` for `message_complete`, `function_execution_start/end`, and `turn_end` frames; interrupt with `claude::stop`. -- Let an LLM turn reach the rest of your backend: Claude calls - `mcp__iii__trigger` to hit any bus function mid-turn. +- Let an LLM turn reach the rest of your backend: with + `expose_iii_bridge: true`, Claude calls `mcp__iii__trigger` to hit any bus + function mid-turn. ## Boundaries diff --git a/claude-code/src/config.ts b/claude-code/src/config.ts index 31cd860e9..5b7f3e6f7 100644 --- a/claude-code/src/config.ts +++ b/claude-code/src/config.ts @@ -17,9 +17,10 @@ const ConfigSchema = z.object({ disallowed_tools: z.array(z.string()).default([]), }) .prefault({}), - expose_iii_bridge: z.boolean().default(true), + expose_iii_bridge: z.boolean().default(false), approval_gate: z.boolean().default(false), events_stream: z.string().default('agent::events'), + raw_events_stream: z.string().default('claude::events'), claude_executable: z.string().default(''), }); diff --git a/claude-code/src/events.ts b/claude-code/src/events.ts index ac4b41d88..19e41d882 100644 --- a/claude-code/src/events.ts +++ b/claude-code/src/events.ts @@ -6,13 +6,11 @@ import { randomUUID } from 'node:crypto'; import type { ISdk } from 'iii-sdk'; -import type { AgentEvent } from './types.js'; - const PROCESS_EPOCH = randomUUID(); const seqBySession = new Map(); export function makeEmitter(iii: ISdk, streamName: string) { - return async function emit(session_id: string, event: AgentEvent): Promise { + return async function emit(session_id: string, event: unknown): Promise { const seq = seqBySession.get(session_id) ?? 0; seqBySession.set(session_id, seq + 1); const item_id = `${session_id}-${PROCESS_EPOCH}-${seq.toString().padStart(8, '0')}`; diff --git a/claude-code/src/index.ts b/claude-code/src/index.ts index 4aef36623..33a4f70ca 100644 --- a/claude-code/src/index.ts +++ b/claude-code/src/index.ts @@ -26,7 +26,8 @@ const url = values.url ? String(values.url) : cfg.engine_url; const iii = registerWorker(url, { workerName: 'claude-code' }); const emit = makeEmitter(iii, cfg.events_stream); -register(iii, cfg, emit); +const emitRaw = makeEmitter(iii, cfg.raw_events_stream); +register(iii, cfg, emit, emitRaw); console.log(`claude-code worker connected to ${url}`); diff --git a/claude-code/src/run.ts b/claude-code/src/run.ts index a35d109c9..da2414406 100644 --- a/claude-code/src/run.ts +++ b/claude-code/src/run.ts @@ -44,6 +44,11 @@ const RunPayloadSchema = z.object({ disallowed_tools: z.array(z.string()).optional(), max_turns: z.number().int().positive().optional(), timeout_ms: z.number().int().positive().optional(), + /** Raw Agent SDK options, spread over everything the worker derives. + * This is the pure pass-through: any Options field the SDK accepts + * (fork_session, include_partial_messages, betas, add dirs, mcp + * servers, ...) goes through untouched, camelCase as in the SDK. */ + options: z.record(z.string(), z.unknown()).optional(), }); export type RunPayload = z.infer; @@ -85,6 +90,7 @@ export async function executeRun( iii: ISdk, cfg: Config, emit: Emit, + emitRaw: Emit, payload: RunPayload, ): Promise> { const session_id = payload.session_id ?? randomUUID(); @@ -121,8 +127,16 @@ export async function executeRun( : { type: 'preset', preset: 'claude_code', ...(append ? { append } : {}) }, settingSources: [], ...(cfg.claude_executable ? { pathToClaudeCodeExecutable: cfg.claude_executable } : {}), - ...(cfg.expose_iii_bridge ? { mcpServers: { iii: makeIiiBridge(iii) } } : {}), ...(cfg.approval_gate ? { canUseTool: gatedCanUseTool(iii, session_id) } : {}), + ...(payload.options as Partial | undefined), + ...(cfg.expose_iii_bridge + ? { + mcpServers: { + ...((payload.options as Partial | undefined)?.mcpServers ?? {}), + iii: makeIiiBridge(iii), + }, + } + : {}), }; const q = query({ prompt, options }); @@ -137,6 +151,7 @@ export async function executeRun( try { for await (const msg of q) { + await emitRaw(session_id, msg); if (msg.type === 'system' && msg.subtype === 'init') { record.claude_session_id = msg.session_id; await saveSession(iii, record); @@ -231,13 +246,14 @@ export async function executeRun( }; } -export function register(iii: ISdk, cfg: Config, emit: Emit): void { +export function register(iii: ISdk, cfg: Config, emit: Emit, emitRaw: Emit): void { iii.registerFunction( 'claude::run', - async (payload: unknown) => executeRun(iii, cfg, emit, RunPayloadSchema.parse(payload ?? {})), + async (payload: unknown) => + executeRun(iii, cfg, emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), { description: - 'Run one Claude Code turn and wait for the result. Accepts `prompt` or brain-contract `messages`; streams AgentEvent frames onto agent::events and returns {session_id, result, usage, total_cost_usd}.', + 'Run one Claude Code turn and wait for the result. Accepts `prompt` or brain-contract `messages` plus a raw SDK `options` pass-through; streams raw Claude Code messages onto claude::events, AgentEvent frames onto agent::events, and returns {session_id, result, usage, total_cost_usd}.', }, ); @@ -246,7 +262,7 @@ export function register(iii: ISdk, cfg: Config, emit: Emit): void { async (payload: unknown) => { const parsed = RunPayloadSchema.parse(payload ?? {}); const session_id = parsed.session_id ?? randomUUID(); - void executeRun(iii, cfg, emit, { ...parsed, session_id }).catch((err) => + void executeRun(iii, cfg, emit, emitRaw, { ...parsed, session_id }).catch((err) => console.error(`claude::start background run failed for ${session_id}: ${String(err)}`), ); return { session_id, started: true }; @@ -287,7 +303,8 @@ export function register(iii: ISdk, cfg: Config, emit: Emit): void { iii.registerFunction( 'run::start_and_wait', - async (payload: unknown) => executeRun(iii, cfg, emit, RunPayloadSchema.parse(payload ?? {})), + async (payload: unknown) => + executeRun(iii, cfg, emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), { description: 'Canonical iii brain entrypoint backed by Claude Code: run a turn for {session_id, messages} and return when it ends.', diff --git a/claude-code/tests/config.test.ts b/claude-code/tests/config.test.ts index 9beb12080..53cd0a0cd 100644 --- a/claude-code/tests/config.test.ts +++ b/claude-code/tests/config.test.ts @@ -10,9 +10,10 @@ describe('loadConfig', () => { expect(cfg.engine_url).toBe('ws://127.0.0.1:49134'); expect(cfg.defaults.permission_mode).toBe('acceptEdits'); expect(cfg.defaults.max_turns).toBe(50); - expect(cfg.expose_iii_bridge).toBe(true); + expect(cfg.expose_iii_bridge).toBe(false); expect(cfg.approval_gate).toBe(false); expect(cfg.events_stream).toBe('agent::events'); + expect(cfg.raw_events_stream).toBe('claude::events'); expect(cfg.claude_executable).toBe(''); }); From a119e2d50ca34a9ac21cd3056ac9b2484e6ebadf Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:29:12 +0100 Subject: [PATCH 03/23] refactor(claude-code): drop the MCP bus bridge The worker exposes the Claude Code API and nothing else. The in-process MCP server that handed Claude mcp__iii__* bus tools was an extra feature on top of that surface; users who want MCP servers in a turn pass them through options.mcpServers exactly as in the Agent SDK. Removes src/bridge.ts, the expose_iii_bridge config option, and all related wiring and docs. --- claude-code/README.md | 5 +-- claude-code/config.yaml | 6 --- claude-code/skills/SKILL.md | 19 ++++------ claude-code/src/bridge.ts | 63 -------------------------------- claude-code/src/config.ts | 1 - claude-code/src/run.ts | 9 ----- claude-code/tests/config.test.ts | 1 - claude-code/tests/map.test.ts | 6 +-- 8 files changed, 11 insertions(+), 99 deletions(-) delete mode 100644 claude-code/src/bridge.ts diff --git a/claude-code/README.md b/claude-code/README.md index 2a424ac82..89203d1ce 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -2,8 +2,6 @@ Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web, MCP). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. -Optionally the integration runs bidirectional: with `expose_iii_bridge: true` an in-process MCP bridge hands Claude three tools (`mcp__iii__functions_list`, `mcp__iii__functions_info`, `mcp__iii__trigger`) that expose the live engine catalog, so a running Claude Code turn can call any function registered by any worker on the bus. Off by default; the worker is a pure Claude Code API surface unless the operator opts in. - ## Install ```bash @@ -95,7 +93,6 @@ defaults: max_turns: 50 cwd: "" # default working directory for runs -expose_iii_bridge: false # opt-in: give Claude the mcp__iii__* bus tools approval_gate: false # route tool permissions through policy::check_permissions events_stream: agent::events # translated AgentEvent frames raw_events_stream: claude::events # verbatim Claude Code messages @@ -115,4 +112,4 @@ With `approval_gate: true` and the harness worker installed, every Claude Code t | final result | `turn_end` + `agent_end` frames, function return value | | session resume | engine state scope `claude_sessions`, keyed by iii session_id | | permission prompt | `canUseTool` -> `policy::check_permissions` (optional) | -| MCP tools | `options.mcpServers` pass-through; opt-in `mcp__iii__trigger` bus bridge | +| MCP servers | `options.mcpServers` pass-through, exactly as in the SDK | diff --git a/claude-code/config.yaml b/claude-code/config.yaml index f1f2064a6..0d120e09c 100644 --- a/claude-code/config.yaml +++ b/claude-code/config.yaml @@ -9,12 +9,6 @@ defaults: allowed_tools: [] disallowed_tools: [] -# Optional: expose the iii bus to Claude Code as in-process MCP tools -# (mcp__iii__trigger / mcp__iii__functions_list / mcp__iii__functions_info). -# Off by default: the worker is a pure Claude Code API surface unless the -# operator opts in. Bring-your-own MCP servers pass through options.mcpServers. -expose_iii_bridge: false - # Route Claude Code permission prompts through policy::check_permissions # (harness approval gate). Fail-closed when the gate is unreachable. # Requires the harness worker; leave false for standalone use. diff --git a/claude-code/skills/SKILL.md b/claude-code/skills/SKILL.md index 6055a56ae..282047c18 100644 --- a/claude-code/skills/SKILL.md +++ b/claude-code/skills/SKILL.md @@ -2,8 +2,8 @@ name: claude-code description: >- Run headless Claude Code turns over the iii bus — file edits, shell, web, - and MCP tools against any host directory — with AgentEvent streaming, - session resume, and an MCP bridge that lets Claude call any iii function. + and MCP tools against any host directory — with raw message streaming, + AgentEvent translation, and session resume. --- # claude-code @@ -17,13 +17,11 @@ and the acp worker render Claude Code turns like any native harness turn. The worker also registers `run::start_and_wait`, so anything built to drive the canonical brain contract can drive Claude Code unchanged. -With `expose_iii_bridge: true` the integration runs bidirectional: each turn -carries an in-process MCP bridge exposing `mcp__iii__functions_list`, -`mcp__iii__functions_info`, and `mcp__iii__trigger`, so the running Claude -turn can discover and invoke any function registered on the engine — shell, -database, storage, or your own workers. Off by default. Requires the `claude` -CLI on the host with an existing login or `ANTHROPIC_API_KEY` in the worker -environment. +The worker is a pure pass-through: named payload fields cover the common +path, the `options` field forwards any Agent SDK option verbatim (including +`mcpServers`), and the raw Claude Code messages mirror onto `claude::events` +untouched. Requires the `claude` CLI on the host with an existing login or +`ANTHROPIC_API_KEY` in the worker environment. ## When to Use @@ -35,9 +33,6 @@ environment. - Run long agentic jobs in the background with `claude::start` and watch `agent::events` for `message_complete`, `function_execution_start/end`, and `turn_end` frames; interrupt with `claude::stop`. -- Let an LLM turn reach the rest of your backend: with - `expose_iii_bridge: true`, Claude calls `mcp__iii__trigger` to hit any bus - function mid-turn. ## Boundaries diff --git a/claude-code/src/bridge.ts b/claude-code/src/bridge.ts deleted file mode 100644 index 4ddc4717d..000000000 --- a/claude-code/src/bridge.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * In-process MCP server handed to every Claude Code run. Exposes the whole - * iii bus to the model as three tools: - * - * mcp__iii__functions_list — engine::functions::list (discover the catalog) - * mcp__iii__functions_info — engine::functions::info (one function's schema) - * mcp__iii__trigger — invoke any registered iii function - * - * This is what makes the worker bidirectional: iii drives Claude Code via - * claude::run, and Claude Code drives iii back through this bridge. - */ - -import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'; -import type { ISdk } from 'iii-sdk'; -import { z } from 'zod'; - -function asText(value: unknown) { - return { - content: [{ type: 'text' as const, text: JSON.stringify(value ?? null, null, 2) }], - }; -} - -export function makeIiiBridge(iii: ISdk) { - return createSdkMcpServer({ - name: 'iii', - version: '0.1.0', - tools: [ - tool( - 'functions_list', - 'List every function currently registered on the iii engine.', - {}, - async () => - asText(await iii.trigger({ function_id: 'engine::functions::list', payload: {} })), - ), - tool( - 'functions_info', - 'Get the description and input schema of one iii function.', - { function_id: z.string() }, - async ({ function_id }) => - asText( - await iii.trigger({ function_id: 'engine::functions::info', payload: { function_id } }), - ), - ), - tool( - 'trigger', - 'Invoke an iii function with a JSON payload and return its result.', - { - function_id: z.string(), - payload: z.record(z.string(), z.unknown()).default({}), - timeout_ms: z.number().int().positive().optional(), - }, - async ({ function_id, payload, timeout_ms }) => - asText( - await iii.trigger({ - function_id, - payload, - ...(timeout_ms ? { timeoutMs: timeout_ms } : {}), - }), - ), - ), - ], - }); -} diff --git a/claude-code/src/config.ts b/claude-code/src/config.ts index 5b7f3e6f7..f58c77b69 100644 --- a/claude-code/src/config.ts +++ b/claude-code/src/config.ts @@ -17,7 +17,6 @@ const ConfigSchema = z.object({ disallowed_tools: z.array(z.string()).default([]), }) .prefault({}), - expose_iii_bridge: z.boolean().default(false), approval_gate: z.boolean().default(false), events_stream: z.string().default('agent::events'), raw_events_stream: z.string().default('claude::events'), diff --git a/claude-code/src/run.ts b/claude-code/src/run.ts index da2414406..7df2e590f 100644 --- a/claude-code/src/run.ts +++ b/claude-code/src/run.ts @@ -10,7 +10,6 @@ import { randomUUID } from 'node:crypto'; import { query, type Options, type PermissionResult } from '@anthropic-ai/claude-agent-sdk'; import type { ISdk } from 'iii-sdk'; import { z } from 'zod'; -import { makeIiiBridge } from './bridge.js'; import type { Config } from './config.js'; import type { Emit } from './events.js'; import { @@ -129,14 +128,6 @@ export async function executeRun( ...(cfg.claude_executable ? { pathToClaudeCodeExecutable: cfg.claude_executable } : {}), ...(cfg.approval_gate ? { canUseTool: gatedCanUseTool(iii, session_id) } : {}), ...(payload.options as Partial | undefined), - ...(cfg.expose_iii_bridge - ? { - mcpServers: { - ...((payload.options as Partial | undefined)?.mcpServers ?? {}), - iii: makeIiiBridge(iii), - }, - } - : {}), }; const q = query({ prompt, options }); diff --git a/claude-code/tests/config.test.ts b/claude-code/tests/config.test.ts index 53cd0a0cd..bc64124f1 100644 --- a/claude-code/tests/config.test.ts +++ b/claude-code/tests/config.test.ts @@ -10,7 +10,6 @@ describe('loadConfig', () => { expect(cfg.engine_url).toBe('ws://127.0.0.1:49134'); expect(cfg.defaults.permission_mode).toBe('acceptEdits'); expect(cfg.defaults.max_turns).toBe(50); - expect(cfg.expose_iii_bridge).toBe(false); expect(cfg.approval_gate).toBe(false); expect(cfg.events_stream).toBe('agent::events'); expect(cfg.raw_events_stream).toBe('claude::events'); diff --git a/claude-code/tests/map.test.ts b/claude-code/tests/map.test.ts index a86a5a671..bf09c59e5 100644 --- a/claude-code/tests/map.test.ts +++ b/claude-code/tests/map.test.ts @@ -17,9 +17,9 @@ describe('toolFunctionId', () => { expect(toolFunctionId('Edit')).toBe('claude-code::Edit'); }); - it('maps MCP tool names back to bus-style ids', () => { - expect(toolFunctionId('mcp__iii__trigger')).toBe('iii::trigger'); - expect(toolFunctionId('mcp__iii__functions_list')).toBe('iii::functions_list'); + it('maps MCP tool names to bus-style ids', () => { + expect(toolFunctionId('mcp__github__create_issue')).toBe('github::create_issue'); + expect(toolFunctionId('mcp__filesystem__read_file')).toBe('filesystem::read_file'); }); }); From efcc073709a2f78e6fd5fa2f08dfeb4ef30cde34 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:31:09 +0100 Subject: [PATCH 04/23] docs(claude-code): drop MCP framing from README and skill Capabilities beyond Claude Code itself come from other iii workers on the bus, not from MCP wiring. --- claude-code/README.md | 4 ++-- claude-code/skills/SKILL.md | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/claude-code/README.md b/claude-code/README.md index 89203d1ce..635bb827e 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -1,6 +1,6 @@ # claude-code -Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web, MCP). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. +Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. ## Install @@ -112,4 +112,4 @@ With `approval_gate: true` and the harness worker installed, every Claude Code t | final result | `turn_end` + `agent_end` frames, function return value | | session resume | engine state scope `claude_sessions`, keyed by iii session_id | | permission prompt | `canUseTool` -> `policy::check_permissions` (optional) | -| MCP servers | `options.mcpServers` pass-through, exactly as in the SDK | +| extra capability | another iii worker on the bus (`shell`, `database`, `storage`, ...) | diff --git a/claude-code/skills/SKILL.md b/claude-code/skills/SKILL.md index 282047c18..f102988ef 100644 --- a/claude-code/skills/SKILL.md +++ b/claude-code/skills/SKILL.md @@ -1,16 +1,16 @@ --- name: claude-code description: >- - Run headless Claude Code turns over the iii bus — file edits, shell, web, - and MCP tools against any host directory — with raw message streaming, - AgentEvent translation, and session resume. + Run headless Claude Code turns over the iii bus — file edits, shell, and + web against any host directory — with raw message streaming, AgentEvent + translation, and session resume. --- # claude-code The claude-code worker turns Claude Code into an iii brain. `claude::run` executes one headless Claude Code turn (the full agent: file edits, shell -commands, web fetch, MCP tools) in a configured working directory and returns +commands, web fetch) in a configured working directory and returns the final result, token usage, and cost over the bus. Every turn streams AgentEvent frames onto `agent::events` keyed by session id, so the iii console and the acp worker render Claude Code turns like any native harness turn. The @@ -18,9 +18,10 @@ worker also registers `run::start_and_wait`, so anything built to drive the canonical brain contract can drive Claude Code unchanged. The worker is a pure pass-through: named payload fields cover the common -path, the `options` field forwards any Agent SDK option verbatim (including -`mcpServers`), and the raw Claude Code messages mirror onto `claude::events` -untouched. Requires the `claude` CLI on the host with an existing login or +path, the `options` field forwards any Agent SDK option verbatim, and the +raw Claude Code messages mirror onto `claude::events` untouched. When a turn +needs a capability beyond Claude Code itself, add another iii worker to the +bus instead of bolting anything onto this one. Requires the `claude` CLI on the host with an existing login or `ANTHROPIC_API_KEY` in the worker environment. ## When to Use From 7043e55e5128b1e667c06452933db090c50af3c9 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:34:35 +0100 Subject: [PATCH 05/23] docs(claude-code): rewrite skill as a usage doc, drop internal jargon - SKILL.md leads with what the worker exposes and how to call it: payload shapes, both streams, session resume, options pass-through - removes 'brain' / 'canonical brain contract' wording everywhere (skill, README, function descriptions, source comments, test names); run::start_and_wait described as the entrypoint the console and acp worker drive - fixes two stale claims: concurrent runs on one session race (no queueing), and token deltas exist on claude::events when includePartialMessages is set --- claude-code/README.md | 6 +- claude-code/skills/SKILL.md | 85 +++++++++++++++------------ claude-code/src/run.ts | 6 +- claude-code/src/types.ts | 2 +- claude-code/tests/run-payload.test.ts | 2 +- 5 files changed, 55 insertions(+), 46 deletions(-) diff --git a/claude-code/README.md b/claude-code/README.md index 635bb827e..779d7773f 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -1,6 +1,6 @@ # claude-code -Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the canonical brain entrypoint, so anything built to drive an iii brain can drive Claude Code with no changes. +Claude Code as an iii worker: the Claude Code API exposed as functions and streams on the iii bus, nothing else. The worker spawns the same `claude` binary the user runs in their terminal, with the same login, the same filesystem, and the same tools (file edits, shell, web). `claude::run` executes one headless turn and returns the result; the raw Claude Code messages mirror verbatim onto the `claude::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Claude Code run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the same entrypoint the console and the acp worker drive, so both run Claude Code with no changes. ## Install @@ -60,9 +60,9 @@ Long turns: use `claude::start` to return immediately, then watch `agent::events | `claude::stop` | Interrupt a live run | | `claude::status` | Session state, live flag, usage, cost | | `claude::sessions::list` | All sessions this worker has run | -| `run::start_and_wait` | Canonical brain contract alias for `claude::run` | +| `run::start_and_wait` | Alias for `claude::run` under the entrypoint the console and acp worker drive | -`claude::run` accepts either a bare `prompt` string or the brain-contract `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `system_prompt`, `append_system_prompt`, `permission_mode`, `allowed_tools`, `disallowed_tools`, and `max_turns` overrides. +`claude::run` accepts either a bare `prompt` string or a `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `system_prompt`, `append_system_prompt`, `permission_mode`, `allowed_tools`, `disallowed_tools`, and `max_turns` overrides. ### Raw API pass-through diff --git a/claude-code/skills/SKILL.md b/claude-code/skills/SKILL.md index f102988ef..4ec23ba87 100644 --- a/claude-code/skills/SKILL.md +++ b/claude-code/skills/SKILL.md @@ -2,63 +2,72 @@ name: claude-code description: >- Run headless Claude Code turns over the iii bus — file edits, shell, and - web against any host directory — with raw message streaming, AgentEvent - translation, and session resume. + web against any host directory — with verbatim message streaming, session + resume, and full Agent SDK option pass-through. --- # claude-code -The claude-code worker turns Claude Code into an iii brain. `claude::run` -executes one headless Claude Code turn (the full agent: file edits, shell -commands, web fetch) in a configured working directory and returns -the final result, token usage, and cost over the bus. Every turn streams -AgentEvent frames onto `agent::events` keyed by session id, so the iii console -and the acp worker render Claude Code turns like any native harness turn. The -worker also registers `run::start_and_wait`, so anything built to drive the -canonical brain contract can drive Claude Code unchanged. +The claude-code worker exposes the Claude Code API as iii functions. One +`claude::run` call executes one headless Claude Code turn — the same agent +the user runs in their terminal, with the same login, filesystem, and +permission model — in a chosen working directory, and returns the final +result, token usage, and cost. The worker is a pure pass-through: named +payload fields cover the common path, the `options` field forwards any Agent +SDK option verbatim, and every message Claude Code emits mirrors untouched +onto the `claude::events` stream. A translated AgentEvent view lands on +`agent::events`, which is what the iii console and the acp worker render. -The worker is a pure pass-through: named payload fields cover the common -path, the `options` field forwards any Agent SDK option verbatim, and the -raw Claude Code messages mirror onto `claude::events` untouched. When a turn -needs a capability beyond Claude Code itself, add another iii worker to the -bus instead of bolting anything onto this one. Requires the `claude` CLI on the host with an existing login or -`ANTHROPIC_API_KEY` in the worker environment. +Requires the `claude` CLI on the host with an existing login or +`ANTHROPIC_API_KEY` in the worker environment. When a turn needs a +capability beyond Claude Code itself, add another iii worker to the bus +instead of bolting anything onto this one. ## When to Use -- Delegate a whole coding task ("add an endpoint and run the tests") to a - full agent from any iii worker or trigger, instead of orchestrating - individual `coder::*` / `shell::*` calls yourself. -- Continue a conversation across calls: pass the same `session_id` and the - worker resumes the underlying Claude Code session with full context. -- Run long agentic jobs in the background with `claude::start` and watch - `agent::events` for `message_complete`, `function_execution_start/end`, - and `turn_end` frames; interrupt with `claude::stop`. +- Delegate a whole coding task ("add an endpoint and run the tests") in one + call, instead of orchestrating individual `coder::*` / `shell::*` calls + yourself: `claude::run` with `prompt` and `cwd`. +- Continue a conversation across calls: pass the same `session_id` again and + the worker resumes the underlying Claude Code session with full context. +- Run long jobs without holding the call open: `claude::start` returns + `{session_id, started}` immediately; follow `claude::events` (group_id = + session_id) for raw progress or `agent::events` for the rendered view; + interrupt with `claude::stop`. +- Reach past the named payload fields: anything the Agent SDK accepts goes + through `options` unchanged — `{"options": {"forkSession": true, + "includePartialMessages": true}}` — and `includePartialMessages` puts + token-level `stream_event` frames on `claude::events`. ## Boundaries - Spawns the host `claude` CLI per turn — needs Claude Code installed and authenticated; not available inside a bare container without it. -- Tool execution happens inside Claude Code's own sandbox and permission - model (`permission_mode`, `allowed_tools`, `disallowed_tools`), not the - engine's; set `approval_gate: true` to route every tool call through +- Function execution happens inside Claude Code's own permission model + (`permission_mode`, `allowed_tools`, `disallowed_tools`), not the + engine's; set `approval_gate: true` to route every call through `policy::check_permissions` (fail-closed, needs the harness worker). -- One live run per session id; a second `claude::run` for a busy session - waits on the engine queue rather than merging into the live turn. -- Emits the AgentEvent subset (`message_complete`, - `function_execution_start/end`, `turn_end`, `agent_end`) — no - token-by-token `message_update` deltas. +- One turn per session at a time: check `claude::status` (`live: true`) + before sending another `claude::run` for the same `session_id`; parallel + runs against one session race on the underlying Claude Code resume. +- `agent::events` carries whole-message frames (`message_complete`, + `function_execution_start/end`, `turn_end`, `agent_end`); token deltas + exist only on `claude::events` and only when `includePartialMessages` is + set. ## Functions -- `claude::run` — run one Claude Code turn and wait; accepts `prompt` or - brain-contract `messages`, plus `model`, `cwd`, `permission_mode`, - `allowed_tools`, `max_turns` overrides; returns - `{session_id, result, usage, total_cost_usd}`. +- `claude::run` — run one Claude Code turn and wait; accepts `prompt` (or a + `messages` array whose last user entry becomes the prompt), plus `model`, + `cwd`, `permission_mode`, `allowed_tools`, `disallowed_tools`, + `max_turns`, `system_prompt`, `append_system_prompt`, and raw `options`; + returns `{session_id, claude_session_id, result, stop_reason, usage, + total_cost_usd}`. - `claude::start` — same payload, returns `{session_id, started}` - immediately; progress arrives on `agent::events`. + immediately; progress arrives on the streams. - `claude::stop` — interrupt the live run for a session. - `claude::status` — point-in-time session view: live flag, status, turns, usage, cost. - `claude::sessions::list` — every session this worker has run. -- `run::start_and_wait` — canonical brain entrypoint backed by Claude Code. +- `run::start_and_wait` — alias for `claude::run` under the entrypoint the + console and acp worker drive, so both run Claude Code with no changes. diff --git a/claude-code/src/run.ts b/claude-code/src/run.ts index 7df2e590f..bec024021 100644 --- a/claude-code/src/run.ts +++ b/claude-code/src/run.ts @@ -1,6 +1,6 @@ /** * claude::* function registrations. `claude::run` accepts either a bare - * `prompt` string or the canonical brain-contract shape (`messages` array of + * `prompt` string or the shared agent entrypoint shape (`messages` array of * role/content-block messages), so anything that can drive * `run::start_and_wait` — the acp worker, the console, another worker — can * drive Claude Code unchanged. @@ -244,7 +244,7 @@ export function register(iii: ISdk, cfg: Config, emit: Emit, emitRaw: Emit): voi executeRun(iii, cfg, emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), { description: - 'Run one Claude Code turn and wait for the result. Accepts `prompt` or brain-contract `messages` plus a raw SDK `options` pass-through; streams raw Claude Code messages onto claude::events, AgentEvent frames onto agent::events, and returns {session_id, result, usage, total_cost_usd}.', + 'Run one Claude Code turn and wait for the result. Accepts `prompt` or a `messages` array plus a raw SDK `options` pass-through; streams raw Claude Code messages onto claude::events, AgentEvent frames onto agent::events, and returns {session_id, result, usage, total_cost_usd}.', }, ); @@ -298,7 +298,7 @@ export function register(iii: ISdk, cfg: Config, emit: Emit, emitRaw: Emit): voi executeRun(iii, cfg, emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), { description: - 'Canonical iii brain entrypoint backed by Claude Code: run a turn for {session_id, messages} and return when it ends.', + 'Alias for claude::run under the shared agent entrypoint: run a turn for {session_id, messages} and return when it ends.', }, ); } diff --git a/claude-code/src/types.ts b/claude-code/src/types.ts index d99097a30..d1f7c34aa 100644 --- a/claude-code/src/types.ts +++ b/claude-code/src/types.ts @@ -1,7 +1,7 @@ /** * Wire types for the AgentEvent subset this worker emits onto the * `agent::events` stream. Mirrors harness/src/types/* in iii-hq/workers so - * the console and acp worker render Claude Code turns like any other brain. + * the console and acp worker render Claude Code turns like any other agent worker. */ export type TextContent = { type: 'text'; text: string }; diff --git a/claude-code/tests/run-payload.test.ts b/claude-code/tests/run-payload.test.ts index 67d013eed..a49bb253c 100644 --- a/claude-code/tests/run-payload.test.ts +++ b/claude-code/tests/run-payload.test.ts @@ -7,7 +7,7 @@ describe('RunPayloadSchema', () => { expect(p.prompt).toBe('hi'); }); - it('accepts the brain-contract messages shape', () => { + it('accepts the messages array shape', () => { const p = RunPayloadSchema.parse({ session_id: 's1', messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }], From 8a719001224e5c241bd62898d91ea0f38f282029 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 12:39:36 +0100 Subject: [PATCH 06/23] test(claude-code): cover the registered surface, not just helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the suite to the level of the other workers' tests: handlers exercised at the engine's unknown boundary with an in-memory bus fake and a scripted Agent SDK query mock. - tests/_helpers: fake ISdk (state::get/set/list semantics, stream::set capture, registerFunction capture) and scripted query() fixtures - run.test.ts: full executeRun flow — result/usage/cost mapping, working->done record lifecycle, verbatim claude::events mirror, AgentEvent sequence with function_execution pairs, named-field and raw-options forwarding, resume for known sessions, SDK throw path, non-success stop reasons; approval gate allow/deny/fail-closed/off - register.test.ts: all six function ids, boundary parse rejection, claude::start background completion, claude::stop on live and ghost runs, status and sessions::list - events.test.ts: frame shape, per-session monotonic item_ids, stream::set failures swallowed - state.test.ts: scope round-trip, direct-value state::get reply (regression for the live resume bug), non-array list tolerance 57 tests, up from 26. --- claude-code/tests/_helpers/fake-iii.ts | 57 +++++ claude-code/tests/_helpers/fake-query.ts | 57 +++++ claude-code/tests/events.test.ts | 45 ++++ claude-code/tests/register.test.ts | 154 +++++++++++++ claude-code/tests/run.test.ts | 272 +++++++++++++++++++++++ claude-code/tests/state.test.ts | 58 +++++ 6 files changed, 643 insertions(+) create mode 100644 claude-code/tests/_helpers/fake-iii.ts create mode 100644 claude-code/tests/_helpers/fake-query.ts create mode 100644 claude-code/tests/events.test.ts create mode 100644 claude-code/tests/register.test.ts create mode 100644 claude-code/tests/run.test.ts create mode 100644 claude-code/tests/state.test.ts diff --git a/claude-code/tests/_helpers/fake-iii.ts b/claude-code/tests/_helpers/fake-iii.ts new file mode 100644 index 000000000..ff2ec05c6 --- /dev/null +++ b/claude-code/tests/_helpers/fake-iii.ts @@ -0,0 +1,57 @@ +import { vi } from 'vitest'; +import type { ISdk } from 'iii-sdk'; + +export type TriggerCall = { function_id: string; payload: Record }; + +export type FakeIii = { + iii: ISdk; + calls: TriggerCall[]; + state: Map; + streamFrames: (stream: string) => Array>; + registered: Map Promise>; +}; + +/** + * In-memory stand-in for the engine bus: `state::get/set/list` backed by a + * Map keyed `${scope}/${key}`, `stream::set` recorded as plain calls, and + * `registerFunction` captured so tests can invoke handlers at the same + * unknown boundary the engine uses. + */ +export function fakeIii(): FakeIii { + const calls: TriggerCall[] = []; + const state = new Map(); + const registered = new Map Promise>(); + + const iii = { + trigger: async (req: { function_id: string; payload: Record }) => { + // Clone like the wire would: the live bus serializes payloads, so + // later caller-side mutation must not rewrite recorded calls. + const payload = structuredClone(req.payload); + calls.push({ function_id: req.function_id, payload }); + const { scope, key, value } = payload as { scope?: string; key?: string; value?: unknown }; + if (req.function_id === 'state::set') { + state.set(`${scope}/${key}`, value); + return null; + } + if (req.function_id === 'state::get') return state.get(`${scope}/${key}`) ?? null; + if (req.function_id === 'state::list') { + return [...state.entries()].filter(([k]) => k.startsWith(`${scope}/`)).map(([, v]) => v); + } + return null; + }, + registerFunction: vi.fn((fnId: string, handler: (payload: unknown) => Promise) => { + registered.set(fnId, handler); + }), + } as unknown as ISdk; + + const streamFrames = (stream: string) => + calls + .filter( + (c) => + c.function_id === 'stream::set' && + (c.payload as { stream_name?: string }).stream_name === stream, + ) + .map((c) => c.payload); + + return { iii, calls, state, streamFrames, registered }; +} diff --git a/claude-code/tests/_helpers/fake-query.ts b/claude-code/tests/_helpers/fake-query.ts new file mode 100644 index 000000000..c2c195241 --- /dev/null +++ b/claude-code/tests/_helpers/fake-query.ts @@ -0,0 +1,57 @@ +/** Scripted Agent SDK `query()` replacement: yields a fixed message list and + * records the prompt/options it was called with plus interrupt() calls. */ + +export type QueryCapture = { + prompt?: string; + options?: Record; + interrupted: boolean; +}; + +export function scriptedQuery(messages: Array>, capture: QueryCapture) { + return (args: { prompt: string; options: Record }) => { + capture.prompt = args.prompt; + capture.options = args.options; + return { + async *[Symbol.asyncIterator]() { + yield* messages; + }, + interrupt: async () => { + capture.interrupted = true; + }, + }; + }; +} + +export const initMsg = { type: 'system', subtype: 'init', session_id: 'cs-1' }; + +export const assistantMsg = { + type: 'assistant', + message: { + model: 'claude-opus-4-8', + usage: { input_tokens: 5, output_tokens: 2 }, + content: [ + { type: 'text', text: 'running ls' }, + { type: 'tool_use', id: 'toolu_1', name: 'Bash', input: { command: 'ls' } }, + ], + }, +}; + +export const toolResultMsg = { + type: 'user', + message: { + content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'files', is_error: false }], + }, +}; + +export const resultMsg = { + type: 'result', + subtype: 'success', + session_id: 'cs-1', + num_turns: 2, + total_cost_usd: 0.01, + usage: { input_tokens: 5, output_tokens: 2 }, + result: 'done', + is_error: false, +}; + +export const fullTurn = [initMsg, assistantMsg, toolResultMsg, resultMsg]; diff --git a/claude-code/tests/events.test.ts b/claude-code/tests/events.test.ts new file mode 100644 index 000000000..e1286ce24 --- /dev/null +++ b/claude-code/tests/events.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { ISdk } from 'iii-sdk'; +import { makeEmitter } from '../src/events.js'; +import { fakeIii } from './_helpers/fake-iii.js'; + +describe('makeEmitter', () => { + it('writes stream::set frames with the stream name, group, and event', async () => { + const fake = fakeIii(); + const emit = makeEmitter(fake.iii, 'agent::events'); + await emit('s1', { type: 'agent_end', messages: [] }); + const frames = fake.streamFrames('agent::events'); + expect(frames).toHaveLength(1); + expect(frames[0]).toMatchObject({ + stream_name: 'agent::events', + group_id: 's1', + data: { type: 'agent_end', messages: [] }, + }); + }); + + it('assigns unique, monotonically increasing item_ids per session', async () => { + const fake = fakeIii(); + const emit = makeEmitter(fake.iii, 'agent::events'); + await emit('s-seq', { a: 1 }); + await emit('s-seq', { a: 2 }); + await emit('other', { a: 3 }); + const ids = fake.streamFrames('agent::events').map((f) => String(f.item_id)); + expect(new Set(ids).size).toBe(3); + const [first, second] = ids; + expect(first < second).toBe(true); + expect(first.startsWith('s-seq-')).toBe(true); + }); + + it('swallows stream::set failures instead of failing the turn', async () => { + const iii = { + trigger: vi.fn(async () => { + throw new Error('stream adapter missing'); + }), + } as unknown as ISdk; + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const emit = makeEmitter(iii, 'agent::events'); + await expect(emit('s1', { type: 'turn_end' })).resolves.toBeUndefined(); + expect(warn).toHaveBeenCalled(); + warn.mockRestore(); + }); +}); diff --git a/claude-code/tests/register.test.ts b/claude-code/tests/register.test.ts new file mode 100644 index 000000000..0054b0b51 --- /dev/null +++ b/claude-code/tests/register.test.ts @@ -0,0 +1,154 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@anthropic-ai/claude-agent-sdk', () => ({ query: vi.fn() })); + +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { loadConfig } from '../src/config.js'; +import { makeEmitter } from '../src/events.js'; +import { register } from '../src/run.js'; +import { fakeIii, type FakeIii } from './_helpers/fake-iii.js'; +import { fullTurn, scriptedQuery, type QueryCapture } from './_helpers/fake-query.js'; + +const queryMock = vi.mocked(query); + +async function registeredWorker(): Promise { + const fake = fakeIii(); + const cfg = await loadConfig('/nonexistent/config.yaml'); + const emit = makeEmitter(fake.iii, cfg.events_stream); + const emitRaw = makeEmitter(fake.iii, cfg.raw_events_stream); + register(fake.iii, cfg, emit, emitRaw); + return fake; +} + +beforeEach(() => { + queryMock.mockReset(); +}); + +describe('register', () => { + it('registers the full claude::* surface plus the shared entrypoint', async () => { + const fake = await registeredWorker(); + expect([...fake.registered.keys()].sort()).toEqual([ + 'claude::run', + 'claude::sessions::list', + 'claude::start', + 'claude::status', + 'claude::stop', + 'run::start_and_wait', + ]); + }); + + it('claude::run parses at the unknown boundary and runs a turn', async () => { + const fake = await registeredWorker(); + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + const res = (await fake.registered.get('claude::run')?.({ + prompt: 'hi', + session_id: 's1', + })) as Record; + expect(res.result).toBe('done'); + expect(capture.prompt).toBe('hi'); + }); + + it('claude::run rejects invalid payloads', async () => { + const fake = await registeredWorker(); + await expect( + fake.registered.get('claude::run')?.({ prompt: 'x', permission_mode: 'yolo' }), + ).rejects.toThrow(); + await expect(fake.registered.get('claude::run')?.({ max_turns: -1 })).rejects.toThrow(); + }); + + it('run::start_and_wait shares the claude::run handler behaviour', async () => { + const fake = await registeredWorker(); + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + const res = (await fake.registered.get('run::start_and_wait')?.({ + session_id: 's1', + messages: [{ role: 'user', content: [{ type: 'text', text: 'go' }] }], + })) as Record; + expect(res.result).toBe('done'); + expect(capture.prompt).toBe('go'); + }); + + it('claude::start returns immediately and the turn lands in the background', async () => { + const fake = await registeredWorker(); + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + const res = (await fake.registered.get('claude::start')?.({ prompt: 'bg' })) as Record< + string, + unknown + >; + expect(res.started).toBe(true); + expect(typeof res.session_id).toBe('string'); + await vi.waitFor(() => { + const record = fake.state.get(`claude_sessions/${res.session_id}`) as + | { status: string } + | undefined; + expect(record?.status).toBe('done'); + }); + }); + + it('claude::stop without a live run reports stopped: false', async () => { + const fake = await registeredWorker(); + const res = (await fake.registered.get('claude::stop')?.({ session_id: 'ghost' })) as Record< + string, + unknown + >; + expect(res).toMatchObject({ session_id: 'ghost', stopped: false }); + }); + + it('claude::stop interrupts a live run', async () => { + const fake = await registeredWorker(); + const capture: QueryCapture = { interrupted: false }; + let release: (() => void) | undefined; + const gate = new Promise((r) => { + release = r; + }); + queryMock.mockImplementation(((args: { options: Record }) => { + capture.options = args.options; + return { + async *[Symbol.asyncIterator]() { + yield { type: 'system', subtype: 'init', session_id: 'cs-1' }; + await gate; + }, + interrupt: async () => { + capture.interrupted = true; + release?.(); + }, + }; + }) as never); + + const startRes = (await fake.registered.get('claude::start')?.({ + prompt: 'long', + session_id: 'live-1', + })) as Record; + await vi.waitFor(() => { + expect(fake.state.has('claude_sessions/live-1')).toBe(true); + }); + const stopRes = (await fake.registered.get('claude::stop')?.({ + session_id: String(startRes.session_id), + })) as Record; + expect(stopRes.stopped).toBe(true); + expect(capture.interrupted).toBe(true); + }); + + it('claude::status reflects the stored record and live flag', async () => { + const fake = await registeredWorker(); + const res = (await fake.registered.get('claude::status')?.({ session_id: 'none' })) as Record< + string, + unknown + >; + expect(res).toMatchObject({ session_id: 'none', live: false, record: null }); + }); + + it('claude::sessions::list returns every stored record', async () => { + const fake = await registeredWorker(); + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + await fake.registered.get('claude::run')?.({ prompt: 'a', session_id: 's1' }); + await fake.registered.get('claude::run')?.({ prompt: 'b', session_id: 's2' }); + const res = (await fake.registered.get('claude::sessions::list')?.({})) as { + sessions: Array<{ session_id: string }>; + }; + expect(res.sessions.map((s) => s.session_id).sort()).toEqual(['s1', 's2']); + }); +}); diff --git a/claude-code/tests/run.test.ts b/claude-code/tests/run.test.ts new file mode 100644 index 000000000..2e5bd0c36 --- /dev/null +++ b/claude-code/tests/run.test.ts @@ -0,0 +1,272 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@anthropic-ai/claude-agent-sdk', () => ({ query: vi.fn() })); + +import { query } from '@anthropic-ai/claude-agent-sdk'; +import { loadConfig, type Config } from '../src/config.js'; +import { makeEmitter } from '../src/events.js'; +import { executeRun, RunPayloadSchema } from '../src/run.js'; +import { fakeIii } from './_helpers/fake-iii.js'; +import { fullTurn, scriptedQuery, type QueryCapture } from './_helpers/fake-query.js'; + +const queryMock = vi.mocked(query); + +async function baseConfig(): Promise { + return loadConfig('/nonexistent/config.yaml'); +} + +async function runTurn( + payload: Record, + messages: Array> = fullTurn, + cfgOverrides: Partial = {}, +) { + const fake = fakeIii(); + const cfg = { ...(await baseConfig()), ...cfgOverrides }; + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(messages, capture) as never); + const emit = makeEmitter(fake.iii, cfg.events_stream); + const emitRaw = makeEmitter(fake.iii, cfg.raw_events_stream); + const result = await executeRun(fake.iii, cfg, emit, emitRaw, RunPayloadSchema.parse(payload)); + return { fake, capture, result }; +} + +beforeEach(() => { + queryMock.mockReset(); +}); + +describe('executeRun', () => { + it('returns the result with mapped usage and cost', async () => { + const { result } = await runTurn({ prompt: 'do it', session_id: 's1' }); + expect(result).toMatchObject({ + session_id: 's1', + claude_session_id: 'cs-1', + result: 'done', + stop_reason: 'end', + is_error: false, + num_turns: 2, + total_cost_usd: 0.01, + usage: { input_tokens: 5, output_tokens: 2 }, + }); + }); + + it('persists the session record working then done with the Claude session id', async () => { + const { fake } = await runTurn({ prompt: 'x', session_id: 's1' }); + const sets = fake.calls.filter( + (c) => + c.function_id === 'state::set' && + (c.payload as { scope?: string }).scope === 'claude_sessions', + ); + const statuses = sets.map((c) => (c.payload.value as { status: string }).status); + expect(statuses[0]).toBe('working'); + expect(statuses[statuses.length - 1]).toBe('done'); + const final = sets[sets.length - 1].payload.value as Record; + expect(final.claude_session_id).toBe('cs-1'); + expect(final.total_cost_usd).toBe(0.01); + }); + + it('mirrors every SDK message verbatim onto the raw stream', async () => { + const { fake } = await runTurn({ prompt: 'x', session_id: 's1' }); + const raw = fake.streamFrames('claude::events').map((f) => f.data); + expect(raw).toEqual(fullTurn); + const groupIds = fake.streamFrames('claude::events').map((f) => f.group_id); + expect(new Set(groupIds)).toEqual(new Set(['s1'])); + }); + + it('emits the translated AgentEvent sequence on agent::events', async () => { + const { fake } = await runTurn({ prompt: 'x', session_id: 's1' }); + const types = fake.streamFrames('agent::events').map((f) => (f.data as { type: string }).type); + expect(types).toEqual([ + 'message_complete', + 'function_execution_start', + 'function_execution_end', + 'turn_end', + 'agent_end', + ]); + const [start, end] = fake + .streamFrames('agent::events') + .map((f) => f.data as Record) + .filter((d) => String(d.type).startsWith('function_execution')); + expect(start).toMatchObject({ + function_call_id: 'toolu_1', + function_id: 'claude-code::Bash', + args: { command: 'ls' }, + }); + expect(end).toMatchObject({ function_call_id: 'toolu_1', is_error: false }); + }); + + it('passes worker defaults and named fields to query options', async () => { + const { capture } = await runTurn({ + prompt: 'x', + session_id: 's1', + cwd: '/repo', + model: 'claude-sonnet-4-6', + permission_mode: 'plan', + max_turns: 7, + }); + expect(capture.prompt).toBe('x'); + expect(capture.options).toMatchObject({ + cwd: '/repo', + model: 'claude-sonnet-4-6', + permissionMode: 'plan', + maxTurns: 7, + settingSources: [], + }); + }); + + it('forwards raw SDK options verbatim and lets them win over derived fields', async () => { + const { capture } = await runTurn({ + prompt: 'x', + session_id: 's1', + max_turns: 7, + options: { forkSession: true, includePartialMessages: true, maxTurns: 3 }, + }); + expect(capture.options).toMatchObject({ + forkSession: true, + includePartialMessages: true, + maxTurns: 3, + }); + }); + + it('resumes the prior Claude session for a known session_id', async () => { + const fake = fakeIii(); + fake.state.set('claude_sessions/s1', { + session_id: 's1', + claude_session_id: 'cs-prior', + cwd: '/repo', + model: '', + status: 'done', + turns: 1, + total_cost_usd: 0.01, + usage: null, + updated_at_ms: 1, + }); + const cfg = await baseConfig(); + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + const emit = makeEmitter(fake.iii, cfg.events_stream); + const result = await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ prompt: 'again', session_id: 's1' }), + ); + expect(capture.options?.resume).toBe('cs-prior'); + expect(result.num_turns).toBe(3); + }); + + it('extracts the prompt from the last user message of a messages payload', async () => { + const { capture } = await runTurn({ + session_id: 's1', + messages: [ + { role: 'user', content: [{ type: 'text', text: 'first' }] }, + { role: 'assistant', content: [{ type: 'text', text: 'reply' }] }, + { role: 'user', content: [{ type: 'text', text: 'second' }] }, + ], + }); + expect(capture.prompt).toBe('second'); + }); + + it('marks the record error and still closes the turn when the SDK throws', async () => { + const fake = fakeIii(); + const cfg = await baseConfig(); + queryMock.mockImplementation((() => { + return { + async *[Symbol.asyncIterator]() { + yield { type: 'system', subtype: 'init', session_id: 'cs-1' }; + throw new Error('spawn failed'); + }, + interrupt: async () => {}, + }; + }) as never); + const emit = makeEmitter(fake.iii, cfg.events_stream); + const result = await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ prompt: 'x', session_id: 's1' }), + ); + expect(result.is_error).toBe(true); + expect(result.stop_reason).toBe('error'); + expect(String(result.result)).toContain('spawn failed'); + const record = fake.state.get('claude_sessions/s1') as { status: string }; + expect(record.status).toBe('error'); + const types = fake.streamFrames('agent::events').map((f) => (f.data as { type: string }).type); + expect(types).toContain('turn_end'); + expect(types).toContain('agent_end'); + }); + + it('reports non-success result subtypes as the stop reason', async () => { + const turn = [ + { type: 'system', subtype: 'init', session_id: 'cs-1' }, + { ...fullTurn[3], subtype: 'error_max_turns', is_error: true }, + ]; + const { result } = await runTurn({ prompt: 'x', session_id: 's1' }, turn); + expect(result.stop_reason).toBe('error_max_turns'); + expect(result.is_error).toBe(true); + }); + + it('omits the resume option for a fresh session', async () => { + const { capture } = await runTurn({ prompt: 'x', session_id: 'fresh' }); + expect(capture.options).not.toHaveProperty('resume'); + }); +}); + +describe('approval gate', () => { + async function captureCanUseTool(policyReply: () => Promise) { + const fake = fakeIii(); + const cfg = { ...(await baseConfig()), approval_gate: true }; + const originalTrigger = fake.iii.trigger.bind(fake.iii); + (fake.iii as { trigger: unknown }).trigger = async (req: { + function_id: string; + payload: Record; + }) => { + if (req.function_id === 'policy::check_permissions') return policyReply(); + return originalTrigger(req as never); + }; + const capture: QueryCapture = { interrupted: false }; + queryMock.mockImplementation(scriptedQuery(fullTurn, capture) as never); + const emit = makeEmitter(fake.iii, cfg.events_stream); + await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ prompt: 'x', session_id: 's1' }), + ); + return capture.options?.canUseTool as ( + tool: string, + input: Record, + ) => Promise<{ behavior: string; message?: string }>; + } + + it('allows when the policy says allow', async () => { + const canUseTool = await captureCanUseTool(async () => ({ decision: 'allow' })); + expect(canUseTool).toBeTypeOf('function'); + await expect(canUseTool('Bash', { command: 'ls' })).resolves.toMatchObject({ + behavior: 'allow', + }); + }); + + it('denies when the policy says deny', async () => { + const canUseTool = await captureCanUseTool(async () => ({ decision: 'deny' })); + await expect(canUseTool('Bash', { command: 'rm' })).resolves.toMatchObject({ + behavior: 'deny', + }); + }); + + it('fails closed when the policy is unreachable', async () => { + const canUseTool = await captureCanUseTool(async () => { + throw new Error('gate down'); + }); + const verdict = await canUseTool('Bash', { command: 'ls' }); + expect(verdict.behavior).toBe('deny'); + expect(verdict.message).toContain('fail-closed'); + }); + + it('does not install canUseTool when the gate is off', async () => { + const { capture } = await runTurn({ prompt: 'x', session_id: 's1' }); + expect(capture.options).not.toHaveProperty('canUseTool'); + }); +}); diff --git a/claude-code/tests/state.test.ts b/claude-code/tests/state.test.ts new file mode 100644 index 000000000..0b9567185 --- /dev/null +++ b/claude-code/tests/state.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; +import type { ISdk } from 'iii-sdk'; +import { listSessions, loadSession, saveSession } from '../src/state.js'; +import type { SessionRecord } from '../src/types.js'; +import { fakeIii } from './_helpers/fake-iii.js'; + +function record(session_id: string): SessionRecord { + return { + session_id, + claude_session_id: `cs-${session_id}`, + cwd: '/repo', + model: '', + status: 'done', + turns: 1, + total_cost_usd: 0.01, + usage: null, + updated_at_ms: 1, + }; +} + +describe('session state', () => { + it('round-trips a record through scope claude_sessions', async () => { + const fake = fakeIii(); + await saveSession(fake.iii, record('s1')); + const set = fake.calls.find((c) => c.function_id === 'state::set'); + expect(set?.payload).toMatchObject({ scope: 'claude_sessions', key: 's1' }); + await expect(loadSession(fake.iii, 's1')).resolves.toMatchObject({ + session_id: 's1', + claude_session_id: 'cs-s1', + }); + }); + + it('returns null for unknown sessions', async () => { + const fake = fakeIii(); + await expect(loadSession(fake.iii, 'missing')).resolves.toBeNull(); + }); + + it('handles state::get returning the value directly, without an envelope', async () => { + // The engine returns the stored value itself, not {value}; a worker + // built against the envelope shape silently loses resume (caught live). + const iii = { + trigger: async (req: { function_id: string }) => + req.function_id === 'state::get' ? record('s1') : null, + } as unknown as ISdk; + await expect(loadSession(iii, 's1')).resolves.toMatchObject({ session_id: 's1' }); + }); + + it('lists every record and tolerates a non-array reply', async () => { + const fake = fakeIii(); + await saveSession(fake.iii, record('s1')); + await saveSession(fake.iii, record('s2')); + const sessions = await listSessions(fake.iii); + expect(sessions.map((s) => s.session_id).sort()).toEqual(['s1', 's2']); + + const iii = { trigger: async () => null } as unknown as ISdk; + await expect(listSessions(iii)).resolves.toEqual([]); + }); +}); From ad78bbd0e1ce152c7ef4dc70ba735e43c9112461 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 13:02:12 +0100 Subject: [PATCH 07/23] docs(claude-code): iii trigger CLI examples and the two-session-id distinction --- claude-code/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/claude-code/README.md b/claude-code/README.md index 779d7773f..fe3bcb8f4 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -47,8 +47,29 @@ const res = await iii.trigger({ // { session_id, claude_session_id, result, stop_reason, usage, total_cost_usd } ``` +Or straight from the terminal with the `iii trigger` CLI: + +```bash +# one full turn (raise the timeout; the default 30s is too short for agent turns) +iii trigger claude::run --timeout-ms 600000 \ + --json '{"prompt":"add a /health endpoint and run the tests","cwd":"/path/to/repo"}' + +# quick reads use key=value syntax +iii trigger claude::sessions::list +iii trigger claude::status session_id= + +# background turn + control +iii trigger claude::start --json '{"prompt":"...","cwd":"/path/to/repo"}' +iii trigger claude::stop session_id= + +# ask the running engine for a function's description +iii trigger claude::run --help +``` + Call `claude::run` again with the returned `session_id` to continue the same conversation: the worker maps iii session ids to Claude Code session ids in engine state and resumes automatically. +Two ids come back from every run. `session_id` is the iii session id: the key for `claude::status`, `claude::stop`, resume, and the stream group. `claude_session_id` is Claude Code's internal session id (what the worker passes to the CLI's resume under the hood) — returned for reference, not a lookup key. + Long turns: use `claude::start` to return immediately, then watch `agent::events` (group_id = your session_id) for `message_complete`, `function_execution_start/end`, and `turn_end` frames. `claude::stop` interrupts a live run, `claude::status` reads a point-in-time view, `claude::sessions::list` enumerates past sessions. ## Functions From 722319e7b880cc627f4f97b07b8ed6e5a779ef84 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Thu, 11 Jun 2026 13:03:37 +0100 Subject: [PATCH 08/23] docs(claude-code): CLI screenshots for run, status, and help output --- claude-code/README.md | 8 ++++++++ claude-code/assets/cli-help.png | Bin 0 -> 189162 bytes claude-code/assets/cli-run.png | Bin 0 -> 133235 bytes claude-code/assets/cli-status.png | Bin 0 -> 195531 bytes 4 files changed, 8 insertions(+) create mode 100644 claude-code/assets/cli-help.png create mode 100644 claude-code/assets/cli-run.png create mode 100644 claude-code/assets/cli-status.png diff --git a/claude-code/README.md b/claude-code/README.md index fe3bcb8f4..7018f71a4 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -66,6 +66,14 @@ iii trigger claude::stop session_id= iii trigger claude::run --help ``` +A turn from the CLI and the session record it leaves behind: + +![iii trigger claude::run returning the result with usage and cost](assets/cli-run.png) + +![iii trigger claude::status showing the stored session record](assets/cli-status.png) + +![iii trigger claude::run --help querying the engine for the function description](assets/cli-help.png) + Call `claude::run` again with the returned `session_id` to continue the same conversation: the worker maps iii session ids to Claude Code session ids in engine state and resumes automatically. Two ids come back from every run. `session_id` is the iii session id: the key for `claude::status`, `claude::stop`, resume, and the stream group. `claude_session_id` is Claude Code's internal session id (what the worker passes to the CLI's resume under the hood) — returned for reference, not a lookup key. diff --git a/claude-code/assets/cli-help.png b/claude-code/assets/cli-help.png new file mode 100644 index 0000000000000000000000000000000000000000..a8eaad36097e4ca354b3d0530cb818164b90ed27 GIT binary patch literal 189162 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