diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml index b8883e82d..5d83539a0 100644 --- a/.github/workflows/create-tag.yml +++ b/.github/workflows/create-tag.yml @@ -25,6 +25,7 @@ on: - image-resize - llm-router - mcp + - pi - provider-anthropic - provider-openai - session-manager diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0fb6f528..113808d22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,6 +19,7 @@ on: - 'image-resize/v*' - 'llm-router/v*' - 'mcp/v*' + - 'pi/v*' - 'provider-anthropic/v*' - 'provider-openai/v*' - 'session-manager/v*' diff --git a/README.md b/README.md index 296fa4327..af905ac4f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ npx skills add iii-hq/iii --all | [`harness`](harness/) | Node | TS port of the iii harness stack — bundles `harness` (provider registry + credentials/settings/permissions via the `configuration` worker), `turn-orchestrator`, `hook-fanout`, `models-catalog`, the `provider-*` workers, `llm-budget`, and `context-compaction` as one pnpm monorepo. Approval is delegated to the standalone `approval-gate` worker via the `pre_trigger` hook. Conversations persist in `session-manager`. See [`harness/README.md`](harness/README.md). | | [`codex`](codex/) | Rust | OpenAI Codex as an iii worker — `codex::*` spawn the codex CLI for headless turns, mirror raw thread events onto `codex::events`, and stream AgentEvent frames onto `agent::events`. | | [`claude-code`](claude-code/) | Node | Claude Code as an iii worker — `claude::*` runs headless Claude Code turns, mirrors raw messages onto `claude::events`, and streams AgentEvent frames onto `agent::events`. | +| [`pi`](pi/) | Node | Pi coding agent as an iii worker — `pi::*` run headless Pi turns, mirror raw events onto `pi::events`, and stream AgentEvent frames onto `agent::events`. | | [`session-manager`](session-manager/) | Rust | Durable, reactive, branching conversation store — fourteen `session::*` functions plus six trigger types; the transcript backend for `harness` and `console`. See [`session-manager/architecture/`](session-manager/architecture/). | | [`telegram-bot`](telegram-bot/) | Rust | Telegram webhook bridge to the harness stack — live message edits, inline approval keyboards, and configurable verbosity. | | [`context-manager`](context-manager/) | Rust | Model-ready context assembly — four `context::*` functions for token counting, function-result pruning, and history compaction over caller-supplied messages. Storage-agnostic; summarisation via `llm-router` when installed. | diff --git a/claude-code/src/iii-prompt.ts b/claude-code/src/iii-prompt.ts index 94d531c3a..088973b52 100644 --- a/claude-code/src/iii-prompt.ts +++ b/claude-code/src/iii-prompt.ts @@ -35,11 +35,17 @@ The live engine is the single source of truth. Ask it — never assume: - \`iii trigger --help\` — that function's description and request schema, served by the engine. THIS IS THE API REFERENCE for every call you make. Fetch it BEFORE the first call to any function; a one-line description from \`list\` is a hint, not the contract. -- \`iii trigger engine::workers::list\` — every connected worker; +- \`iii trigger engine::workers::list\` — every WS-connected (currently running) worker; \`iii trigger engine::workers::info name=\` — one worker's full surface. +- \`iii trigger worker::list\` — installed + running workers, including daemon-managed + builtins. \`engine::workers::list\` sees only WS-connected workers, so to confirm a worker is + RUNNING, merge the two by name. - \`iii trigger engine::triggers::list\` — every trigger TYPE; \`iii trigger engine::registered-triggers::list\` — every trigger INSTANCE already bound. +Trust a successful runtime call over an empty list: an empty \`*::list\` can mean lag, not +absence — never conclude a function or worker is missing from one empty read. + Need a backend capability? Check what is already registered FIRST — it is usually one call away. When nothing fits, search the public registry before building anything: \`iii trigger directory::registry::workers::list --json '{"search":""}'\` pages the diff --git a/codex/src/iii_prompt.rs b/codex/src/iii_prompt.rs index 28d535c75..24d7f4d4d 100644 --- a/codex/src/iii_prompt.rs +++ b/codex/src/iii_prompt.rs @@ -32,11 +32,17 @@ The live engine is the single source of truth. Ask it — never assume: - `iii trigger --help` — that function's description and request schema, served by the engine. THIS IS THE API REFERENCE for every call you make. Fetch it BEFORE the first call to any function; a one-line description from `list` is a hint, not the contract. -- `iii trigger engine::workers::list` — every connected worker; +- `iii trigger engine::workers::list` — every WS-connected (currently running) worker; `iii trigger engine::workers::info name=` — one worker's full surface. +- `iii trigger worker::list` — installed + running workers, including daemon-managed + builtins. `engine::workers::list` sees only WS-connected workers, so to confirm a worker is + RUNNING, merge the two by name. - `iii trigger engine::triggers::list` — every trigger TYPE; `iii trigger engine::registered-triggers::list` — every trigger INSTANCE already bound. +Trust a successful runtime call over an empty list: an empty `*::list` can mean lag, not +absence — never conclude a function or worker is missing from one empty read. + Need a backend capability? Check what is already registered FIRST — it is usually one call away. When nothing fits, search the public registry before building anything: `iii trigger directory::registry::workers::list --json '{"search":""}'` pages the diff --git a/pi/.gitignore b/pi/.gitignore new file mode 100644 index 000000000..43370fa91 --- /dev/null +++ b/pi/.gitignore @@ -0,0 +1 @@ +*.tsbuildinfo diff --git a/pi/README.md b/pi/README.md new file mode 100644 index 000000000..ecdf6cc18 --- /dev/null +++ b/pi/README.md @@ -0,0 +1,183 @@ +# pi + +Pi coding agent as an iii worker: the Pi API exposed as functions and streams on the iii bus, nothing else. The worker runs the same in-process agent loop Pi runs in the terminal, with the same tools (read, bash, edit, write) against any host directory. `pi::run` executes one headless turn and returns the result; the raw Pi events mirror verbatim onto the `pi::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Pi 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 Pi with no changes. + +## Install + +```bash +iii worker add pi +``` + +Pi runs the loop in-process (no CLI subprocess), so the worker environment needs model credentials — `ANTHROPIC_API_KEY` (or the provider key Pi is configured for) or an existing Pi login. + +## Skills + +Install the `pi` agent skill for Claude Code, Cursor, and 30+ other agents: + +```bash +npx skills add iii-hq/workers --skill pi +``` + +## Quickstart + +From zero to a Pi turn over the bus: + +```bash +curl -fsSL https://install.iii.dev/iii/main/install.sh | sh +iii worker add pi +iii # starts the engine + worker +``` + +Then talk to it like any other function: from the console chat, from `iii trigger pi::run`, or 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: 'pi::run', + payload: { + prompt: 'Add a /health endpoint to server.ts and run the tests', + cwd: '/path/to/repo', + }, + timeout_ms: 600_000, +}); +// { session_id, pi_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 pi::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 pi::sessions::list +iii trigger pi::status session_id= + +# background turn + control +iii trigger pi::start --json '{"prompt":"...","cwd":"/path/to/repo"}' +iii trigger pi::stop session_id= + +# ask the running engine for a function's description +iii trigger pi::run --help +``` + +A turn from the CLI returns the result with token usage and cost: + +![iii trigger pi::run returning pong with usage and cost](https://raw.githubusercontent.com/iii-hq/workers/main/pi/assets/cli-run.png) + +`iii trigger pi::run --help` prints the published request schema as a parameter table: + +![iii trigger pi::run --help printing the request schema as a parameter table](https://raw.githubusercontent.com/iii-hq/workers/main/pi/assets/cli-help.png) + +Call `pi::run` again with the returned `session_id` to continue the same conversation: the worker maps iii session ids to Pi session files in engine state and resumes automatically. + +![iii trigger pi::sessions::list showing the stored session records](https://raw.githubusercontent.com/iii-hq/workers/main/pi/assets/cli-sessions.png) + +Two ids come back from every run. `session_id` is the iii session id: the key for `pi::status`, `pi::stop`, `pi::steer`, resume, and the stream group. `pi_session_id` is Pi's internal session id — returned for reference, not a lookup key. + +Long turns: use `pi::start` to return immediately, then watch `agent::events` (group_id = your session_id) for `message_complete`, `function_execution_start/end`, and `turn_end` frames. `pi::stop` interrupts a live run, `pi::status` reads a point-in-time view, `pi::sessions::list` enumerates past sessions. + +## Functions + +| Function | Purpose | +| --- | --- | +| `pi::run` | Run one turn, wait, return the final result | +| `pi::start` | Fire-and-forget turn; progress arrives on `agent::events` | +| `pi::steer` | Inject a steering instruction into a live run | +| `pi::follow_up` | Queue a follow-up message for a live run | +| `pi::stop` | Interrupt a live run | +| `pi::status` | Session state, live flag, usage, cost | +| `pi::sessions::list` | All sessions this worker has run | +| `run::start_and_wait` | Alias for `pi::run` under the entrypoint the console and acp worker drive | + +`pi::run` accepts either a bare `prompt` string or a `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `thinking_level`, `tools`, and `iii_context` overrides. + +### Raw events + +Every event Pi emits (`agent_start/end`, `turn_start/end`, `message_start/update/end`, `tool_execution_start/update/end`, and the session events `queue_update`, `compaction_start/end`) is mirrored verbatim onto the `pi::events` stream, group_id = session_id. Consumers that want the exact Pi event format read `pi::events`; consumers that want harness-shaped frames read `agent::events`. Same turn, two views. + +## Steering a live run + +A turn started with `pi::start` keeps its session reachable while it streams. Two functions push instructions into it: + +```bash +# start a long run +iii trigger pi::start --json '{"prompt":"refactor the auth module","cwd":"/path/to/repo","session_id":"s1"}' + +# redirect it mid-flight — applied after the current tool calls finish +iii trigger pi::steer --json '{"session_id":"s1","prompt":"stop, keep the public API unchanged"}' + +# queue work for after it would otherwise stop +iii trigger pi::follow_up --json '{"session_id":"s1","prompt":"then add tests for the new paths"}' +``` + +`pi::steer` maps onto Pi's steering queue (interrupt-style), `pi::follow_up` onto its follow-up queue (wait-style). Both no-op with `{steered:false}` / `{queued:false}` when no run is live for the session. + +## The agent on the bus + +By default every turn's prompt carries the iii runtime context: the same engine-grounded rules as the harness identity prompts, retargeted to the `iii` CLI the agent reaches through its shell. The agent discovers capabilities from the live engine instead of memory — `iii trigger engine::functions::list` to find function ids, `iii trigger --help` as the contract before every first call, the registry flow (`directory::registry::workers::list/info`, `worker::add`) when nothing registered fits — plus the calling rules and error-handling discipline that go with them. Local file edits stay on Pi's native tools; backend actions go through registered functions. + +```bash +# the agent answers this by querying the live engine itself +iii trigger pi::run --timeout-ms 300000 \ + --json '{"prompt":"List every worker connected to this engine and what each one does.","cwd":"/tmp"}' +``` + +Pi answers by querying the live engine itself, grouping every connected worker by role: + +![iii trigger pi::run enumerating every worker connected to the engine](https://raw.githubusercontent.com/iii-hq/workers/main/pi/assets/cli-discovery.png) + +The context is prepended on a fresh session and skipped on resume (it is already in the conversation history). Turn it off entirely with `"iii_context": false` per call or globally in `config.yaml`. + +## Thinking and tools + +`thinking_level` maps straight onto Pi's reasoning levels, per turn: + +| Level | Behavior | +| --- | --- | +| `off` | No extended reasoning | +| `minimal` / `low` | Short reasoning budget | +| `medium` | The worker default | +| `high` / `xhigh` | Deep reasoning (xhigh on supported model families) | + +`tools` is an allowlist: leave it empty for Pi's defaults (`read`, `bash`, `edit`, `write`), or pass a subset to narrow what the turn can do — e.g. `{"tools":["read","bash"]}` for a read-and-run turn that cannot edit or write files. + +## Configuration + +```yaml +engine_url: ws://127.0.0.1:49134 + +defaults: + model: "" # empty = Pi settings default; else "provider/modelId" + thinking_level: medium # off | minimal | low | medium | high | xhigh + cwd: "" # default working directory for runs + tools: [] # empty = Pi defaults (read, bash, edit, write) + agent_dir: "" # Pi global config dir; empty = ~/.pi/agent + +events_stream: agent::events # translated AgentEvent frames +raw_events_stream: pi::events # verbatim Pi events +iii_context: true # prepend the iii runtime context on fresh sessions +``` + +`config.yaml` is the seed: on first boot the worker registers it with the built-in `configuration` worker as the initial value, then reads the live value back and hot-reloads on every `configuration:updated`. `engine_url` is excluded from the managed schema — it is bootstrap, so it stays on the local seed / `--url`. + +## Observability + +Every `pi::run` is an ordinary traced invocation on the engine: the trace carries the full input payload (prompt, cwd, caller worker id) and the output (result, stop reason, token usage, cost) as span events, with per-function p50/p95/p99 in the console's trace explorer — no extra instrumentation in the worker. + +## How it maps + +| Pi | iii | +| --- | --- | +| `AgentSession.prompt()` turn | `pi::run` invocation | +| every AgentSession event, verbatim | `pi::events` stream frame | +| assistant `message_end` | `message_complete` frame on `agent::events` | +| `tool_execution_start` / `tool_execution_end` | `function_execution_start` / `function_execution_end` frames | +| final result | `turn_end` + `agent_end` frames, function return value | +| `steer()` / `followUp()` | `pi::steer` / `pi::follow_up` | +| session resume | engine state scope `pi_sessions`, keyed by iii session_id | +| extra capability | another iii worker on the bus (`shell`, `database`, `storage`, ...) | diff --git a/pi/assets/cli-discovery.png b/pi/assets/cli-discovery.png new file mode 100644 index 000000000..e900af434 Binary files /dev/null and b/pi/assets/cli-discovery.png differ diff --git a/pi/assets/cli-help.png b/pi/assets/cli-help.png new file mode 100644 index 000000000..5e10b8db5 Binary files /dev/null and b/pi/assets/cli-help.png differ diff --git a/pi/assets/cli-run.png b/pi/assets/cli-run.png new file mode 100644 index 000000000..ae3954d7a Binary files /dev/null and b/pi/assets/cli-run.png differ diff --git a/pi/assets/cli-sessions.png b/pi/assets/cli-sessions.png new file mode 100644 index 000000000..7c79e9bf2 Binary files /dev/null and b/pi/assets/cli-sessions.png differ diff --git a/pi/biome.json b/pi/biome.json new file mode 100644 index 000000000..29673813a --- /dev/null +++ b/pi/biome.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.10/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/pi/config.yaml b/pi/config.yaml new file mode 100644 index 000000000..8ece34152 --- /dev/null +++ b/pi/config.yaml @@ -0,0 +1,23 @@ +engine_url: ws://127.0.0.1:49134 + +defaults: + model: "" # empty = Pi settings default; else "provider/modelId", e.g. "anthropic/claude-sonnet-4-20250514" + thinking_level: medium # off | minimal | low | medium | high | xhigh + cwd: "" # default working directory for runs + tools: [] # empty = Pi defaults (read, bash, edit, write); else an allowlist of tool names + agent_dir: "" # Pi global config dir; empty = ~/.pi/agent + +# Stream AgentEvent frames here, grouped by session_id. The console and +# acp worker both read this stream. +events_stream: agent::events + +# Raw Pi events (exact AgentSession event shapes: agent_start/end, turn_*, +# message_*, tool_execution_*) mirrored verbatim here, grouped by session_id. +raw_events_stream: pi::events + +# Append the iii runtime context to the system prompt: teaches the agent +# live discovery against the engine catalog through the iii CLI +# (engine::functions::list, `iii trigger --help`). Per-turn override +# via the iii_context payload field; a caller-supplied system_prompt +# always wins and gets nothing appended. +iii_context: true diff --git a/pi/iii-permissions.yaml b/pi/iii-permissions.yaml new file mode 100644 index 000000000..b0399071f --- /dev/null +++ b/pi/iii-permissions.yaml @@ -0,0 +1,16 @@ +# Agent permissions for the pi worker. +# Spec: docs/sops/new-worker.md § 7. First-match-wins. +# +# pi::run / pi::start (and the run::start_and_wait alias) spawn a full Pi +# coding agent with the host's filesystem and shell — an agent invoking those +# without human approval is a privilege escalation, so they are NOT +# allow-listed and stay at the needs_approval default. pi::steer / pi::follow_up +# inject instructions into a live run, so they stay gated too. Read-only +# introspection is safe to allow. +version: 1 + +rules: + # internal config-reload callback — bus-internal, never agent-callable + - '!pi::on-config-change' + - pi::status + - pi::sessions::list diff --git a/pi/iii.worker.yaml b/pi/iii.worker.yaml new file mode 100644 index 000000000..8dae5d31c --- /dev/null +++ b/pi/iii.worker.yaml @@ -0,0 +1,16 @@ +iii: v1 +name: pi +language: javascript +deploy: bundle +manifest: package.json +description: Pi coding agent as an iii worker — pi::* functions run headless Pi turns, mirror raw events onto pi::events, and stream AgentEvent frames onto agent::events. + +runtime: + kind: javascript + +scripts: + start: node ./index.mjs + +dependencies: + iii-state: "^0.17.0" + iii-stream: "^0.17.0" diff --git a/pi/package.json b/pi/package.json new file mode 100644 index 000000000..d1c046630 --- /dev/null +++ b/pi/package.json @@ -0,0 +1,44 @@ +{ + "name": "pi", + "version": "0.1.0", + "private": true, + "description": "Pi coding agent as an iii worker: headless turns over the iii bus, raw events on pi::events, AgentEvent frames on agent::events.", + "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": { + "pi": "./dist/index.js" + }, + "dependencies": { + "@earendil-works/pi-coding-agent": "^0.79.0", + "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" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild" + ] + } +} diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml new file mode 100644 index 000000000..394856bc3 --- /dev/null +++ b/pi/pnpm-lock.yaml @@ -0,0 +1,3211 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@earendil-works/pi-coding-agent': + specifier: ^0.79.0 + version: 0.79.10(ws@8.21.0)(zod@4.4.3) + iii-sdk: + specifier: ^0.19.2 + version: 0.19.4 + 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.20.0 + 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.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + +packages: + + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-bedrock-runtime@3.1048.0': + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.974.23': + resolution: {integrity: sha512-MiWR/uWjxjFXGzrE0Ghc5lWxUxzHsUWFhV+OX7M4cR9SrmrnZs6TXavnCWnzzdwJeFri34xQo81rvGNzK3c4BQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.49': + resolution: {integrity: sha512-liB3yQNHCM9k/gu/w36XHMKPluT7HTlnGUhRbBGSISDQkcr/Sy1zsZabiuvQj8WG5yW573u9RehrBvvnIQ9OEQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.51': + resolution: {integrity: sha512-XET0H2oofciJ5lMRWNIvRjAP7Q3wv2XT+JtJJEdhPWUMwe3TvQ9qcxonpu7vXmNngncvFpi4E2It+Tamas/naA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.56': + resolution: {integrity: sha512-IAmc61hbgQiHht9U3x0tnRwz0lzdwOwD/i9voRgdJrKamF+JtmrBOsW9GwB7mfFonNWOWL4qARWYrF8veEMe3w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.55': + resolution: {integrity: sha512-hBBkANo3cDn+h2qxxzER4a+J8JCO9o9Z/YYmU7iky6AcaarX5RRdRcHNC6SLdwY0vAXQygn6soUbDqPn3GghaA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.58': + resolution: {integrity: sha512-OyCLVmSI7pZO8hxwNVX6pXhTVlJqRBTp+ijdEfJSUj0RyjHnF602OfAarOzGq6wkGodeFkYBt8MmJ6A6ycRgWw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.49': + resolution: {integrity: sha512-C8h36lBuC/RnBSsjlO+dn6xZm3KbAl5vpJaVPAfQnMmz2/OISmKOc8XZcqMQgO2ADwBYNRMM6Kf3vz9G/TulMQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.55': + resolution: {integrity: sha512-1FkOz74Ea5QGS9jtIoXp55T/IkSS3spv+nLTT07fRY/+T5xmEOqaYBVIaEmX4zTNvbV6g2lrtlaVKWEoNyJt3w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.55': + resolution: {integrity: sha512-g2BoECD1q01kTPByi56+VLVvdWDzMkKIcr77qixpqH0okw2t0U5CoPv+6S8v/D1Y2Wa6QKKtn6XAtDzP+Kfpvg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/eventstream-handler-node@3.972.22': + resolution: {integrity: sha512-tqPJv0dz4+O0hWGm1a6YekcMZyPhDFs/zH73Von7icaVT5n0Jqvm86typ3jRrG+qoUdPhALOnboRLTmnWQTlYQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-eventstream@3.972.18': + resolution: {integrity: sha512-OHpk8YoZi3yexPq8aFt1vN1IxA2zLKvsIR5GpWYylX/ve6kQmY7wxHNSFy/D3t2apMZ16rs76Co4dJWcDyIk3A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-websocket@3.972.31': + resolution: {integrity: sha512-ps1rumU1LybSFHaW9dTDgkhCMJLVaedEY78kKSzUDDY+b9974/g6aiaYYA0U9WV0oL4CJCJrVWG+EZ/qr4or7g==} + engines: {node: '>= 14.0.0'} + + '@aws-sdk/nested-clients@3.997.23': + resolution: {integrity: sha512-gO93ZPsI2bxeFZD42f1/qjDw6FAZkNZcKRO94LIiT03fzOmcJ9e/tunxjVjA1Rl69ClmVJzz8H3G9CdKef10PA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.35': + resolution: {integrity: sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1048.0': + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1074.0': + resolution: {integrity: sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.13': + resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.8': + resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.31': + resolution: {integrity: sha512-SzE4Pgyl+hDF+BuyuzxUSpwnuUu9lJuO1YGgteG89/4Qv0+2IQiVQqdbPV32IozLvXWQChPQcdkk/sKvb1QHiQ==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@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] + + '@earendil-works/pi-agent-core@0.79.10': + resolution: {integrity: sha512-XKxgdjhcPuyjrthCOFSgfzT3xZ1uBrJ1IMVDxci1to6hIN6BIg9J5iY8q0pGXK1DLgATLP23da+1UyZLwA360Q==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-ai@0.79.10': + resolution: {integrity: sha512-9jR23tOl0BIUdQMn70Gr72xYBpM7Xgl9Lyv7gAnU1USfkNRuYG/f/edLl+n/Dp/RafDW3JI4DF7y/GhgkORuew==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-coding-agent@0.79.10': + resolution: {integrity: sha512-YxaRhmgyDTvLDdGVbe7YzTHV80oL5mX5odg6EhGHz3w5Wu1Ix8DCw7bhtiOBLGQNFRcknia0zPmVWIj30XP1EA==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-tui@0.79.10': + resolution: {integrity: sha512-FUVOjDn1DVwM1uHD5MNYboXQrXjIDbSt+BQ3py7nQWCY62tKfxgiM1OBMxTcwRWLfSdZHUPpV0hm1loIdUJnPw==} + engines: {node: '>=22.19.0'} + + '@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.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + 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.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + 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.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + 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.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + 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.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + 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.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + 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.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + 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.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + 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.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + 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.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + 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.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + 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.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + 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.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + 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.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + 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.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + 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.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + 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.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + 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.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + 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.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + 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.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + 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.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + 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.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + 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.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + 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.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + 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.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + 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.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + + '@iii-dev/observability@0.19.4': + resolution: {integrity: sha512-uEGMUGUhv7SNmcp6A/kZwHmV9eUEQcZl8gDkq5NIWcl9P6tL+uRWan1Rfx5yEBTsWo4CVecgd7Pe9pEhVHF+Mg==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@mariozechner/clipboard-darwin-arm64@0.3.9': + resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@mariozechner/clipboard-darwin-universal@0.3.9': + resolution: {integrity: sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==} + engines: {node: '>= 10'} + os: [darwin] + + '@mariozechner/clipboard-darwin-x64@0.3.9': + resolution: {integrity: sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + resolution: {integrity: sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + resolution: {integrity: sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + resolution: {integrity: sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + resolution: {integrity: sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + resolution: {integrity: sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + resolution: {integrity: sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + resolution: {integrity: sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@mariozechner/clipboard@0.3.9': + resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==} + engines: {node: '>= 10'} + + '@mistralai/mistralai@2.2.6': + resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@opentelemetry/api-logs@0.57.2': + resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@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/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.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@silvia-odwyer/photon-node@0.3.4': + resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} + + '@smithy/core@3.26.0': + resolution: {integrity: sha512-mLUktFAn+Pa2agl1J7VgtYNFWCX8/b4GMJSK1hCu4YCvtBfM6F8Os3EP4ry+DFFlXOf3wyvlgXhuUdFoy52D3g==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.4.2': + resolution: {integrity: sha512-18UMDMyrAbDcpmL1gLUA7ww0fRTcdCrSjSJOi2Sbld+tVjwD/pW+OAwjlScFLR7vvBnhZrIPQ7kVuTf1mnJLug==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.5.2': + resolution: {integrity: sha512-Ei/UK/QMhq0rKaMqGPlOAkE2yS9DZeYmZdk1RAKc3vp3zxgleZHZyBLlZv8yLsxljX4svCRuMTD6u3LLIcU4Bg==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.8.2': + resolution: {integrity: sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.5.2': + resolution: {integrity: sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.15.0': + resolution: {integrity: sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@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.20.0': + resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@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==} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + 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==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + 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'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + 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==} + + 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.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + 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==} + + gaxios@7.1.5: + resolution: {integrity: sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + google-auth-library@10.7.0: + resolution: {integrity: sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==} + engines: {node: '>=18'} + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + iii-sdk@0.19.4: + resolution: {integrity: sha512-fJK7rcCZNRFmJcJPuwhII3RIEZsS0F7xaj0Y8bJ+tL+XDAO/4I4D8jG2pYrXUgO+urUcWkQ98UkkDCMDhlHrpQ==} + + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} + engines: {node: '>= 20'} + hasBin: true + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + 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.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + openai@6.26.0: + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + + 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-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + 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'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + protobufjs@7.6.4: + resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==} + engines: {node: '>=12.0.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 + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + 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==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + 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==} + + 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'} + + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + + typebox@1.1.38: + resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} + + 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==} + + undici@8.5.0: + resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} + engines: {node: '>=22.19.0'} + + 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 + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + 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 + + 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/sdk@0.91.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.4.3 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.13 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.13 + '@aws-sdk/util-locate-window': 3.965.8 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.13 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.13 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-bedrock-runtime@3.1048.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.23 + '@aws-sdk/credential-provider-node': 3.972.58 + '@aws-sdk/eventstream-handler-node': 3.972.22 + '@aws-sdk/middleware-eventstream': 3.972.18 + '@aws-sdk/middleware-websocket': 3.972.31 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/fetch-http-handler': 5.5.2 + '@smithy/node-http-handler': 4.7.3 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/core@3.974.23': + dependencies: + '@aws-sdk/types': 3.973.13 + '@aws-sdk/xml-builder': 3.972.31 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.26.0 + '@smithy/signature-v4': 5.5.2 + '@smithy/types': 4.15.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.51': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/fetch-http-handler': 5.5.2 + '@smithy/node-http-handler': 4.8.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.56': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/credential-provider-env': 3.972.49 + '@aws-sdk/credential-provider-http': 3.972.51 + '@aws-sdk/credential-provider-login': 3.972.55 + '@aws-sdk/credential-provider-process': 3.972.49 + '@aws-sdk/credential-provider-sso': 3.972.55 + '@aws-sdk/credential-provider-web-identity': 3.972.55 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/credential-provider-imds': 4.4.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.55': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.58': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.49 + '@aws-sdk/credential-provider-http': 3.972.51 + '@aws-sdk/credential-provider-ini': 3.972.56 + '@aws-sdk/credential-provider-process': 3.972.49 + '@aws-sdk/credential-provider-sso': 3.972.55 + '@aws-sdk/credential-provider-web-identity': 3.972.55 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/credential-provider-imds': 4.4.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.55': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/token-providers': 3.1074.0 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.55': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/eventstream-handler-node@3.972.22': + dependencies: + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-eventstream@3.972.18': + dependencies: + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-websocket@3.972.31': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/fetch-http-handler': 5.5.2 + '@smithy/signature-v4': 5.5.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.23': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.23 + '@aws-sdk/signature-v4-multi-region': 3.996.35 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/fetch-http-handler': 5.5.2 + '@smithy/node-http-handler': 4.8.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.35': + dependencies: + '@aws-sdk/types': 3.973.13 + '@smithy/signature-v4': 5.5.2 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1048.0': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1074.0': + dependencies: + '@aws-sdk/core': 3.974.23 + '@aws-sdk/nested-clients': 3.997.23 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.973.13': + dependencies: + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.8': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.31': + dependencies: + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@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 + + '@earendil-works/pi-agent-core@0.79.10(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-ai': 0.79.10(ws@8.21.0)(zod@4.4.3) + ignore: 7.0.5 + typebox: 1.1.38 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.79.10(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0 + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + openai: 6.26.0(ws@8.21.0)(zod@4.4.3) + partial-json: 0.1.7 + typebox: 1.1.38 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-coding-agent@0.79.10(ws@8.21.0)(zod@4.4.3)': + dependencies: + '@earendil-works/pi-agent-core': 0.79.10(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-ai': 0.79.10(ws@8.21.0)(zod@4.4.3) + '@earendil-works/pi-tui': 0.79.10 + '@silvia-odwyer/photon-node': 0.3.4 + chalk: 5.6.2 + cross-spawn: 7.0.6 + diff: 8.0.4 + glob: 13.0.6 + highlight.js: 10.7.3 + hosted-git-info: 9.0.3 + ignore: 7.0.5 + jiti: 2.7.0 + minimatch: 10.2.5 + proper-lockfile: 4.1.2 + semver: 7.8.0 + typebox: 1.1.38 + undici: 8.5.0 + yaml: 2.9.0 + optionalDependencies: + '@mariozechner/clipboard': 0.3.9 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-tui@0.79.10': + dependencies: + get-east-asian-width: 1.6.0 + marked: 18.0.5 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@google/genai@1.52.0': + dependencies: + google-auth-library: 10.7.0 + p-retry: 4.6.2 + protobufjs: 7.6.4 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@iii-dev/observability@0.19.4': + 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': {} + + '@mariozechner/clipboard-darwin-arm64@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-universal@0.3.9': + optional: true + + '@mariozechner/clipboard-darwin-x64@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-arm64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-gnu@0.3.9': + optional: true + + '@mariozechner/clipboard-linux-x64-musl@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-arm64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard-win32-x64-msvc@0.3.9': + optional: true + + '@mariozechner/clipboard@0.3.9': + optionalDependencies: + '@mariozechner/clipboard-darwin-arm64': 0.3.9 + '@mariozechner/clipboard-darwin-universal': 0.3.9 + '@mariozechner/clipboard-darwin-x64': 0.3.9 + '@mariozechner/clipboard-linux-arm64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-arm64-musl': 0.3.9 + '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-gnu': 0.3.9 + '@mariozechner/clipboard-linux-x64-musl': 0.3.9 + '@mariozechner/clipboard-win32-arm64-msvc': 0.3.9 + '@mariozechner/clipboard-win32-x64-msvc': 0.3.9 + optional: true + + '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/semantic-conventions': 1.41.1 + ws: 8.21.0 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@opentelemetry/api-logs@0.57.2': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.0': {} + + '@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.5 + 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.4 + + '@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.5 + + '@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/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@silvia-odwyer/photon-node@0.3.4': {} + + '@smithy/core@3.26.0': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.4.2': + dependencies: + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.5.2': + dependencies: + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/node-http-handler@4.7.3': + dependencies: + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.8.2': + dependencies: + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.5.2': + dependencies: + '@smithy/core': 3.26.0 + '@smithy/types': 4.15.0 + tslib: 2.8.1 + + '@smithy/types@4.15.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.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.20.0': + dependencies: + undici-types: 6.21.0 + + '@types/retry@0.12.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.20.0)(jiti@2.7.0)(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.20.0)(jiti@2.7.0)(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 + + acorn-import-attributes@1.9.5(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + agent-base@7.1.4: {} + + assertion-error@2.0.1: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + bignumber.js@9.3.1: {} + + bowser@2.14.1: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + buffer-equal-constant-time@1.0.1: {} + + cac@6.7.14: {} + + 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 + + chalk@5.6.2: {} + + check-error@2.1.3: {} + + cjs-module-lexer@1.4.3: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + diff@8.0.4: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + es-errors@1.3.0: {} + + es-module-lexer@1.7.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.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + extend@3.0.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gaxios@7.1.5: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.5 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + get-east-asian-width@1.6.0: {} + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + google-auth-library@10.7.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.5 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + + graceful-fs@4.2.11: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + highlight.js@10.7.3: {} + + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.5.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ignore@7.0.5: {} + + iii-sdk@0.19.4: + dependencies: + '@iii-dev/observability': 0.19.4 + '@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.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + js-tokens@9.0.1: {} + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + long@5.3.2: {} + + loupe@3.2.1: {} + + lru-cache@11.5.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + marked@18.0.5: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minipass@7.1.3: {} + + module-details-from-path@1.0.4: {} + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + openai@6.26.0(ws@8.21.0)(zod@4.4.3): + optionalDependencies: + ws: 8.21.0 + zod: 4.4.3 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + partial-json@0.1.7: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + protobufjs@7.6.4: + 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/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 22.20.0 + long: 5.3.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 + + retry@0.12.0: {} + + retry@0.13.1: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + safe-buffer@5.2.1: {} + + semver@7.8.0: {} + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shimmer@1.2.1: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + source-map-js@1.2.1: {} + + stackback@0.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: {} + + ts-algebra@2.0.0: {} + + tslib@2.8.1: {} + + tsx@4.22.4: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + typebox@1.1.38: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici@8.5.0: {} + + vite-node@3.2.4(@types/node@22.20.0)(jiti@2.7.0)(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.20.0)(jiti@2.7.0)(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.20.0)(jiti@2.7.0)(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.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.0 + fsevents: 2.3.3 + jiti: 2.7.0 + tsx: 4.22.4 + yaml: 2.9.0 + + vitest@3.2.6(@types/node@22.20.0)(jiti@2.7.0)(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.20.0)(jiti@2.7.0)(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.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + web-streams-polyfill@3.3.3: {} + + 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 + + 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/pi/scripts/build-bundle.mjs b/pi/scripts/build-bundle.mjs new file mode 100644 index 000000000..879ba01af --- /dev/null +++ b/pi/scripts/build-bundle.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +/** + * Single-file ESM bundle for pi (`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. `@earendil-works/pi-coding-agent` runs the agent loop in-process, + * so there is no native CLI to resolve at runtime — unlike the + * subprocess-backed agent SDKs, the bundle is self-contained. + */ + +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, + minify: 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/pi/skills/SKILL.md b/pi/skills/SKILL.md new file mode 100644 index 000000000..cd0eca573 --- /dev/null +++ b/pi/skills/SKILL.md @@ -0,0 +1,74 @@ +--- +name: pi +description: >- + Run headless Pi coding-agent turns over the iii bus — file edits, shell, and + search against any host directory — with verbatim event streaming, session + resume, and live steering. +--- + +# pi + +The pi worker exposes the Pi coding-agent API as iii functions. One `pi::run` +call executes one headless Pi turn — the same in-process agent loop Pi runs in +the terminal, with the same tools (read, bash, edit, write) — in a chosen +working directory, and returns the final result, token usage, and cost. Every +event Pi emits mirrors untouched onto the `pi::events` stream; a translated +AgentEvent view lands on `agent::events`, which is what the iii console and the +acp worker render. + +Pi runs the loop in-process (no CLI subprocess), so it needs model credentials +in the worker environment (e.g. `ANTHROPIC_API_KEY`) or an existing Pi login. +When a turn needs a capability beyond Pi 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") in one + call, instead of orchestrating individual `coder::*` / `shell::*` calls + yourself: `pi::run` with `prompt` and `cwd`. +- Continue a conversation across calls: pass the same `session_id` again and + the worker resumes the underlying Pi session file with full context. +- Run long jobs without holding the call open: `pi::start` returns + `{session_id, started}` immediately; follow `pi::events` (group_id = + session_id) for raw progress or `agent::events` for the rendered view; + interrupt with `pi::stop`. +- Steer a run while it works: `pi::steer` injects an instruction applied after + the current tool calls finish; `pi::follow_up` queues a message processed + after the agent would otherwise stop. +- Act on the whole backend: turns carry the iii runtime context by default, so + the agent discovers and calls any registered function through the iii CLI + (engine::functions::list, `iii trigger --help`); disable per turn with + `iii_context: false`. +- Tune depth and scope per turn: `thinking_level` (off → xhigh) sets reasoning + effort, and `tools` narrows the active tool set to an allowlist. + +## Boundaries + +- Runs the Pi loop in-process — needs model credentials in the worker + environment; without them a turn fails at the first model request. +- Tool execution happens inside Pi's own tool set (`tools` allowlist), not the + engine's permission model; `pi::run` / `pi::start` are not agent-callable + without human approval (see iii-permissions.yaml). +- One turn per session at a time: check `pi::status` (`live: true`) before + sending another `pi::run` for the same `session_id`; parallel runs against + one session race on the underlying session file. +- `agent::events` carries whole-message frames (`message_complete`, + `function_execution_start/end`, `turn_end`, `agent_end`); the raw Pi event + shapes (including token deltas via `message_update`) live on `pi::events`. + +## Functions + +- `pi::run` — run one Pi turn and wait; accepts `prompt` (or a `messages` + array whose last user entry becomes the prompt), plus `model`, `cwd`, + `thinking_level`, `tools`, and `iii_context`; returns `{session_id, + pi_session_id, result, stop_reason, usage, total_cost_usd}`. +- `pi::start` — same payload, returns `{session_id, started}` immediately; + progress arrives on the streams. +- `pi::steer` — inject a steering instruction into a live run. +- `pi::follow_up` — queue a follow-up message for a live run. +- `pi::stop` — interrupt the live run for a session. +- `pi::status` — point-in-time session view: live flag, status, turns, usage, + cost. +- `pi::sessions::list` — every session this worker has run. +- `run::start_and_wait` — alias for `pi::run` under the entrypoint the console + and acp worker drive, so both run Pi with no changes. diff --git a/pi/src/config.ts b/pi/src/config.ts new file mode 100644 index 000000000..2139e934a --- /dev/null +++ b/pi/src/config.ts @@ -0,0 +1,54 @@ +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(''), + thinking_level: z + .enum(['off', 'minimal', 'low', 'medium', 'high', 'xhigh']) + .default('medium'), + cwd: z.string().default(''), + tools: z.array(z.string()).default([]), + agent_dir: z.string().default(''), + }) + .prefault({}), + events_stream: z.string().default('agent::events'), + raw_events_stream: z.string().default('pi::events'), + iii_context: z.boolean().default(true), +}); + +export type Config = z.infer; + +/** + * The slice managed by the `configuration` worker. `engine_url` is excluded — + * it is bootstrap (needed to reach the configuration worker), so it stays on + * the local seed / `--url` and never hot-reloads. + */ +export const RuntimeConfigSchema = ConfigSchema.omit({ engine_url: true }); +export type RuntimeConfig = z.infer; + +/** JSON Schema published to the configuration worker. */ +export function runtimeJsonSchema(): Record { + return z.toJSONSchema(RuntimeConfigSchema) as Record; +} + +/** The runtime slice of a full config, for use as `initial_value`. */ +export function toRuntime(cfg: Config): RuntimeConfig { + const { engine_url: _drop, ...runtime } = cfg; + return runtime; +} + +export async function loadConfig(path: string): Promise { + let raw: unknown = {}; + try { + raw = parse(await readFile(path, 'utf8')) ?? {}; + } catch (err) { + // a missing config file falls back to defaults; anything else + // (YAML parse error, permissions) must fail the worker fast + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err; + } + return ConfigSchema.parse(raw); +} diff --git a/pi/src/configuration.ts b/pi/src/configuration.ts new file mode 100644 index 000000000..d40496535 --- /dev/null +++ b/pi/src/configuration.ts @@ -0,0 +1,74 @@ +/** + * Integration with the built-in `configuration` worker. `config.yaml` is the + * seed installed as `initial_value` on first registration; the live value is + * authoritative thereafter and hot-reloads on `configuration:updated`. + * + * Stream names (`events_stream` / `raw_events_stream`) are read once at boot to + * build the emitters — a change to those needs a restart, like the path-jail + * workers refuse a topology change. Every other field hot-reloads. + */ + +import type { ISdk } from 'iii-sdk'; +import { + type Config, + type RuntimeConfig, + RuntimeConfigSchema, + runtimeJsonSchema, + toRuntime, +} from './config.js'; + +const CONFIG_ID = 'pi'; +const CONFIG_FN_ID = 'pi::on-config-change'; +const TIMEOUT_MS = 5_000; + +/** Live snapshot shared with the handlers; `current` is whole-replaced on reload. */ +export type ConfigHolder = { current: Config }; + +export async function registerPiConfig(iii: ISdk, seed: Config): Promise { + await iii.trigger({ + function_id: 'configuration::register', + payload: { + id: CONFIG_ID, + name: 'Pi', + description: + 'Pi coding agent worker: per-turn defaults (model, thinking level, working directory, tool allowlist, agent config dir), the agent::events / pi::events stream names, and whether to inject the iii runtime context.', + schema: runtimeJsonSchema(), + initial_value: toRuntime(seed), + }, + timeoutMs: TIMEOUT_MS, + }); +} + +/** Fetch the live runtime config; null when unset/unreachable. */ +export async function fetchRuntime(iii: ISdk): Promise { + try { + const res = await iii.trigger({ + function_id: 'configuration::get', + payload: { id: CONFIG_ID, raw: false }, + timeoutMs: TIMEOUT_MS, + }); + const value = res && typeof res === 'object' ? res.value : null; + if (value == null) return null; + return RuntimeConfigSchema.parse(value); + } catch (err) { + console.warn(`configuration::get failed for ${CONFIG_ID}: ${String(err)}`); + return null; + } +} + +/** + * Register the change handler + bind the `configuration` trigger. `onChange` is + * called once now (reconcile) and on every `configuration:updated`. + */ +export async function bindConfigTrigger(iii: ISdk, onChange: () => Promise): Promise { + await onChange(); + iii.registerFunction(CONFIG_FN_ID, async () => { + await onChange(); + return null; + }); + iii.registerTrigger({ + type: 'configuration', + function_id: CONFIG_FN_ID, + config: { configuration_id: CONFIG_ID, event_types: ['configuration:updated'] }, + }); +} diff --git a/pi/src/events.ts b/pi/src/events.ts new file mode 100644 index 000000000..19e41d882 --- /dev/null +++ b/pi/src/events.ts @@ -0,0 +1,28 @@ +/** + * 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'; +const PROCESS_EPOCH = randomUUID(); +const seqBySession = new Map(); + +export function makeEmitter(iii: ISdk, streamName: string) { + 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')}`; + 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/pi/src/iii-prompt.ts b/pi/src/iii-prompt.ts new file mode 100644 index 000000000..088973b52 --- /dev/null +++ b/pi/src/iii-prompt.ts @@ -0,0 +1,85 @@ +/** + * iii runtime context appended to the system prompt when `iii_context` is + * enabled. Carries the same engine-grounded rules as the harness identity + * prompts (harness/src/turn-orchestrator/prompt/*), retargeted from the + * `agent_trigger` tool to the `iii` CLI, which this agent reaches through + * its shell. The override rule matches the harness: a caller-supplied + * `system_prompt` wins and nothing is appended to it. + */ + +export const III_CONTEXT_PROMPT = `# iii runtime + +This machine runs an iii engine: a WebSocket-routed worker mesh whose single engine process +holds a live registry of every connected worker, every function those workers expose, and every +trigger bound to them. Every call routes worker -> engine -> worker, so the language, runtime, +and location of a worker are invisible to its callers. The function id is the ONLY contract +between two workers. + +You act on iii ONLY through the \`iii\` CLI on PATH, via your shell: + + iii trigger [key=value ...] [--json ''] [--timeout-ms ] + +Function ids are namespaced with \`::\` (e.g. \`engine::functions::list\`). Simple arguments go +as \`key=value\` pairs; structured payloads go as \`--json\` with a single-quoted JSON OBJECT. + +IMPORTANT: NEVER invent function ids or argument names from memory. Discover them from the live +engine and trust it over memory or this prompt. + +## Discovery + +The live engine is the single source of truth. Ask it — never assume: + +- \`iii trigger engine::functions::list --json '{"search":""}'\` — every function across + all workers; optional filters \`prefix\` / \`search\` / \`worker\`. Use it to FIND a function + id. +- \`iii trigger --help\` — that function's description and request schema, served by + the engine. THIS IS THE API REFERENCE for every call you make. Fetch it BEFORE the first call + to any function; a one-line description from \`list\` is a hint, not the contract. +- \`iii trigger engine::workers::list\` — every WS-connected (currently running) worker; + \`iii trigger engine::workers::info name=\` — one worker's full surface. +- \`iii trigger worker::list\` — installed + running workers, including daemon-managed + builtins. \`engine::workers::list\` sees only WS-connected workers, so to confirm a worker is + RUNNING, merge the two by name. +- \`iii trigger engine::triggers::list\` — every trigger TYPE; + \`iii trigger engine::registered-triggers::list\` — every trigger INSTANCE already bound. + +Trust a successful runtime call over an empty list: an empty \`*::list\` can mean lag, not +absence — never conclude a function or worker is missing from one empty read. + +Need a backend capability? Check what is already registered FIRST — it is usually one call +away. When nothing fits, search the public registry before building anything: +\`iii trigger directory::registry::workers::list --json '{"search":""}'\` pages the +published catalogue and \`iii trigger directory::registry::workers::info name=\` returns +one worker's full detail. Say what you are about to install and why, install with +\`iii trigger worker::add --json '{"source":{"kind":"registry","name":""}}'\`, then +confirm the new ids appear via \`engine::functions::list\` with that prefix and fetch each +contract with \`--help\` as usual. + +## Calling rules + +- \`--json\` takes a JSON OBJECT in single quotes: \`--json '{"path":"/tmp"}'\`. Never pass a + JSON-encoded string where the engine expects an object — workers reject it with + \`invalid_arguments\` / \`serialization error\`. +- Long-running functions need \`--timeout-ms\` well above the default 30000. +- Triggers are the engine's push channel: NEVER poll (a loop re-reading a queue, file, or + table) when a trigger type fits — bind a trigger instead. A trigger registration succeeds + even when its type's provider is absent or the config keys are wrong — the binding lands but + never fires — so copy config keys from \`engine::triggers::info\`, not from memory. + +## Error handling + +When a call errors, READ the error and CHANGE something before the next call. NEVER resend the +same function + payload unchanged. \`invalid_arguments\` / \`missing field\` means YOUR payload +is wrong: re-read the contract via \`--help\` and fix the object, keeping the same function. +\`function_not_found\` means the id is wrong: re-check via \`engine::functions::list\`. A +repeating timeout means the approach is wrong, not the arguments: simplify, split the work, or +report the blocker and stop. + +## Boundaries + +- Files in your working directory: use your native tools (read, edit, search). The bus is not + for local file edits. +- Backend actions beyond the working directory — email, databases, storage, queues, schedules, + other services — go through registered iii functions, never ad-hoc processes or foreign + patterns carried in from other ecosystems. If you reach for a tool that is not an iii + function for a backend action, stop and re-check the engine's surface first.`; diff --git a/pi/src/index.ts b/pi/src/index.ts new file mode 100644 index 000000000..dfce6d04a --- /dev/null +++ b/pi/src/index.ts @@ -0,0 +1,67 @@ +#!/usr/bin/env node +/** + * Worker bootstrap: connect, register the configuration schema, fetch the live + * config, register pi::* functions, wait for SIGINT/SIGTERM. Config is managed + * by the `configuration` worker (config.yaml is the seed); the live value + * hot-reloads. Mirrors the binary-worker lifecycle. + */ + +import { parseArgs } from 'node:util'; +import { registerWorker } from 'iii-sdk'; +import { type Config, loadConfig } from './config.js'; +import { + bindConfigTrigger, + type ConfigHolder, + fetchRuntime, + registerPiConfig, +} from './configuration.js'; +import { makeEmitter } from './events.js'; +import { register } from './run.js'; + +const { values } = parseArgs({ + options: { + config: { type: 'string', default: './config.yaml' }, + url: { type: 'string' }, + }, + strict: false, +}); + +const seed = await loadConfig(String(values.config)); +const url = values.url ? String(values.url) : seed.engine_url; + +const iii = registerWorker(url, { workerName: 'pi' }); + +// Best-effort: a configuration-worker hiccup at boot must not stop the worker +// from registering pi::*; it falls back to the seed via fetchRuntime. +try { + await registerPiConfig(iii, seed); +} catch (err) { + console.warn(`configuration::register failed; continuing with the seed: ${String(err)}`); +} + +// Live snapshot: start from the seed, then refresh from the configuration worker. +const holder: ConfigHolder = { current: seed }; +const refresh = async () => { + const runtime = (await fetchRuntime(iii)) ?? undefined; + const merged: Config = runtime ? { engine_url: seed.engine_url, ...runtime } : { ...seed }; + holder.current = merged; +}; + +await bindConfigTrigger(iii, refresh); + +// Emitters bind the boot stream names (a stream-name change needs a restart). +const emit = makeEmitter(iii, holder.current.events_stream); +const emitRaw = makeEmitter(iii, holder.current.raw_events_stream); +register(iii, () => holder.current, emit, emitRaw); + +console.log(`pi worker connected to ${url}`); + +const shutdown = async () => { + try { + await iii.shutdown?.(); + } finally { + process.exit(0); + } +}; +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/pi/src/map.ts b/pi/src/map.ts new file mode 100644 index 000000000..0728020a2 --- /dev/null +++ b/pi/src/map.ts @@ -0,0 +1,130 @@ +/** + * Map Pi AgentSession events to the AgentEvent wire subset. One Pi "turn" + * (pi::run call) becomes: + * + * message_end (assistant) -> message_complete + * tool_execution_start -> function_execution_start + * tool_execution_end -> function_execution_end + * turn_end -> turn_end + * agent_end -> agent_end + * + * Pi surfaces tool calls and results as dedicated tool_execution_* events with + * structured fields, so the assistant message only contributes text/thinking. + */ + +import type { + AgentMessage, + AssistantMessage, + ContentBlock, + FunctionResultMessage, + Usage, +} from './types.js'; + +type PiContentBlock = { + type?: string; + text?: string; + thinking?: string; + reasoning?: string; + content?: unknown; +}; + +type PiMessage = { role?: string; content?: unknown }; + +export type ToolCallIndex = Map; + +export function toolFunctionId(name: string): string { + return name.startsWith('mcp__') ? name.replace(/^mcp__/, '').replace(/__/g, '::') : `pi::${name}`; +} + +/** Extract the text/thinking blocks from a Pi assistant message. */ +export function mapMessageContent(message: unknown): ContentBlock[] { + const content = (message as PiMessage)?.content; + if (typeof content === 'string') return content ? [{ type: 'text', text: content }] : []; + if (!Array.isArray(content)) return []; + const out: ContentBlock[] = []; + for (const raw of content as PiContentBlock[]) { + if (typeof raw === 'string') { + if (raw) out.push({ type: 'text', text: raw }); + continue; + } + if (raw?.type === 'text' && raw.text) out.push({ type: 'text', text: raw.text }); + else if (raw?.type === 'thinking' || raw?.type === 'reasoning') { + const text = raw.thinking ?? raw.reasoning ?? ''; + if (text) out.push({ type: 'thinking', text }); + } + } + return out; +} + +/** Normalize a Pi tool result (string | block array | object) to wire content. */ +export function mapToolResultContent(raw: unknown): ContentBlock[] { + if (typeof raw === 'string') return [{ type: 'text', text: raw }]; + if (Array.isArray(raw)) { + return raw.map((b) => { + if (typeof b === 'object' && b !== null) { + const blk = b as PiContentBlock; + return { + type: 'text' as const, + text: blk.type === 'text' ? (blk.text ?? '') : JSON.stringify(blk), + }; + } + return { type: 'text' as const, text: JSON.stringify(b ?? null) }; + }); + } + 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: 'pi', + 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(), + }; +} + +/** Map Pi SessionStats tokens (input/output/cacheRead/cacheWrite) to wire Usage. */ +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 ?? u.input_tokens ?? 0, + output_tokens: u.output ?? u.output_tokens ?? 0, + cache_read_tokens: u.cacheRead ?? u.cache_read_tokens ?? 0, + cache_write_tokens: u.cacheWrite ?? u.cache_write_tokens ?? 0, + }; +} + +export function lastAssistant(messages: AgentMessage[]): AgentMessage { + if (messages.length === 0) return makeAssistantMessage([], '', null); + 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/pi/src/run.ts b/pi/src/run.ts new file mode 100644 index 000000000..d5c9c2085 --- /dev/null +++ b/pi/src/run.ts @@ -0,0 +1,430 @@ +/** + * pi::* function registrations. `pi::run` accepts either a bare `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 Pi unchanged. + * + * Pi runs the loop in-process and pushes events through `session.subscribe`. + * Because that listener is synchronous and stream emits are async, events are + * drained through a serial promise chain so frames land on the streams in + * arrival order. + */ + +import { randomUUID } from 'node:crypto'; +import type { AgentSession } from '@earendil-works/pi-coding-agent'; +import type { ISdk } from 'iii-sdk'; +import { z } from 'zod'; +import type { Config } from './config.js'; +import type { Emit } from './events.js'; +import { III_CONTEXT_PROMPT } from './iii-prompt.js'; +import { + lastAssistant, + makeAssistantMessage, + makeFunctionResult, + mapMessageContent, + mapToolResultContent, + mapUsage, + toolFunctionId, + type ToolCallIndex, +} from './map.js'; +import { buildSession } from './session.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() + .describe('iii session id; reuse to resume the same Pi conversation'), + prompt: z.string().optional().describe('The user prompt for this turn'), + messages: z + .array(MessageSchema) + .optional() + .describe( + 'Alternative to prompt: role/content messages; the last user entry becomes the prompt', + ), + model: z.string().optional().describe('Model as "provider/modelId"; empty = Pi settings default'), + cwd: z.string().optional().describe('Working directory the turn runs in'), + thinking_level: z + .enum(['off', 'minimal', 'low', 'medium', 'high', 'xhigh']) + .optional() + .describe('Reasoning depth for this turn'), + tools: z + .array(z.string()) + .optional() + .describe('Tool allowlist; empty = Pi defaults (read, bash, edit, write)'), + iii_context: z + .boolean() + .optional() + .describe('Prepend the iii runtime discovery context (engine catalog via the iii CLI)'), + timeout_ms: z + .number() + .int() + .positive() + .optional() + .describe('Reserved for callers; not forwarded'), +}); + +export type RunPayload = z.infer; +export { RunPayloadSchema }; + +const SessionIdSchema = z.object({ + session_id: z.string().describe('iii session id returned by pi::run / pi::start'), +}); + +const SteerPayloadSchema = z.object({ + session_id: z.string().describe('iii session id of a live run'), + prompt: z.string().describe('Instruction to inject into the live run'), +}); + +const RUN_REQUEST_FORMAT = z.toJSONSchema(RunPayloadSchema); +const SESSION_ID_FORMAT = z.toJSONSchema(SessionIdSchema); +const STEER_REQUEST_FORMAT = z.toJSONSchema(SteerPayloadSchema); + +type LiveRun = { session: AgentSession }; +const live = new Map(); + +/** Best-effort: flip a session record to `error` so a failed background run + * never leaves it stuck in `working`. Swallows its own failure. */ +async function markSessionError(iii: ISdk, session_id: string): Promise { + try { + const record = await loadSession(iii, session_id); + if (record && record.status === 'working') { + record.status = 'error'; + record.updated_at_ms = Date.now(); + await saveSession(iii, record); + } + } catch (err) { + console.error(`failed to mark session ${session_id} error: ${String(err)}`); + } +} + +export function extractPrompt(payload: RunPayload): string { + if (typeof payload.prompt === 'string') return payload.prompt; + const users = (payload.messages ?? []).filter((m) => m.role === 'user'); + const last = users[users.length - 1]; + if (!last) throw new Error('pi::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'); +} + +export async function executeRun( + iii: ISdk, + cfg: Config, + emit: Emit, + emitRaw: Emit, + payload: RunPayload, +): Promise> { + const session_id = payload.session_id ?? randomUUID(); + const prompt = extractPrompt(payload); + // One live run per session. Reserve the slot atomically: the has-check and + // the set happen with NO await between them, so two concurrent same-session + // calls cannot both pass (JS is single-threaded). Everything past here runs + // inside the try/finally so the slot is always released. + if (live.has(session_id)) { + return { session_id, busy: true, reason: 'a run is already active for this session' }; + } + const placeholder = { session: undefined as unknown as AgentSession }; + live.set(session_id, placeholder); + + try { + return await runReserved(iii, cfg, emit, emitRaw, payload, session_id, prompt, placeholder); + } finally { + if (live.get(session_id) === placeholder) live.delete(session_id); + } +} + +async function runReserved( + iii: ISdk, + cfg: Config, + emit: Emit, + emitRaw: Emit, + payload: RunPayload, + session_id: string, + prompt: string, + slot: LiveRun, +): Promise> { + const prior = await loadSession(iii, session_id); + const d = cfg.defaults; + const model = payload.model ?? d.model; + const cwd = payload.cwd ?? prior?.cwd ?? d.cwd; + + const record: SessionRecord = prior ?? { + session_id, + pi_session_id: null, + session_file: null, + cwd, + model, + status: 'working', + turns: 0, + total_cost_usd: 0, + usage: null, + updated_at_ms: Date.now(), + }; + if (payload.cwd) record.cwd = payload.cwd; + if (payload.model) record.model = payload.model; + + // iii context teaches discovery through the CLI; prepend it on a fresh + // session only — on resume it is already in the conversation history. + const iiiContext = payload.iii_context ?? cfg.iii_context; + const promptText = + iiiContext && !prior?.session_file ? `${III_CONTEXT_PROMPT}\n\n---\n\n${prompt}` : prompt; + + const session = await buildSession({ + cwd: record.cwd || undefined, + model: record.model || undefined, + thinkingLevel: payload.thinking_level ?? d.thinking_level, + tools: payload.tools ?? d.tools, + agentDir: d.agent_dir || undefined, + resumeFile: prior?.session_file ?? null, + }); + slot.session = session; + + record.status = 'working'; + record.updated_at_ms = Date.now(); + await saveSession(iii, record); + + const transcript: AgentMessage[] = []; + const pendingResults: FunctionResultMessage[] = []; + const calls: ToolCallIndex = new Map(); + let resultText = ''; + let stopReason = 'end'; + let isError = false; + + // Serial drain: the sync subscribe listener enqueues async emits so frames + // reach the streams in arrival order without blocking Pi's loop. + let chain: Promise = Promise.resolve(); + const enqueue = (fn: () => Promise) => { + chain = chain.then(fn).catch((err) => console.warn(`pi event emit failed: ${String(err)}`)); + }; + + const unsubscribe = session.subscribe((event) => { + enqueue(async () => { + await emitRaw(session_id, event); + await translate(event); + }); + }); + + async function translate(event: { type: string; [k: string]: unknown }): Promise { + if (event.type === 'message_end') { + const message = event.message as { role?: string } | undefined; + if (message?.role !== 'assistant') return; + const content = mapMessageContent(message); + const assistant = makeAssistantMessage(content, record.model, record.usage); + transcript.push(assistant); + const text = content + .filter((b): b is { type: 'text'; text: string } => b.type === 'text') + .map((b) => b.text) + .join(''); + if (text) resultText = text; + await emit(session_id, { type: 'message_complete', message: assistant }); + } else if (event.type === 'tool_execution_start') { + const callId = String(event.toolCallId ?? ''); + const function_id = toolFunctionId(String(event.toolName ?? 'unknown')); + calls.set(callId, { function_id, started_at: Date.now() }); + await emit(session_id, { + type: 'function_execution_start', + function_call_id: callId, + function_id, + args: event.args ?? {}, + }); + } else if (event.type === 'tool_execution_end') { + const callId = String(event.toolCallId ?? ''); + const call = calls.get(callId); + const function_id = call?.function_id ?? toolFunctionId(String(event.toolName ?? 'unknown')); + const content = mapToolResultContent(event.result); + const fr = makeFunctionResult(callId, function_id, content, event.isError === true); + transcript.push(fr); + pendingResults.push(fr); + await emit(session_id, { + type: 'function_execution_end', + function_call_id: callId, + function_id, + result: { content, details: null }, + is_error: fr.is_error, + duration_ms: call ? Date.now() - call.started_at : 0, + }); + } + } + + try { + await session.prompt(promptText); + await chain; + const stats = session.getSessionStats(); + record.usage = mapUsage(stats.tokens); + record.total_cost_usd += stats.cost ?? 0; + record.turns += 1; + record.pi_session_id = session.sessionId; + record.session_file = stats.sessionFile ?? session.sessionFile ?? record.session_file; + } catch (err) { + await chain; + isError = true; + stopReason = 'error'; + resultText = String(err); + } finally { + unsubscribe(); + session.dispose(); + } + + record.status = isError ? 'error' : 'done'; + record.updated_at_ms = Date.now(); + await saveSession(iii, record); + + if (transcript.length === 0 || transcript[transcript.length - 1].role !== 'assistant') { + 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, + pi_session_id: record.pi_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, getCfg: () => Config, emit: Emit, emitRaw: Emit): void { + iii.registerFunction( + 'pi::run', + async (payload: unknown) => + executeRun(iii, getCfg(), emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), + { + description: + 'Run one Pi coding-agent turn and wait for the result. Accepts `prompt` or a `messages` array; streams raw Pi events onto pi::events, AgentEvent frames onto agent::events, and returns {session_id, result, usage, total_cost_usd}.', + request_format: RUN_REQUEST_FORMAT, + }, + ); + + iii.registerFunction( + 'pi::start', + async (payload: unknown) => { + const parsed = RunPayloadSchema.parse(payload ?? {}); + const session_id = parsed.session_id ?? randomUUID(); + // executeRun reserves the live slot synchronously, so a prior start for + // this session is already visible here: report busy instead of a false + // started: true. Cannot await executeRun — start must return immediately. + if (live.has(session_id)) { + return { + session_id, + started: false, + busy: true, + reason: 'a run is already active for this session', + }; + } + void executeRun(iii, getCfg(), emit, emitRaw, { ...parsed, session_id }).catch( + async (err) => { + console.error(`pi::start background run failed for ${session_id}: ${String(err)}`); + await markSessionError(iii, session_id); + }, + ); + return { session_id, started: true }; + }, + { + description: + 'Start a Pi turn and return immediately; watch agent::events (group_id = session_id) for progress and turn_end.', + request_format: RUN_REQUEST_FORMAT, + }, + ); + + iii.registerFunction( + 'pi::steer', + async (payload: unknown) => { + const { session_id, prompt } = SteerPayloadSchema.parse(payload ?? {}); + const run = live.get(session_id); + if (!run?.session) return { session_id, steered: false, reason: 'no live run' }; + await run.session.steer(prompt); + return { session_id, steered: true }; + }, + { + description: + 'Inject a steering instruction into a live Pi run; applied after the current tool calls finish.', + request_format: STEER_REQUEST_FORMAT, + }, + ); + + iii.registerFunction( + 'pi::follow_up', + async (payload: unknown) => { + const { session_id, prompt } = SteerPayloadSchema.parse(payload ?? {}); + const run = live.get(session_id); + if (!run?.session) return { session_id, queued: false, reason: 'no live run' }; + await run.session.followUp(prompt); + return { session_id, queued: true }; + }, + { + description: + 'Queue a follow-up message for a live Pi run; processed after the agent would otherwise stop.', + request_format: STEER_REQUEST_FORMAT, + }, + ); + + iii.registerFunction( + 'pi::stop', + async (payload: unknown) => { + const { session_id } = SessionIdSchema.parse(payload ?? {}); + const run = live.get(session_id); + if (!run?.session) return { session_id, stopped: false, reason: 'no live run' }; + await run.session.abort(); + return { session_id, stopped: true }; + }, + { + description: 'Interrupt a live Pi run for a session.', + request_format: SESSION_ID_FORMAT, + }, + ); + + iii.registerFunction( + 'pi::status', + async (payload: unknown) => { + const { session_id } = SessionIdSchema.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 Pi session.', + request_format: SESSION_ID_FORMAT, + }, + ); + + iii.registerFunction('pi::sessions::list', async () => ({ sessions: await listSessions(iii) }), { + description: 'List every Pi session this worker has run.', + request_format: { type: 'object', properties: {} }, + }); + + iii.registerFunction( + 'run::start_and_wait', + async (payload: unknown) => + executeRun(iii, getCfg(), emit, emitRaw, RunPayloadSchema.parse(payload ?? {})), + { + description: + 'Alias for pi::run under the shared agent entrypoint: run a turn for {session_id, messages} and return when it ends.', + request_format: RUN_REQUEST_FORMAT, + }, + ); +} diff --git a/pi/src/session.ts b/pi/src/session.ts new file mode 100644 index 000000000..5c915f107 --- /dev/null +++ b/pi/src/session.ts @@ -0,0 +1,67 @@ +/** + * Build a Pi AgentSession for one turn. Pi runs the agent loop in-process via + * `@earendil-works/pi-coding-agent`, so there is no CLI subprocess: the worker + * owns auth, the model registry, and the session manager directly. + * + * Resume is by session file: a prior run stores its `sessionFile`, and the next + * run for the same iii session_id opens it so the Pi conversation continues. + */ + +import { + type AgentSession, + AuthStorage, + createAgentSession, + ModelRegistry, + SessionManager, +} from '@earendil-works/pi-coding-agent'; + +export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'; + +export type BuildSessionOptions = { + cwd?: string; + model?: string; + thinkingLevel: ThinkingLevel; + tools: string[]; + agentDir?: string; + resumeFile?: string | null; +}; + +/** Resolve a "provider/modelId" string against the registry; undefined = Pi default. */ +function resolveModel(registry: ModelRegistry, model: string | undefined) { + if (!model) return undefined; + const sep = model.indexOf('/'); + if (sep <= 0) { + console.warn(`model "${model}" is not in "provider/modelId" form; using Pi default`); + return undefined; + } + const provider = model.slice(0, sep); + const modelId = model.slice(sep + 1); + const resolved = registry.find(provider, modelId); + if (!resolved) console.warn(`model "${model}" not found in registry; using Pi default`); + return resolved; +} + +export async function buildSession(opts: BuildSessionOptions): Promise { + const agentDir = opts.agentDir || undefined; + const cwd = opts.cwd || process.cwd(); + const authStorage = AuthStorage.create(agentDir); + const modelRegistry = ModelRegistry.create(authStorage); + const sessionManager = opts.resumeFile + ? SessionManager.open(opts.resumeFile) + : SessionManager.create(cwd); + + const { session } = await createAgentSession({ + cwd, + agentDir, + authStorage, + modelRegistry, + sessionManager, + thinkingLevel: opts.thinkingLevel, + ...(opts.tools.length ? { tools: opts.tools } : {}), + ...(() => { + const model = resolveModel(modelRegistry, opts.model); + return model ? { model } : {}; + })(), + }); + return session; +} diff --git a/pi/src/state.ts b/pi/src/state.ts new file mode 100644 index 000000000..b6907a199 --- /dev/null +++ b/pi/src/state.ts @@ -0,0 +1,33 @@ +/** + * Session registry on engine state. Scope `pi_sessions`, key = iii session_id. + * Maps iii sessions to Pi session files so `pi::run` with the same session_id + * resumes the underlying Pi conversation. + */ + +import type { ISdk } from 'iii-sdk'; +import type { SessionRecord } from './types.js'; + +const SCOPE = 'pi_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/pi/src/types.ts b/pi/src/types.ts new file mode 100644 index 000000000..491ff40e6 --- /dev/null +++ b/pi/src/types.ts @@ -0,0 +1,89 @@ +/** + * 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 Pi turns like any other agent worker. + */ + +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; + pi_session_id: string | null; + session_file: 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/pi/tests/_helpers/fake-iii.ts b/pi/tests/_helpers/fake-iii.ts new file mode 100644 index 000000000..ff2ec05c6 --- /dev/null +++ b/pi/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/pi/tests/_helpers/fake-session.ts b/pi/tests/_helpers/fake-session.ts new file mode 100644 index 000000000..1e2766057 --- /dev/null +++ b/pi/tests/_helpers/fake-session.ts @@ -0,0 +1,101 @@ +/** Scripted Pi AgentSession replacement: stores the subscribe listener, fires + * a fixed event list when prompt() is called, and records the build options, + * prompt, steering/follow-up, abort, and dispose calls it sees. */ + +import type { AgentSession } from '@earendil-works/pi-coding-agent'; +import type { BuildSessionOptions } from '../../src/session.js'; + +export type PiEvent = { type: string; [k: string]: unknown }; + +export type SessionCapture = { + buildOpts?: BuildSessionOptions; + promptText?: string; + steered: string[]; + followUps: string[]; + aborted: boolean; + disposed: boolean; +}; + +export function newCapture(): SessionCapture { + return { steered: [], followUps: [], aborted: false, disposed: false }; +} + +export type ScriptOptions = { + events?: PiEvent[]; + stats?: unknown; + sessionId?: string; + sessionFile?: string; + throwOnPrompt?: Error; + gate?: Promise; +}; + +export function scriptedSession(opts: ScriptOptions, capture: SessionCapture): AgentSession { + let listener: ((e: PiEvent) => void) | undefined; + const stats = opts.stats ?? { + sessionFile: opts.sessionFile ?? '/sessions/cs-1.jsonl', + sessionId: opts.sessionId ?? 'cs-1', + tokens: { input: 5, output: 2, cacheRead: 0, cacheWrite: 0, total: 7 }, + cost: 0.01, + }; + const fake = { + subscribe(l: (e: PiEvent) => void) { + listener = l; + return () => { + listener = undefined; + }; + }, + async prompt(text: string) { + capture.promptText = text; + for (const e of opts.events ?? []) listener?.(e); + if (opts.gate) await opts.gate; + if (opts.throwOnPrompt) throw opts.throwOnPrompt; + }, + getSessionStats() { + return stats; + }, + get sessionId() { + return opts.sessionId ?? 'cs-1'; + }, + get sessionFile() { + return opts.sessionFile ?? '/sessions/cs-1.jsonl'; + }, + async steer(t: string) { + capture.steered.push(t); + }, + async followUp(t: string) { + capture.followUps.push(t); + }, + async abort() { + capture.aborted = true; + }, + dispose() { + capture.disposed = true; + }, + }; + return fake as unknown as AgentSession; +} + +/** One turn: a single bash tool call, then a final assistant message. */ +export const fullTurnEvents: PiEvent[] = [ + { type: 'agent_start' }, + { type: 'turn_start' }, + { + type: 'tool_execution_start', + toolCallId: 'call_1', + toolName: 'bash', + args: { command: 'ls' }, + }, + { + type: 'tool_execution_end', + toolCallId: 'call_1', + toolName: 'bash', + result: 'files', + isError: false, + }, + { + type: 'message_end', + message: { role: 'assistant', content: [{ type: 'text', text: 'done' }] }, + }, + { type: 'turn_end', message: { role: 'assistant' }, toolResults: [] }, + { type: 'agent_end', messages: [] }, +]; diff --git a/pi/tests/config.test.ts b/pi/tests/config.test.ts new file mode 100644 index 000000000..479230578 --- /dev/null +++ b/pi/tests/config.test.ts @@ -0,0 +1,50 @@ +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.thinking_level).toBe('medium'); + expect(cfg.defaults.tools).toEqual([]); + expect(cfg.events_stream).toBe('agent::events'); + expect(cfg.raw_events_stream).toBe('pi::events'); + expect(cfg.iii_context).toBe(true); + }); + + it('merges a partial file over defaults', async () => { + const dir = await mkdtemp(join(tmpdir(), 'pi-config-')); + const path = join(dir, 'config.yaml'); + await writeFile( + path, + [ + 'engine_url: ws://10.0.0.1:49134', + 'defaults:', + ' thinking_level: high', + 'iii_context: false', + ].join('\n'), + ); + const cfg = await loadConfig(path); + expect(cfg.engine_url).toBe('ws://10.0.0.1:49134'); + expect(cfg.defaults.thinking_level).toBe('high'); + expect(cfg.defaults.tools).toEqual([]); + expect(cfg.iii_context).toBe(false); + }); + + it('rethrows YAML parse errors instead of silently using defaults', async () => { + const dir = await mkdtemp(join(tmpdir(), 'pi-config-')); + const path = join(dir, 'config.yaml'); + await writeFile(path, 'defaults: [unclosed\n bad: {'); + await expect(loadConfig(path)).rejects.toThrow(); + }); + + it('rejects an invalid thinking level', async () => { + const dir = await mkdtemp(join(tmpdir(), 'pi-config-')); + const path = join(dir, 'config.yaml'); + await writeFile(path, 'defaults:\n thinking_level: ultra\n'); + await expect(loadConfig(path)).rejects.toThrow(); + }); +}); diff --git a/pi/tests/configuration.test.ts b/pi/tests/configuration.test.ts new file mode 100644 index 000000000..4f6f9c2c2 --- /dev/null +++ b/pi/tests/configuration.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { loadConfig, runtimeJsonSchema, toRuntime } from '../src/config.js'; +import { registerPiConfig } from '../src/configuration.js'; +import { fakeIii } from './_helpers/fake-iii.js'; + +describe('configuration worker integration', () => { + it('runtime schema excludes the bootstrap engine_url', () => { + const schema = runtimeJsonSchema() as { properties?: Record }; + expect(schema.properties).toBeDefined(); + expect(schema.properties).not.toHaveProperty('engine_url'); + expect(schema.properties).toHaveProperty('defaults'); + expect(schema.properties).toHaveProperty('raw_events_stream'); + }); + + it('toRuntime drops engine_url, keeps the rest', async () => { + const cfg = await loadConfig('/nonexistent/config.yaml'); + const rt = toRuntime(cfg) as Record; + expect(rt).not.toHaveProperty('engine_url'); + expect(rt.raw_events_stream).toBe('pi::events'); + expect(rt.iii_context).toBe(true); + }); + + it('registerPiConfig registers the schema with the seed as initial_value', async () => { + const fake = fakeIii(); + const cfg = await loadConfig('/nonexistent/config.yaml'); + await registerPiConfig(fake.iii, cfg); + const reg = fake.calls.find((c) => c.function_id === 'configuration::register'); + expect(reg).toBeDefined(); + const payload = reg?.payload as { + id?: string; + schema?: unknown; + initial_value?: Record; + }; + expect(payload.id).toBe('pi'); + expect(payload.schema).toBeDefined(); + expect(payload.initial_value).not.toHaveProperty('engine_url'); + expect(payload.initial_value?.iii_context).toBe(true); + }); +}); diff --git a/pi/tests/events.test.ts b/pi/tests/events.test.ts new file mode 100644 index 000000000..e1286ce24 --- /dev/null +++ b/pi/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/pi/tests/map.test.ts b/pi/tests/map.test.ts new file mode 100644 index 000000000..c278f0c9b --- /dev/null +++ b/pi/tests/map.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, it } from 'vitest'; +import { + lastAssistant, + makeAssistantMessage, + makeFunctionResult, + mapMessageContent, + mapToolResultContent, + mapUsage, + toolFunctionId, +} from '../src/map.js'; +import type { AgentMessage } from '../src/types.js'; + +describe('toolFunctionId', () => { + it('namespaces built-in Pi tools', () => { + expect(toolFunctionId('bash')).toBe('pi::bash'); + expect(toolFunctionId('edit')).toBe('pi::edit'); + }); + + 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'); + }); +}); + +describe('mapMessageContent', () => { + it('extracts text and thinking blocks', () => { + const out = mapMessageContent({ + role: 'assistant', + content: [ + { type: 'text', text: 'hello' }, + { type: 'thinking', thinking: 'hmm' }, + ], + }); + expect(out).toEqual([ + { type: 'text', text: 'hello' }, + { type: 'thinking', text: 'hmm' }, + ]); + }); + + it('reads reasoning as thinking', () => { + const out = mapMessageContent({ content: [{ type: 'reasoning', reasoning: 'because' }] }); + expect(out).toEqual([{ type: 'thinking', text: 'because' }]); + }); + + it('wraps string content as one text block', () => { + expect(mapMessageContent({ content: 'plain' })).toEqual([{ type: 'text', text: 'plain' }]); + }); + + it('drops tool-call blocks (surfaced as tool_execution events) and empties', () => { + const out = mapMessageContent({ + content: [{ type: 'toolCall', id: 'x' }, { type: 'text' }], + }); + expect(out).toEqual([]); + }); + + it('returns nothing for non-message input', () => { + expect(mapMessageContent(null)).toEqual([]); + expect(mapMessageContent({ content: 42 })).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('preserves scalar array entries instead of dropping them', () => { + expect(mapToolResultContent(['plain', 42, null])).toEqual([ + { type: 'text', text: '"plain"' }, + { type: 'text', text: '42' }, + { type: 'text', text: 'null' }, + ]); + }); + + 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 Pi token fields onto the wire shape', () => { + expect(mapUsage({ input: 10, output: 5, cacheRead: 100, cacheWrite: 7, total: 122 })).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(); + }); + + it('defaults absent cache token fields to 0 instead of undefined', () => { + expect(mapUsage({ input: 3, output: 1 })).toEqual({ + input_tokens: 3, + output_tokens: 1, + cache_read_tokens: 0, + cache_write_tokens: 0, + }); + }); +}); + +describe('message constructors', () => { + it('builds an assistant message with provider pi', () => { + const msg = makeAssistantMessage([{ type: 'text', text: 'hi' }], 'anthropic/claude', null); + expect(msg.role).toBe('assistant'); + expect(msg.provider).toBe('pi'); + expect(msg.stop_reason).toBe('end'); + }); + + it('builds a function_result message', () => { + const fr = makeFunctionResult('call_1', 'pi::bash', [{ type: 'text', text: 'ok' }], false); + expect(fr.role).toBe('function_result'); + expect(fr.function_call_id).toBe('call_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); + }); + + it('returns a synthetic assistant message for an empty transcript', () => { + const msg = lastAssistant([]); + expect(msg).toMatchObject({ role: 'assistant', content: [] }); + }); +}); diff --git a/pi/tests/register.test.ts b/pi/tests/register.test.ts new file mode 100644 index 000000000..ef5e4e627 --- /dev/null +++ b/pi/tests/register.test.ts @@ -0,0 +1,246 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('../src/session.js', () => ({ buildSession: vi.fn() })); + +import { loadConfig } from '../src/config.js'; +import { makeEmitter } from '../src/events.js'; +import { register } from '../src/run.js'; +import { buildSession } from '../src/session.js'; +import { fakeIii, type FakeIii } from './_helpers/fake-iii.js'; +import { + fullTurnEvents, + newCapture, + type PiEvent, + scriptedSession, + type SessionCapture, +} from './_helpers/fake-session.js'; + +const buildMock = vi.mocked(buildSession); + +/** Point buildSession at a scripted session; returns the shared capture. */ +function script(events: PiEvent[] = fullTurnEvents, gate?: Promise): SessionCapture { + const capture = newCapture(); + buildMock.mockImplementation(async (opts) => { + capture.buildOpts = opts; + return scriptedSession({ events, gate }, capture); + }); + return capture; +} + +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(() => { + buildMock.mockReset(); +}); + +describe('register', () => { + it('registers the full pi::* surface plus the shared entrypoint', async () => { + const fake = await registeredWorker(); + expect([...fake.registered.keys()].sort()).toEqual([ + 'pi::follow_up', + 'pi::run', + 'pi::sessions::list', + 'pi::start', + 'pi::status', + 'pi::steer', + 'pi::stop', + 'run::start_and_wait', + ]); + }); + + it('pi::run parses at the unknown boundary and runs a turn', async () => { + const fake = await registeredWorker(); + const capture = script(); + const res = (await fake.registered.get('pi::run')?.({ + prompt: 'hi', + session_id: 's1', + })) as Record; + expect(res.result).toBe('done'); + expect(capture.promptText).toContain('hi'); + }); + + it('pi::run rejects invalid payloads', async () => { + const fake = await registeredWorker(); + await expect( + fake.registered.get('pi::run')?.({ prompt: 'x', thinking_level: 'ultra' }), + ).rejects.toThrow(); + await expect(fake.registered.get('pi::run')?.({ timeout_ms: -1 })).rejects.toThrow(); + }); + + it('run::start_and_wait shares the pi::run handler behaviour', async () => { + const fake = await registeredWorker(); + const capture = script(); + 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.promptText).toContain('go'); + }); + + it('pi::start returns immediately and the turn lands in the background', async () => { + const fake = await registeredWorker(); + script(); + const res = (await fake.registered.get('pi::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(`pi_sessions/${res.session_id}`) as + | { status: string } + | undefined; + expect(record?.status).toBe('done'); + }); + }); + + it('marks the session error when a background run throws mid-stream', async () => { + const fake = await registeredWorker(); + const capture = newCapture(); + buildMock.mockImplementation(async (opts) => { + capture.buildOpts = opts; + return scriptedSession({ events: [], throwOnPrompt: new Error('stream died') }, capture); + }); + const res = (await fake.registered.get('pi::start')?.({ + prompt: 'bg', + session_id: 'bg-1', + })) as Record; + expect(res.started).toBe(true); + await vi.waitFor(() => { + const record = fake.state.get('pi_sessions/bg-1') as { status: string } | undefined; + expect(record?.status).toBe('error'); + }); + }); + + it('pi::stop without a live run reports stopped: false', async () => { + const fake = await registeredWorker(); + const res = (await fake.registered.get('pi::stop')?.({ session_id: 'ghost' })) as Record< + string, + unknown + >; + expect(res).toMatchObject({ session_id: 'ghost', stopped: false }); + }); + + it('pi::stop aborts a live run', async () => { + const fake = await registeredWorker(); + let release: (() => void) | undefined; + const gate = new Promise((r) => { + release = r; + }); + const capture = script(fullTurnEvents, gate); + const startRes = (await fake.registered.get('pi::start')?.({ + prompt: 'long', + session_id: 'live-1', + })) as Record; + await vi.waitFor(() => { + expect(fake.state.has('pi_sessions/live-1')).toBe(true); + }); + const stopRes = (await fake.registered.get('pi::stop')?.({ + session_id: String(startRes.session_id), + })) as Record; + expect(stopRes.stopped).toBe(true); + expect(capture.aborted).toBe(true); + release?.(); + }); + + it('pi::steer and pi::follow_up inject into a live run', async () => { + const fake = await registeredWorker(); + let release: (() => void) | undefined; + const gate = new Promise((r) => { + release = r; + }); + const capture = script(fullTurnEvents, gate); + await fake.registered.get('pi::start')?.({ prompt: 'long', session_id: 'live-2' }); + await vi.waitFor(() => { + expect(fake.state.has('pi_sessions/live-2')).toBe(true); + }); + const steerRes = (await fake.registered.get('pi::steer')?.({ + session_id: 'live-2', + prompt: 'do this instead', + })) as Record; + const followRes = (await fake.registered.get('pi::follow_up')?.({ + session_id: 'live-2', + prompt: 'then this', + })) as Record; + expect(steerRes).toMatchObject({ steered: true }); + expect(followRes).toMatchObject({ queued: true }); + expect(capture.steered).toEqual(['do this instead']); + expect(capture.followUps).toEqual(['then this']); + release?.(); + }); + + it('pi::steer without a live run reports steered: false', async () => { + const fake = await registeredWorker(); + const res = (await fake.registered.get('pi::steer')?.({ + session_id: 'ghost', + prompt: 'x', + })) as Record; + expect(res).toMatchObject({ session_id: 'ghost', steered: false }); + }); + + it('rejects a second run while one is already live for the session', async () => { + const fake = await registeredWorker(); + let release: (() => void) | undefined; + const gate = new Promise((r) => { + release = r; + }); + script(fullTurnEvents, gate); + await fake.registered.get('pi::start')?.({ prompt: 'first', session_id: 'busy-1' }); + await vi.waitFor(() => { + expect(fake.state.has('pi_sessions/busy-1')).toBe(true); + }); + const second = (await fake.registered.get('pi::run')?.({ + prompt: 'second', + session_id: 'busy-1', + })) as Record; + expect(second).toMatchObject({ session_id: 'busy-1', busy: true }); + release?.(); + }); + + it('pi::start reports busy instead of started when a run is already live', async () => { + const fake = await registeredWorker(); + let release: (() => void) | undefined; + const gate = new Promise((r) => { + release = r; + }); + script(fullTurnEvents, gate); + await fake.registered.get('pi::start')?.({ prompt: 'first', session_id: 'busy-2' }); + await vi.waitFor(() => { + expect(fake.state.has('pi_sessions/busy-2')).toBe(true); + }); + const second = (await fake.registered.get('pi::start')?.({ + prompt: 'second', + session_id: 'busy-2', + })) as Record; + expect(second).toMatchObject({ session_id: 'busy-2', busy: true, started: false }); + release?.(); + }); + + it('pi::status reflects the stored record and live flag', async () => { + const fake = await registeredWorker(); + const res = (await fake.registered.get('pi::status')?.({ session_id: 'none' })) as Record< + string, + unknown + >; + expect(res).toMatchObject({ session_id: 'none', live: false, record: null }); + }); + + it('pi::sessions::list returns every stored record', async () => { + const fake = await registeredWorker(); + script(); + await fake.registered.get('pi::run')?.({ prompt: 'a', session_id: 's1' }); + await fake.registered.get('pi::run')?.({ prompt: 'b', session_id: 's2' }); + const res = (await fake.registered.get('pi::sessions::list')?.({})) as { + sessions: Array<{ session_id: string }>; + }; + expect(res.sessions.map((s) => s.session_id).sort()).toEqual(['s1', 's2']); + }); +}); diff --git a/pi/tests/run-payload.test.ts b/pi/tests/run-payload.test.ts new file mode 100644 index 000000000..c4b3e3b4c --- /dev/null +++ b/pi/tests/run-payload.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('../src/session.js', () => ({ buildSession: vi.fn() })); + +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 messages array 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 thinking level', () => { + expect(() => RunPayloadSchema.parse({ prompt: 'x', thinking_level: 'ultra' })).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/pi/tests/run.test.ts b/pi/tests/run.test.ts new file mode 100644 index 000000000..8fa6d98c9 --- /dev/null +++ b/pi/tests/run.test.ts @@ -0,0 +1,290 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('../src/session.js', () => ({ buildSession: vi.fn() })); + +import { type Config, loadConfig } from '../src/config.js'; +import { makeEmitter } from '../src/events.js'; +import { executeRun, RunPayloadSchema } from '../src/run.js'; +import { buildSession } from '../src/session.js'; +import { fakeIii } from './_helpers/fake-iii.js'; +import { + fullTurnEvents, + newCapture, + type PiEvent, + scriptedSession, + type SessionCapture, +} from './_helpers/fake-session.js'; + +const buildMock = vi.mocked(buildSession); + +async function baseConfig(): Promise { + return loadConfig('/nonexistent/config.yaml'); +} + +function script(events: PiEvent[] = fullTurnEvents, throwOnPrompt?: Error): SessionCapture { + const capture = newCapture(); + buildMock.mockImplementation(async (opts) => { + capture.buildOpts = opts; + return scriptedSession({ events, throwOnPrompt }, capture); + }); + return capture; +} + +async function runTurn( + payload: Record, + events: PiEvent[] = fullTurnEvents, + cfgOverrides: Partial = {}, +) { + const fake = fakeIii(); + const cfg = { ...(await baseConfig()), ...cfgOverrides }; + const capture = script(events); + 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(() => { + buildMock.mockReset(); +}); + +describe('executeRun', () => { + it('releases the live slot even when the working-save rejects (no stuck busy)', async () => { + const fake = fakeIii(); + const cfg = await baseConfig(); + script(); + // make every state::set reject — the working-save during setup throws + const realTrigger = fake.iii.trigger.bind(fake.iii); + (fake.iii as { trigger: unknown }).trigger = async (req: { function_id: string }) => { + if (req.function_id === 'state::set') throw new Error('store down'); + return realTrigger(req as never); + }; + const emit = makeEmitter(fake.iii, cfg.events_stream); + await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ prompt: 'x', session_id: 'leak-1' }), + ).catch(() => {}); + const second = (await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ prompt: 'x', session_id: 'leak-1' }), + ).catch(() => ({ busy: undefined }))) as Record; + expect(second.busy).not.toBe(true); + }); + + 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', + pi_session_id: 'cs-1', + result: 'done', + stop_reason: 'end', + is_error: false, + num_turns: 1, + total_cost_usd: 0.01, + usage: { input_tokens: 5, output_tokens: 2 }, + }); + }); + + it('persists the session record working then done with the Pi session id and file', 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 === 'pi_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.pi_session_id).toBe('cs-1'); + expect(final.session_file).toBe('/sessions/cs-1.jsonl'); + expect(final.total_cost_usd).toBe(0.01); + }); + + it('mirrors every Pi event verbatim onto the raw stream', async () => { + const { fake } = await runTurn({ prompt: 'x', session_id: 's1' }); + const raw = fake.streamFrames('pi::events').map((f) => f.data); + expect(raw).toEqual(fullTurnEvents); + const groupIds = fake.streamFrames('pi::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([ + 'function_execution_start', + 'function_execution_end', + 'message_complete', + '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: 'call_1', + function_id: 'pi::bash', + args: { command: 'ls' }, + }); + expect(end).toMatchObject({ function_call_id: 'call_1', is_error: false }); + }); + + it('prepends the iii runtime context to the prompt by default on a fresh session', async () => { + const { capture } = await runTurn({ prompt: 'do it', session_id: 's1' }); + expect(capture.promptText).toContain('# iii runtime'); + expect(capture.promptText).toContain('iii trigger engine::functions::list'); + expect(capture.promptText?.trimEnd().endsWith('do it')).toBe(true); + }); + + it('config-level iii_context: false disables the block for every turn', async () => { + const { capture } = await runTurn({ prompt: 'do it', session_id: 's1' }, fullTurnEvents, { + iii_context: false, + }); + expect(capture.promptText).toBe('do it'); + }); + + it('omits the iii context when disabled per turn', async () => { + const { capture } = await runTurn({ + prompt: 'do it', + session_id: 's1', + iii_context: false, + }); + expect(capture.promptText).toBe('do it'); + }); + + it('passes worker defaults and named fields to the session builder', async () => { + const { capture } = await runTurn({ + prompt: 'x', + session_id: 's1', + cwd: '/repo', + model: 'anthropic/claude-sonnet-4', + thinking_level: 'high', + tools: ['read', 'bash'], + }); + expect(capture.buildOpts).toMatchObject({ + cwd: '/repo', + model: 'anthropic/claude-sonnet-4', + thinkingLevel: 'high', + tools: ['read', 'bash'], + resumeFile: null, + }); + }); + + it('resumes the prior Pi session file for a known session_id', async () => { + const fake = fakeIii(); + fake.state.set('pi_sessions/s1', { + session_id: 's1', + pi_session_id: 'cs-prior', + session_file: '/sessions/prior.jsonl', + cwd: '/repo', + model: '', + status: 'done', + turns: 1, + total_cost_usd: 0.01, + usage: null, + updated_at_ms: 1, + }); + const cfg = await baseConfig(); + const capture = script(); + 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.buildOpts?.resumeFile).toBe('/sessions/prior.jsonl'); + // resume skips the iii context prepend — already in conversation history + expect(capture.promptText).toBe('again'); + expect(result.num_turns).toBe(2); + }); + + it('honors per-turn cwd and model overrides on a resumed session', async () => { + const fake = fakeIii(); + fake.state.set('pi_sessions/s1', { + session_id: 's1', + pi_session_id: 'cs-prior', + session_file: '/sessions/prior.jsonl', + cwd: '/old/repo', + model: 'old/model', + status: 'done', + turns: 1, + total_cost_usd: 0, + usage: null, + updated_at_ms: 1, + }); + const cfg = await baseConfig(); + const capture = script(); + const emit = makeEmitter(fake.iii, cfg.events_stream); + await executeRun( + fake.iii, + cfg, + emit, + emit, + RunPayloadSchema.parse({ + prompt: 'x', + session_id: 's1', + cwd: '/new/repo', + model: 'new/model', + }), + ); + expect(capture.buildOpts).toMatchObject({ + cwd: '/new/repo', + model: 'new/model', + resumeFile: '/sessions/prior.jsonl', + }); + }); + + it('extracts the prompt from the last user message of a messages payload', async () => { + const { capture } = await runTurn({ + session_id: 's1', + iii_context: false, + messages: [ + { role: 'user', content: [{ type: 'text', text: 'first' }] }, + { role: 'assistant', content: [{ type: 'text', text: 'reply' }] }, + { role: 'user', content: [{ type: 'text', text: 'second' }] }, + ], + }); + expect(capture.promptText).toBe('second'); + }); + + it('marks the record error and still closes the turn when the prompt throws', async () => { + const fake = fakeIii(); + const cfg = await baseConfig(); + script([], new Error('spawn failed')); + 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('pi_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('disposes the session after the turn', async () => { + const { capture } = await runTurn({ prompt: 'x', session_id: 's1' }); + expect(capture.disposed).toBe(true); + }); + + it('starts a fresh session with no resume file', async () => { + const { capture } = await runTurn({ prompt: 'x', session_id: 'fresh' }); + expect(capture.buildOpts?.resumeFile).toBeNull(); + }); +}); diff --git a/pi/tests/state.test.ts b/pi/tests/state.test.ts new file mode 100644 index 000000000..b4d92e6bd --- /dev/null +++ b/pi/tests/state.test.ts @@ -0,0 +1,59 @@ +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, + pi_session_id: `cs-${session_id}`, + session_file: `/sessions/${session_id}.jsonl`, + 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 pi_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: 'pi_sessions', key: 's1' }); + await expect(loadSession(fake.iii, 's1')).resolves.toMatchObject({ + session_id: 's1', + pi_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([]); + }); +}); diff --git a/pi/tsconfig.json b/pi/tsconfig.json new file mode 100644 index 000000000..9a3b09b03 --- /dev/null +++ b/pi/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/pi/vitest.config.ts b/pi/vitest.config.ts new file mode 100644 index 000000000..1c1ef7836 --- /dev/null +++ b/pi/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, + }, +});