From ec378ebd2867f6485c6652d0da930525587408ff Mon Sep 17 00:00:00 2001 From: alex wiesner Date: Thu, 9 Apr 2026 23:14:57 +0100 Subject: [PATCH] sync local pi changes --- .gitignore | 11 + .pi/agent/auth.json | 15 - .pi/agent/extensions/context-manager/index.ts | 353 ++ .../context-manager/package-lock.json | 4361 ++++++++++++++++ .../extensions/context-manager/package.json | 18 + .../context-manager/src/commands.ts | 76 + .../context-manager/src/config.test.ts | 86 + .../extensions/context-manager/src/config.ts | 97 + .../context-manager/src/distill.test.ts | 30 + .../extensions/context-manager/src/distill.ts | 47 + .../context-manager/src/extension.test.ts | 833 ++++ .../context-manager/src/extract.test.ts | 280 ++ .../extensions/context-manager/src/extract.ts | 314 ++ .../context-manager/src/ledger.test.ts | 132 + .../extensions/context-manager/src/ledger.ts | 196 + .../context-manager/src/packet.test.ts | 130 + .../extensions/context-manager/src/packet.ts | 91 + .../context-manager/src/persist.test.ts | 67 + .../extensions/context-manager/src/persist.ts | 142 + .../context-manager/src/prune.test.ts | 131 + .../extensions/context-manager/src/prune.ts | 54 + .../context-manager/src/runtime.test.ts | 178 + .../extensions/context-manager/src/runtime.ts | 127 + .../context-manager/src/summaries.test.ts | 139 + .../context-manager/src/summaries.ts | 251 + .pi/agent/extensions/dev-tools/index.ts | 50 + .../extensions/dev-tools/package-lock.json | 4386 +++++++++++++++++ .pi/agent/extensions/dev-tools/package.json | 23 + .../extensions/dev-tools/src/config.test.ts | 37 + .pi/agent/extensions/dev-tools/src/config.ts | 38 + .../src/diagnostics/command-backend.test.ts | 40 + .../src/diagnostics/command-backend.ts | 45 + .../src/diagnostics/lsp-client.test.ts | 41 + .../dev-tools/src/diagnostics/lsp-client.ts | 102 + .../dev-tools/src/diagnostics/types.ts | 19 + .../dev-tools/src/extension.test.ts | 16 + .../src/formatting/command-runner.test.ts | 27 + .../src/formatting/command-runner.ts | 33 + .../extensions/dev-tools/src/profiles.test.ts | 26 + .../extensions/dev-tools/src/profiles.ts | 47 + .../dev-tools/src/project-probe.test.ts | 14 + .../extensions/dev-tools/src/project-probe.ts | 68 + .../extensions/dev-tools/src/runtime.test.ts | 44 + .pi/agent/extensions/dev-tools/src/runtime.ts | 134 + .pi/agent/extensions/dev-tools/src/schema.ts | 40 + .../extensions/dev-tools/src/summary.test.ts | 27 + .pi/agent/extensions/dev-tools/src/summary.ts | 31 + .../dev-tools/src/tools/edit.test.ts | 54 + .../extensions/dev-tools/src/tools/edit.ts | 21 + .../dev-tools/src/tools/setup-suggest.test.ts | 15 + .../dev-tools/src/tools/setup-suggest.ts | 17 + .../dev-tools/src/tools/write.test.ts | 40 + .../extensions/dev-tools/src/tools/write.ts | 23 + .pi/agent/extensions/tmux-subagent/index.ts | 99 + .../tmux-subagent/package-lock.json | 4365 ++++++++++++++++ .../extensions/tmux-subagent/package.json | 23 + .../prompts/implement-and-review.md | 10 + .../tmux-subagent/prompts/implement.md | 10 + .../tmux-subagent/prompts/scout-and-plan.md | 9 + .../tmux-subagent/src/agents.test.ts | 54 + .../extensions/tmux-subagent/src/agents.ts | 91 + .../tmux-subagent/src/artifacts.test.ts | 21 + .../extensions/tmux-subagent/src/artifacts.ts | 66 + .../tmux-subagent/src/builtin-agents.ts | 43 + .../tmux-subagent/src/extension.test.ts | 397 ++ .../tmux-subagent/src/models.test.ts | 44 + .../extensions/tmux-subagent/src/models.ts | 58 + .../tmux-subagent/src/monitor.test.ts | 33 + .../extensions/tmux-subagent/src/monitor.ts | 34 + .../tmux-subagent/src/prompts.test.ts | 20 + .../tmux-subagent/src/runner.test.ts | 33 + .../extensions/tmux-subagent/src/runner.ts | 44 + .../extensions/tmux-subagent/src/schema.ts | 84 + .../extensions/tmux-subagent/src/tmux.test.ts | 43 + .../extensions/tmux-subagent/src/tmux.ts | 41 + .../tmux-subagent/src/tool-chain.test.ts | 107 + .../tmux-subagent/src/tool-parallel.test.ts | 97 + .../extensions/tmux-subagent/src/tool.test.ts | 177 + .../extensions/tmux-subagent/src/tool.ts | 336 ++ .../tmux-subagent/src/wrapper/cli.mjs | 214 + .../tmux-subagent/src/wrapper/cli.test.ts | 192 + .../tmux-subagent/src/wrapper/normalize.mjs | 35 + .../src/wrapper/normalize.test.ts | 38 + .../tmux-subagent/src/wrapper/render.mjs | 33 + .../tmux-subagent/src/wrapper/render.test.ts | 28 + .pi/agent/extensions/web-search/index.ts | 31 +- .../src/commands/web-search-config.test.ts | 65 + .../src/commands/web-search-config.ts | 229 + .../extensions/web-search/src/config.test.ts | 24 + .pi/agent/extensions/web-search/src/config.ts | 73 +- .../web-search/src/extension.test.ts | 7 +- .../extensions/web-search/src/format.test.ts | 44 + .pi/agent/extensions/web-search/src/format.ts | 62 +- .../web-search/src/providers/tavily.test.ts | 84 + .../web-search/src/providers/tavily.ts | 107 + .../web-search/src/providers/types.ts | 28 + .../extensions/web-search/src/runtime.test.ts | 85 + .../extensions/web-search/src/runtime.ts | 139 + .pi/agent/extensions/web-search/src/schema.ts | 43 +- .../web-search/src/tools/web-fetch.test.ts | 64 +- .../web-search/src/tools/web-fetch.ts | 10 +- .../web-search/src/tools/web-search.test.ts | 59 +- .../web-search/src/tools/web-search.ts | 10 +- .pi/agent/skills/find-docs/SKILL.md | 154 + .../skills/research/deep-research/SKILL.md | 192 + .pi/agent/web-search.json | 14 - .../context-manager-task2-fix-report.md | 31 + ...ontext-manager-task2-implementer-report.md | 38 + .../context-manager-task2-minor-fix-report.md | 30 + ...ntext-manager-task2-snapshot-fix-report.md | 35 + ...ontext-manager-task3-durable-fix-report.md | 44 + .../context-manager-task3-fix-report.md | 41 + ...ontext-manager-task3-implementer-report.md | 40 + .../context-manager-task3-scope-fix-report.md | 42 + ...ntext-manager-task4-boundary-fix-report.md | 13 + .../context-manager-task4-fix-report.md | 46 + ...ontext-manager-task4-implementer-report.md | 37 + .../context-manager-task5-fix-report.md | 21 + ...ontext-manager-task5-implementer-report.md | 55 + ...context-manager-task5-legacy-fix-report.md | 29 + .../context-manager-task6-fix-2-report.md | 30 + .../context-manager-task6-fix-report.md | 44 + ...ontext-manager-task6-implementer-report.md | 48 + .../context-manager-task6-mode-fix-report.md | 20 + .../plans/2026-04-09-question-tool.md | 1114 ----- .../plans/2026-04-09-web-search-tools.md | 1498 ------ .../specs/2026-04-09-question-tool-design.md | 279 -- .../2026-04-09-web-search-tools-design.md | 391 -- 128 files changed, 22510 insertions(+), 3436 deletions(-) delete mode 100644 .pi/agent/auth.json create mode 100644 .pi/agent/extensions/context-manager/index.ts create mode 100644 .pi/agent/extensions/context-manager/package-lock.json create mode 100644 .pi/agent/extensions/context-manager/package.json create mode 100644 .pi/agent/extensions/context-manager/src/commands.ts create mode 100644 .pi/agent/extensions/context-manager/src/config.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/config.ts create mode 100644 .pi/agent/extensions/context-manager/src/distill.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/distill.ts create mode 100644 .pi/agent/extensions/context-manager/src/extension.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/extract.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/extract.ts create mode 100644 .pi/agent/extensions/context-manager/src/ledger.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/ledger.ts create mode 100644 .pi/agent/extensions/context-manager/src/packet.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/packet.ts create mode 100644 .pi/agent/extensions/context-manager/src/persist.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/persist.ts create mode 100644 .pi/agent/extensions/context-manager/src/prune.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/prune.ts create mode 100644 .pi/agent/extensions/context-manager/src/runtime.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/runtime.ts create mode 100644 .pi/agent/extensions/context-manager/src/summaries.test.ts create mode 100644 .pi/agent/extensions/context-manager/src/summaries.ts create mode 100644 .pi/agent/extensions/dev-tools/index.ts create mode 100644 .pi/agent/extensions/dev-tools/package-lock.json create mode 100644 .pi/agent/extensions/dev-tools/package.json create mode 100644 .pi/agent/extensions/dev-tools/src/config.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/config.ts create mode 100644 .pi/agent/extensions/dev-tools/src/diagnostics/command-backend.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/diagnostics/command-backend.ts create mode 100644 .pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.ts create mode 100644 .pi/agent/extensions/dev-tools/src/diagnostics/types.ts create mode 100644 .pi/agent/extensions/dev-tools/src/extension.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/formatting/command-runner.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/formatting/command-runner.ts create mode 100644 .pi/agent/extensions/dev-tools/src/profiles.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/profiles.ts create mode 100644 .pi/agent/extensions/dev-tools/src/project-probe.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/project-probe.ts create mode 100644 .pi/agent/extensions/dev-tools/src/runtime.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/runtime.ts create mode 100644 .pi/agent/extensions/dev-tools/src/schema.ts create mode 100644 .pi/agent/extensions/dev-tools/src/summary.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/summary.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/edit.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/edit.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/setup-suggest.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/setup-suggest.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/write.test.ts create mode 100644 .pi/agent/extensions/dev-tools/src/tools/write.ts create mode 100644 .pi/agent/extensions/tmux-subagent/index.ts create mode 100644 .pi/agent/extensions/tmux-subagent/package-lock.json create mode 100644 .pi/agent/extensions/tmux-subagent/package.json create mode 100644 .pi/agent/extensions/tmux-subagent/prompts/implement-and-review.md create mode 100644 .pi/agent/extensions/tmux-subagent/prompts/implement.md create mode 100644 .pi/agent/extensions/tmux-subagent/prompts/scout-and-plan.md create mode 100644 .pi/agent/extensions/tmux-subagent/src/agents.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/agents.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/artifacts.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/artifacts.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/builtin-agents.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/extension.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/models.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/models.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/monitor.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/monitor.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/prompts.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/runner.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/runner.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/schema.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tmux.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tmux.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tool-chain.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tool-parallel.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tool.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/tool.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/cli.mjs create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/cli.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/normalize.mjs create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/normalize.test.ts create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/render.mjs create mode 100644 .pi/agent/extensions/tmux-subagent/src/wrapper/render.test.ts create mode 100644 .pi/agent/extensions/web-search/src/commands/web-search-config.test.ts create mode 100644 .pi/agent/extensions/web-search/src/commands/web-search-config.ts create mode 100644 .pi/agent/extensions/web-search/src/providers/tavily.test.ts create mode 100644 .pi/agent/extensions/web-search/src/providers/tavily.ts create mode 100644 .pi/agent/extensions/web-search/src/runtime.test.ts create mode 100644 .pi/agent/extensions/web-search/src/runtime.ts create mode 100644 .pi/agent/skills/find-docs/SKILL.md create mode 100644 .pi/agent/skills/research/deep-research/SKILL.md delete mode 100644 .pi/agent/web-search.json create mode 100644 .pi/reviews/context-manager-task2-fix-report.md create mode 100644 .pi/reviews/context-manager-task2-implementer-report.md create mode 100644 .pi/reviews/context-manager-task2-minor-fix-report.md create mode 100644 .pi/reviews/context-manager-task2-snapshot-fix-report.md create mode 100644 .pi/reviews/context-manager-task3-durable-fix-report.md create mode 100644 .pi/reviews/context-manager-task3-fix-report.md create mode 100644 .pi/reviews/context-manager-task3-implementer-report.md create mode 100644 .pi/reviews/context-manager-task3-scope-fix-report.md create mode 100644 .pi/reviews/context-manager-task4-boundary-fix-report.md create mode 100644 .pi/reviews/context-manager-task4-fix-report.md create mode 100644 .pi/reviews/context-manager-task4-implementer-report.md create mode 100644 .pi/reviews/context-manager-task5-fix-report.md create mode 100644 .pi/reviews/context-manager-task5-implementer-report.md create mode 100644 .pi/reviews/context-manager-task5-legacy-fix-report.md create mode 100644 .pi/reviews/context-manager-task6-fix-2-report.md create mode 100644 .pi/reviews/context-manager-task6-fix-report.md create mode 100644 .pi/reviews/context-manager-task6-implementer-report.md create mode 100644 .pi/reviews/context-manager-task6-mode-fix-report.md delete mode 100644 docs/superpowers/plans/2026-04-09-question-tool.md delete mode 100644 docs/superpowers/plans/2026-04-09-web-search-tools.md delete mode 100644 docs/superpowers/specs/2026-04-09-question-tool-design.md delete mode 100644 docs/superpowers/specs/2026-04-09-web-search-tools-design.md diff --git a/.gitignore b/.gitignore index 4cf2205..3680a3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ .worktrees/ .pi/npm/ .pi/agent/sessions/ +.pi/agent/auth.json +.pi/agent/web-search.json +.pi/subagents/ +.pi/agent/extensions/.pi/ +.pi/agent/extensions/tmux-subagent/events.jsonl +.pi/agent/extensions/tmux-subagent/result.json +.pi/agent/extensions/tmux-subagent/stderr.log +.pi/agent/extensions/tmux-subagent/stdout.log +.pi/agent/extensions/tmux-subagent/transcript.log +*bun.lock +*node_modules diff --git a/.pi/agent/auth.json b/.pi/agent/auth.json deleted file mode 100644 index 2e4a2a4..0000000 --- a/.pi/agent/auth.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "github-copilot": { - "type": "oauth", - "refresh": "ghu_j9QHUrVzPLoYOsyjarpzktAFDQWqP31gz2Ac", - "access": "tid=af454cc719f9e4daffe9b4892fa4e791;exp=1775732126;sku=plus_monthly_subscriber_quota;proxy-ep=proxy.individual.githubcopilot.com;st=dotcom;chat=1;cit=1;malfil=1;editor_preview_features=1;agent_mode=1;agent_mode_auto_approval=1;mcp=1;client_byok=0;ccr=1;8kp=1;ip=81.104.194.177;asn=AS5089:e4ff19791adbf3b64531636bad853d4de1c02e75ac46089baa3d2d799cbefadf", - "expires": 1775731826000 - }, - "openai-codex": { - "type": "oauth", - "access": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfRU1vYW1FRVo3M2YwQ2tYYVhwN2hyYW5uIiwiZXhwIjoxNzc2NDE1MDUwLCJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiYW1yIjpbInBvcCIsInVybjpvcGVuYWk6YW1yOnBhc3NrZXkiLCJtZmEiXSwiY2hhdGdwdF9hY2NvdW50X2lkIjoiOTY1MTZkMjYtMjljOS00Y2JjLWEwZDItNmZjODdlNzc3ZjRhIiwiY2hhdGdwdF9hY2NvdW50X3VzZXJfaWQiOiJ1c2VyLXloUkkzTjdiVHlvc0xBd1I5NmNOU25wUV9fOTY1MTZkMjYtMjljOS00Y2JjLWEwZDItNmZjODdlNzc3ZjRhIiwiY2hhdGdwdF9jb21wdXRlX3Jlc2lkZW5jeSI6Im5vX2NvbnN0cmFpbnQiLCJjaGF0Z3B0X3BsYW5fdHlwZSI6InBsdXMiLCJjaGF0Z3B0X3VzZXJfaWQiOiJ1c2VyLXloUkkzTjdiVHlvc0xBd1I5NmNOU25wUSIsImxvY2FsaG9zdCI6dHJ1ZSwidXNlcl9pZCI6InVzZXIteWhSSTNON2JUeW9zTEF3Ujk2Y05TbnBRIn0sImh0dHBzOi8vYXBpLm9wZW5haS5jb20vbWZhIjp7InJlcXVpcmVkIjoieWVzIn0sImh0dHBzOi8vYXBpLm9wZW5haS5jb20vcHJvZmlsZSI6eyJlbWFpbCI6ImNoYXRncHQuY29tLmRldGVyZ2VudDI3N0BwYXNzbWFpbC5uZXQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZX0sImlhdCI6MTc3NTU1MTA1MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLm9wZW5haS5jb20iLCJqdGkiOiI5OGZmNDRjOC02ZWFlLTRhOWQtOGQ1Yy04MTNkYWI1NjY4ZDgiLCJuYmYiOjE3NzU1NTEwNTAsInB3ZF9hdXRoX3RpbWUiOjE3NzU1NTEwNDg4NTAsInNjcCI6WyJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJvZmZsaW5lX2FjY2VzcyJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfOXVQRzcwbDcyQ1dkbTVtMXkyMmp6WkpyIiwic2wiOnRydWUsInN1YiI6ImF1dGgwfG1zcmNqUDZTYkgzdTROUHFzZ1Y2SERyNyJ9.AIcfng7BnC_IUK8DYedcWI8M6AZ5r2FszzM4orhrI5Ql0nXQ-eZBtV8RVcSl6wHvkcj6XX-BcpxxJQL_w0JbRPs4utQ7ayTeEhFYut8OnsLCTcMJDF0s5Qwv4GlTJNbuG_3P6hBe8xiZ6kPkDp0ZihZOkiceghPEaBRh_npt8-zm7SQyl8R8qdfhFToYzUAgGox3aZHVQeWGWpBm39MB_WigA6jsLCK5h-SwX5iuSHppGzii8ohyiaTgHfcEKUa9kgWXHa4iOtPHxPtD3t_rWJTZuc3XfeO4V3raR8HT96m8wrAHTgKlNA5IrmVwj8pt_fUH6AbApMrJY9q5Le6ubzCbH5bmnO2PIVLKfd7Kyw-E1gtjSOH61dvgRxDFLNwjAMeKNYRnrsPRZRr1pI5Y4JV9VejsjEE-MdvN48EEIWbZn4MvKtSSd5Xr_RGZPS80wLWV0WV_5qWL62aYJjTS4Vz4B3kWFBQsPNp08ykd2NL7b5H-uuP3akY97Jasklzvhuc9BgQZBymVlGO6Fwq1GiRggCu62B6OKJlxKOqgTOHGNGhFgmgGQWxpz-cCm-qKTb81vBEbziNBmXQdhL-507cFMJwsYBYyxKI1x79Gn3odkzHWoyijTxSCColYeqOBOdba9B9y8hdNmUwhn42W27A6Hm0bojiPoerUh6ng7Nk", - "refresh": "rt_LTCkO68CsFMGg9wrJP3qgfroR-b32AXV7Uw9cmtD_nA.4ZFAy5DZCiJaIEbHiSpLyddbqWhs02ZB53NMA9PRjq8", - "expires": 1776415049237, - "accountId": "96516d26-29c9-4cbc-a0d2-6fc87e777f4a" - } -} \ No newline at end of file diff --git a/.pi/agent/extensions/context-manager/index.ts b/.pi/agent/extensions/context-manager/index.ts new file mode 100644 index 0000000..376680d --- /dev/null +++ b/.pi/agent/extensions/context-manager/index.ts @@ -0,0 +1,353 @@ +import type { AgentMessage } from "@mariozechner/pi-agent-core"; +import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { adjustPolicyForZone } from "./src/config.ts"; +import { deserializeLatestSnapshot, serializeSnapshot, SNAPSHOT_ENTRY_TYPE, type RuntimeSnapshot } from "./src/persist.ts"; +import { createEmptyLedger } from "./src/ledger.ts"; +import { pruneContextMessages } from "./src/prune.ts"; +import { createContextManagerRuntime } from "./src/runtime.ts"; +import { registerContextCommands } from "./src/commands.ts"; +import { buildBranchSummaryFromEntries, buildCompactionSummaryFromPreparation } from "./src/summaries.ts"; + +type TrackedMessage = Extract; +type BranchEntry = ReturnType[number]; + +function isTextPart(part: unknown): part is { type: "text"; text?: string } { + return typeof part === "object" && part !== null && "type" in part && (part as { type?: unknown }).type === "text"; +} + +function toText(content: unknown): string { + if (typeof content === "string") return content; + if (!Array.isArray(content)) return ""; + + return content + .map((part) => { + if (!isTextPart(part)) return ""; + return typeof part.text === "string" ? part.text : ""; + }) + .join("\n") + .trim(); +} + +function isMessageEntry(entry: BranchEntry): entry is Extract { + return entry.type === "message"; +} + +function isCompactionEntry(entry: BranchEntry): entry is Extract { + return entry.type === "compaction"; +} + +function isBranchSummaryEntry(entry: BranchEntry): entry is Extract { + return entry.type === "branch_summary"; +} + +function isTrackedMessage(message: AgentMessage): message is TrackedMessage { + return message.role === "user" || message.role === "assistant" || message.role === "toolResult"; +} + +function createDefaultSnapshot(): RuntimeSnapshot { + return { + mode: "balanced", + lastZone: "green", + ledger: createEmptyLedger(), + }; +} + +function getMessageContent(message: AgentMessage): string { + return "content" in message ? toText(message.content) : ""; +} + +function getMessageToolName(message: AgentMessage): string | undefined { + return message.role === "toolResult" ? message.toolName : undefined; +} + +function rewriteContextMessage(message: { role: string; content: string; original: AgentMessage; distilled?: boolean }): AgentMessage { + if (!message.distilled || message.role !== "toolResult") { + return message.original; + } + + return { + ...(message.original as Extract), + content: [{ type: "text", text: message.content }], + } as AgentMessage; +} + +function findLatestSnapshotState(branch: BranchEntry[]): { snapshot: RuntimeSnapshot; index: number } | undefined { + for (let index = branch.length - 1; index >= 0; index -= 1) { + const entry = branch[index]!; + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = deserializeLatestSnapshot([entry]); + if (snapshot) { + return { snapshot, index }; + } + } + + return undefined; +} + +function findLatestSessionSnapshot(entries: BranchEntry[]): RuntimeSnapshot | undefined { + let latest: RuntimeSnapshot | undefined; + let latestFreshness = -Infinity; + + for (const entry of entries) { + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = deserializeLatestSnapshot([entry]); + if (!snapshot) { + continue; + } + + const sessionItems = snapshot.ledger.items.filter((item) => item.scope === "session"); + const freshness = sessionItems.length > 0 ? Math.max(...sessionItems.map((item) => item.timestamp)) : -Infinity; + if (freshness >= latestFreshness) { + latest = snapshot; + latestFreshness = freshness; + } + } + + return latest; +} + +function createSessionFallbackSnapshot(source?: RuntimeSnapshot): RuntimeSnapshot { + return { + mode: source?.mode ?? "balanced", + lastZone: "green", + ledger: { + items: structuredClone((source?.ledger.items ?? []).filter((item) => item.scope === "session")), + rollingSummary: "", + }, + }; +} + +function overlaySessionLayer(base: RuntimeSnapshot, latestSessionSnapshot?: RuntimeSnapshot): RuntimeSnapshot { + const sessionItems = latestSessionSnapshot?.ledger.items.filter((item) => item.scope === "session") ?? []; + if (sessionItems.length === 0) { + return base; + } + + return { + ...base, + ledger: { + ...base.ledger, + items: [ + ...structuredClone(base.ledger.items.filter((item) => item.scope !== "session")), + ...structuredClone(sessionItems), + ], + }, + }; +} + +export default function contextManager(pi: ExtensionAPI) { + const runtime = createContextManagerRuntime({ + mode: "balanced", + contextWindow: 200_000, + }); + let pendingResumeInjection = false; + + const syncContextWindow = (ctx: Pick) => { + runtime.setContextWindow(ctx.model?.contextWindow ?? 200_000); + }; + + const armResumeInjection = () => { + const snapshot = runtime.getSnapshot(); + pendingResumeInjection = Boolean(snapshot.lastCompactionSummary || snapshot.lastBranchSummary) && runtime.buildResumePacket().trim().length > 0; + }; + + const replayBranchEntry = (entry: BranchEntry) => { + if (isMessageEntry(entry) && isTrackedMessage(entry.message)) { + runtime.ingest({ + entryId: entry.id, + role: entry.message.role, + text: toText(entry.message.content), + timestamp: entry.message.timestamp, + isError: entry.message.role === "toolResult" ? entry.message.isError : undefined, + }); + return; + } + + if (isCompactionEntry(entry)) { + runtime.recordCompactionSummary(entry.summary, entry.id, Date.parse(entry.timestamp)); + return; + } + + if (isBranchSummaryEntry(entry)) { + runtime.recordBranchSummary(entry.summary, entry.id, Date.parse(entry.timestamp)); + } + }; + + const rebuildRuntimeFromBranch = ( + ctx: Pick, + fallbackSnapshot: RuntimeSnapshot, + options?: { preferRuntimeMode?: boolean }, + ) => { + syncContextWindow(ctx); + + const branch = ctx.sessionManager.getBranch(); + const latestSessionSnapshot = findLatestSessionSnapshot(ctx.sessionManager.getEntries() as BranchEntry[]); + const restored = findLatestSnapshotState(branch); + const baseSnapshot = restored + ? overlaySessionLayer(restored.snapshot, latestSessionSnapshot) + : createSessionFallbackSnapshot(latestSessionSnapshot ?? fallbackSnapshot); + + runtime.restore({ + ...baseSnapshot, + mode: options?.preferRuntimeMode ? fallbackSnapshot.mode : baseSnapshot.mode, + }); + + const replayEntries = restored ? branch.slice(restored.index + 1) : branch; + for (const entry of replayEntries) { + replayBranchEntry(entry); + } + + const snapshot = runtime.getSnapshot(); + ctx.ui.setStatus("context-manager", `ctx ${snapshot.lastZone}`); + }; + + registerContextCommands(pi, { + getSnapshot: runtime.getSnapshot, + buildPacket: runtime.buildPacket, + buildResumePacket: runtime.buildResumePacket, + setMode: runtime.setMode, + rebuildFromBranch: async (commandCtx) => { + rebuildRuntimeFromBranch(commandCtx, runtime.getSnapshot(), { preferRuntimeMode: true }); + armResumeInjection(); + }, + isResumePending: () => pendingResumeInjection, + }); + + pi.on("session_start", async (_event, ctx) => { + rebuildRuntimeFromBranch(ctx, createDefaultSnapshot()); + armResumeInjection(); + }); + + pi.on("session_tree", async (event, ctx) => { + rebuildRuntimeFromBranch(ctx, createDefaultSnapshot()); + + if ( + event.summaryEntry && + !ctx.sessionManager.getBranch().some((entry) => isBranchSummaryEntry(entry) && entry.id === event.summaryEntry.id) + ) { + runtime.recordBranchSummary(event.summaryEntry.summary, event.summaryEntry.id, Date.parse(event.summaryEntry.timestamp)); + } + + armResumeInjection(); + + if (event.summaryEntry) { + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + } + }); + + pi.on("tool_result", async (event) => { + runtime.ingest({ + entryId: event.toolCallId, + role: "toolResult", + text: toText(event.content), + timestamp: Date.now(), + }); + }); + + pi.on("turn_end", async (_event, ctx) => { + rebuildRuntimeFromBranch(ctx, runtime.getSnapshot(), { preferRuntimeMode: true }); + + const usage = ctx.getContextUsage(); + if (usage?.tokens !== null && usage?.tokens !== undefined) { + runtime.observeTokens(usage.tokens); + } + + const snapshot = runtime.getSnapshot(); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(snapshot)); + ctx.ui.setStatus("context-manager", `ctx ${snapshot.lastZone}`); + }); + + pi.on("context", async (event, ctx) => { + syncContextWindow(ctx); + const snapshot = runtime.getSnapshot(); + const policy = adjustPolicyForZone(runtime.getPolicy(), snapshot.lastZone); + const normalized = event.messages.map((message) => ({ + role: message.role, + content: getMessageContent(message), + toolName: getMessageToolName(message), + original: message, + })); + + const pruned = pruneContextMessages(normalized, policy); + const nextMessages = pruned.map((message) => + rewriteContextMessage(message as { role: string; content: string; original: AgentMessage; distilled?: boolean }), + ); + const resumeText = pendingResumeInjection ? runtime.buildResumePacket() : ""; + const packetText = pendingResumeInjection ? "" : runtime.buildPacket().text; + const injectedText = resumeText || packetText; + + if (!injectedText) { + return { messages: nextMessages }; + } + + if (resumeText) { + pendingResumeInjection = false; + } + + return { + messages: [ + { + role: "custom", + customType: resumeText ? "context-manager.resume" : "context-manager.packet", + content: injectedText, + display: false, + timestamp: Date.now(), + } as any, + ...nextMessages, + ], + }; + }); + + pi.on("session_before_compact", async (event, ctx) => { + syncContextWindow(ctx); + + try { + return { + compaction: { + summary: buildCompactionSummaryFromPreparation({ + messagesToSummarize: event.preparation.messagesToSummarize, + turnPrefixMessages: event.preparation.turnPrefixMessages, + previousSummary: event.preparation.previousSummary, + fileOps: event.preparation.fileOps, + customInstructions: event.customInstructions, + }), + firstKeptEntryId: event.preparation.firstKeptEntryId, + tokensBefore: event.preparation.tokensBefore, + details: event.preparation.fileOps, + }, + }; + } catch (error) { + ctx.ui.notify(`context-manager compaction fallback: ${error instanceof Error ? error.message : String(error)}`, "warning"); + return; + } + }); + + pi.on("session_before_tree", async (event, ctx) => { + syncContextWindow(ctx); + if (!event.preparation.userWantsSummary) return; + return { + summary: { + summary: buildBranchSummaryFromEntries({ + branchLabel: "branch handoff", + entriesToSummarize: event.preparation.entriesToSummarize, + customInstructions: event.preparation.customInstructions, + replaceInstructions: event.preparation.replaceInstructions, + commonAncestorId: event.preparation.commonAncestorId, + }), + }, + }; + }); + + pi.on("session_compact", async (event, ctx) => { + runtime.recordCompactionSummary(event.compactionEntry.summary, event.compactionEntry.id, Date.parse(event.compactionEntry.timestamp)); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + armResumeInjection(); + ctx.ui.setStatus("context-manager", `ctx ${runtime.getSnapshot().lastZone}`); + }); +} diff --git a/.pi/agent/extensions/context-manager/package-lock.json b/.pi/agent/extensions/context-manager/package-lock.json new file mode 100644 index 0000000..d085b00 --- /dev/null +++ b/.pi/agent/extensions/context-manager/package-lock.json @@ -0,0 +1,4361 @@ +{ + "name": "pi-context-manager-extension", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-context-manager-extension", + "version": "0.0.0", + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-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.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1027.0.tgz", + "integrity": "sha512-Qcda5Z5Vb3LPVt7zNycEiiAo9Blk0JpEPJwz/sUBJby6/0zvTlo+/FIXlwYZ3TJHSgKCYiCaBqAB0WRlWDfLfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/eventstream-handler-node": "^3.972.13", + "@aws-sdk/middleware-eventstream": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/token-providers": "3.1027.0", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", + "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", + "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", + "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-format-url": "^3.972.9", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1027.0.tgz", + "integrity": "sha512-mI3Jm14cM5sNKc7aNX3cqJe/rFQ2Zzx7x5W8WUtxj2lVxcH2RGYhqI3hK9nnImY6Ec5MeGXCVPjl/q6Mz5HmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", + "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.49.0.tgz", + "integrity": "sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", + "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/jiti": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", + "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "std-env": "^3.10.0", + "yoctocolors": "^2.1.2" + }, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@mariozechner/pi-agent-core": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.66.1.tgz", + "integrity": "sha512-Nj54A7SuB/EQi8r3Gs+glFOr9wz/a9uxYFf0pCLf2DE7VmzA9O7WSejrvArna17K6auftLSdNyRRe2bIO0qezg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/pi-ai": "^0.66.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.66.1.tgz", + "integrity": "sha512-7IZHvpsFdKEBkTmjNrdVL7JLUJVIpha6bwTr12cZ5XyDrxij06wP6Ncpnf4HT5BXAzD5w2JnoqTOSbMEIZj3dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "1.14.1", + "@sinclair/typebox": "^0.34.41", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-coding-agent": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.66.1.tgz", + "integrity": "sha512-cNmatT+5HvYzQ78cRhRih00wCeUTH/fFx9ecJh5AbN7axgWU+bwiZYy0cjrTsGVgMGF4xMYlPRn/Nze9JEB+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/jiti": "^2.6.2", + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@silvia-odwyer/photon-node": "^0.3.4", + "ajv": "^8.17.1", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "undici": "^7.19.1", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.2" + } + }, + "node_modules/@mariozechner/pi-tui": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.66.1.tgz", + "integrity": "sha512-hNFN42ebjwtfGooqoUwM+QaPR1XCyqPuueuP3aLOWS1bZ2nZP/jq8MBuGNrmMw1cgiDcotvOlSNj3BatzEOGsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", + "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "dev": true, + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.6.tgz", + "integrity": "sha512-WQBpM5uo74UQ17UpsFN+PUOrQQg4/nYdey4SGVluQun2drYYfePziLLWdSmFb4wSdWlJC1aimXQnjhPCheRKuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", + "integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "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 + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/.pi/agent/extensions/context-manager/package.json b/.pi/agent/extensions/context-manager/package.json new file mode 100644 index 0000000..5693d8d --- /dev/null +++ b/.pi/agent/extensions/context-manager/package.json @@ -0,0 +1,18 @@ +{ + "name": "pi-context-manager-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test": "tsx --test src/*.test.ts src/**/*.test.ts" + }, + "pi": { + "extensions": ["./index.ts"] + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } +} diff --git a/.pi/agent/extensions/context-manager/src/commands.ts b/.pi/agent/extensions/context-manager/src/commands.ts new file mode 100644 index 0000000..23b7092 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/commands.ts @@ -0,0 +1,76 @@ +import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import type { ContextMode } from "./config.ts"; +import { serializeSnapshot, SNAPSHOT_ENTRY_TYPE, type RuntimeSnapshot } from "./persist.ts"; + +interface CommandRuntime { + getSnapshot(): RuntimeSnapshot; + buildPacket(): { estimatedTokens: number }; + buildResumePacket(): string; + setMode(mode: ContextMode): void; + rebuildFromBranch(ctx: ExtensionCommandContext): Promise; + isResumePending(): boolean; +} + +export function registerContextCommands(pi: ExtensionAPI, runtime: CommandRuntime) { + pi.registerCommand("ctx-status", { + description: "Show context pressure, packet status, and persisted handoff state", + handler: async (_args, ctx) => { + const snapshot = runtime.getSnapshot(); + const packet = runtime.buildPacket(); + const resumePending = runtime.isResumePending(); + const contextTokens = ctx.getContextUsage()?.tokens; + const nextInjectionTokens = resumePending ? Math.ceil(runtime.buildResumePacket().length / 4) : packet.estimatedTokens; + ctx.ui.notify( + [ + `mode=${snapshot.mode}`, + `zone=${snapshot.lastZone}`, + `contextTokens=${contextTokens ?? "unknown"}`, + `packetTokens=${packet.estimatedTokens}`, + `nextInjectionTokens=${nextInjectionTokens}`, + `resumePending=${resumePending ? "yes" : "no"}`, + `compaction=${snapshot.lastCompactionSummary ? "yes" : "no"}`, + `branch=${snapshot.lastBranchSummary ? "yes" : "no"}`, + ].join(" "), + "info", + ); + }, + }); + + pi.registerCommand("ctx-memory", { + description: "Inspect the active context ledger", + handler: async (_args, ctx) => { + const snapshot = runtime.getSnapshot(); + await ctx.ui.editor("Context ledger", JSON.stringify(snapshot.ledger, null, 2)); + }, + }); + + pi.registerCommand("ctx-refresh", { + description: "Rebuild runtime state from the current branch and refresh the working packet", + handler: async (_args, ctx) => { + await runtime.rebuildFromBranch(ctx); + const packet = runtime.buildPacket(); + ctx.ui.notify(`rebuilt runtime from branch (${packet.estimatedTokens} tokens)`, "info"); + }, + }); + + pi.registerCommand("ctx-compact", { + description: "Trigger compaction with optional focus instructions", + handler: async (args, ctx) => { + ctx.compact({ customInstructions: args.trim() || undefined }); + }, + }); + + pi.registerCommand("ctx-mode", { + description: "Switch context mode: conservative | balanced | aggressive", + handler: async (args, ctx) => { + const value = args.trim() as "conservative" | "balanced" | "aggressive"; + if (!["conservative", "balanced", "aggressive"].includes(value)) { + ctx.ui.notify("usage: /ctx-mode conservative|balanced|aggressive", "warning"); + return; + } + runtime.setMode(value); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + ctx.ui.notify(`context mode set to ${value}`, "info"); + }, + }); +} diff --git a/.pi/agent/extensions/context-manager/src/config.test.ts b/.pi/agent/extensions/context-manager/src/config.test.ts new file mode 100644 index 0000000..7d212f1 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/config.test.ts @@ -0,0 +1,86 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { adjustPolicyForZone, resolvePolicy, zoneForTokens } from "./config.ts"; + +test("resolvePolicy returns the balanced policy for a 200k context window", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.deepEqual(policy, { + mode: "balanced", + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + yellowAtTokens: 110_000, + redAtTokens: 140_000, + compactAtTokens: 164_000, + }); +}); + +test("resolvePolicy clamps context windows below 50k before calculating thresholds", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 10_000 }); + + assert.deepEqual(policy, { + mode: "balanced", + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + yellowAtTokens: 27_500, + redAtTokens: 35_000, + compactAtTokens: 41_000, + }); +}); + +test("aggressive mode compacts earlier than conservative mode", () => { + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + + assert.ok(aggressive.compactAtTokens < conservative.compactAtTokens); +}); + +test("aggressive mode reduces raw-window and packet budgets compared with conservative mode", () => { + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + + assert.ok(aggressive.recentUserTurns < conservative.recentUserTurns); + assert.ok(aggressive.packetTokenCap < conservative.packetTokenCap); + assert.ok(aggressive.bulkyBytes < conservative.bulkyBytes); + assert.ok(aggressive.bulkyLines < conservative.bulkyLines); +}); + + test("adjustPolicyForZone tightens packet and pruning thresholds in yellow, red, and compact zones", () => { + const base = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + const yellow = adjustPolicyForZone(base, "yellow"); + const red = adjustPolicyForZone(base, "red"); + const compact = adjustPolicyForZone(base, "compact"); + + assert.ok(yellow.packetTokenCap < base.packetTokenCap); + assert.ok(yellow.bulkyBytes < base.bulkyBytes); + assert.ok(red.packetTokenCap < yellow.packetTokenCap); + assert.ok(red.recentUserTurns <= yellow.recentUserTurns); + assert.ok(red.bulkyBytes < yellow.bulkyBytes); + assert.ok(compact.packetTokenCap < red.packetTokenCap); + assert.ok(compact.recentUserTurns <= red.recentUserTurns); + assert.ok(compact.bulkyLines < red.bulkyLines); +}); + +test("zoneForTokens returns green, yellow, red, and compact for the balanced 200k policy", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.equal(zoneForTokens(80_000, policy), "green"); + assert.equal(zoneForTokens(120_000, policy), "yellow"); + assert.equal(zoneForTokens(150_000, policy), "red"); + assert.equal(zoneForTokens(170_000, policy), "compact"); +}); + +test("zoneForTokens uses inclusive balanced 200k thresholds", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.equal(zoneForTokens(109_999, policy), "green"); + assert.equal(zoneForTokens(110_000, policy), "yellow"); + assert.equal(zoneForTokens(139_999, policy), "yellow"); + assert.equal(zoneForTokens(140_000, policy), "red"); + assert.equal(zoneForTokens(163_999, policy), "red"); + assert.equal(zoneForTokens(164_000, policy), "compact"); +}); diff --git a/.pi/agent/extensions/context-manager/src/config.ts b/.pi/agent/extensions/context-manager/src/config.ts new file mode 100644 index 0000000..7c6a662 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/config.ts @@ -0,0 +1,97 @@ +export type ContextMode = "conservative" | "balanced" | "aggressive"; +export type ContextZone = "green" | "yellow" | "red" | "compact"; + +export interface Policy { + mode: ContextMode; + recentUserTurns: number; + packetTokenCap: number; + bulkyBytes: number; + bulkyLines: number; + yellowAtTokens: number; + redAtTokens: number; + compactAtTokens: number; +} + +export const MODE_PCTS: Record = { + conservative: { yellow: 0.60, red: 0.76, compact: 0.88 }, + balanced: { yellow: 0.55, red: 0.70, compact: 0.82 }, + aggressive: { yellow: 0.50, red: 0.64, compact: 0.76 }, +}; + +const MODE_SETTINGS: Record> = { + conservative: { + recentUserTurns: 5, + packetTokenCap: 1_400, + bulkyBytes: 6_144, + bulkyLines: 220, + }, + balanced: { + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + }, + aggressive: { + recentUserTurns: 3, + packetTokenCap: 900, + bulkyBytes: 3_072, + bulkyLines: 100, + }, +}; + +export function resolvePolicy(input: { mode: ContextMode; contextWindow: number }): Policy { + const contextWindow = Math.max(input.contextWindow, 50_000); + const percentages = MODE_PCTS[input.mode]; + const settings = MODE_SETTINGS[input.mode]; + + return { + mode: input.mode, + recentUserTurns: settings.recentUserTurns, + packetTokenCap: settings.packetTokenCap, + bulkyBytes: settings.bulkyBytes, + bulkyLines: settings.bulkyLines, + yellowAtTokens: Math.floor(contextWindow * percentages.yellow), + redAtTokens: Math.floor(contextWindow * percentages.red), + compactAtTokens: Math.floor(contextWindow * percentages.compact), + }; +} + +export function adjustPolicyForZone(policy: Policy, zone: ContextZone): Policy { + if (zone === "green") { + return { ...policy }; + } + + if (zone === "yellow") { + return { + ...policy, + packetTokenCap: Math.max(500, Math.floor(policy.packetTokenCap * 0.9)), + bulkyBytes: Math.max(1_536, Math.floor(policy.bulkyBytes * 0.9)), + bulkyLines: Math.max(80, Math.floor(policy.bulkyLines * 0.9)), + }; + } + + if (zone === "red") { + return { + ...policy, + recentUserTurns: Math.max(2, policy.recentUserTurns - 1), + packetTokenCap: Math.max(400, Math.floor(policy.packetTokenCap * 0.75)), + bulkyBytes: Math.max(1_024, Math.floor(policy.bulkyBytes * 0.75)), + bulkyLines: Math.max(60, Math.floor(policy.bulkyLines * 0.75)), + }; + } + + return { + ...policy, + recentUserTurns: Math.max(1, policy.recentUserTurns - 2), + packetTokenCap: Math.max(300, Math.floor(policy.packetTokenCap * 0.55)), + bulkyBytes: Math.max(768, Math.floor(policy.bulkyBytes * 0.5)), + bulkyLines: Math.max(40, Math.floor(policy.bulkyLines * 0.5)), + }; +} + +export function zoneForTokens(tokens: number, policy: Policy): ContextZone { + if (tokens >= policy.compactAtTokens) return "compact"; + if (tokens >= policy.redAtTokens) return "red"; + if (tokens >= policy.yellowAtTokens) return "yellow"; + return "green"; +} diff --git a/.pi/agent/extensions/context-manager/src/distill.test.ts b/.pi/agent/extensions/context-manager/src/distill.test.ts new file mode 100644 index 0000000..fab5d8e --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/distill.test.ts @@ -0,0 +1,30 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { distillToolResult } from "./distill.ts"; + +const noisy = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + "at src/summaries.ts:44:12", + "line filler", + "line filler", + "line filler", +].join("\n"); + +test("distillToolResult prioritizes salient failure lines and truncates noise", () => { + const distilled = distillToolResult({ toolName: "bash", content: noisy }); + + assert.ok(distilled); + assert.match(distilled!, /Build failed while compiling focus parser/); + assert.match(distilled!, /missing export createFocusMatcher/); + assert.ok(distilled!.length < 320); +}); + +test("distillToolResult falls back to the first meaningful non-empty lines", () => { + const distilled = distillToolResult({ + toolName: "read", + content: ["", "src/runtime.ts", "exports createContextManagerRuntime", "", "more noise"].join("\n"), + }); + + assert.equal(distilled, "[distilled read output] src/runtime.ts; exports createContextManagerRuntime"); +}); diff --git a/.pi/agent/extensions/context-manager/src/distill.ts b/.pi/agent/extensions/context-manager/src/distill.ts new file mode 100644 index 0000000..632efb3 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/distill.ts @@ -0,0 +1,47 @@ +const ERROR_RE = /\b(?:error|failed|failure|missing|undefined|exception)\b/i; +const LOCATION_RE = /\b(?:at\s+.+:\d+(?::\d+)?)\b|(?:[A-Za-z0-9_./-]+\.(?:ts|tsx|js|mjs|json|md):\d+(?::\d+)?)/i; +const MAX_SUMMARY_LENGTH = 320; +const MAX_LINES = 2; + +function unique(lines: string[]): string[] { + return lines.filter((line, index) => lines.indexOf(line) === index); +} + +function pickSalientLines(content: string): string[] { + const lines = content + .split(/\n+/) + .map((line) => line.trim()) + .filter(Boolean); + + if (lines.length === 0) { + return []; + } + + const important = unique(lines.filter((line) => ERROR_RE.test(line))); + const location = unique(lines.filter((line) => LOCATION_RE.test(line))); + const fallback = unique(lines); + + const selected: string[] = []; + for (const line of [...important, ...location, ...fallback]) { + if (selected.includes(line)) { + continue; + } + + selected.push(line); + if (selected.length >= MAX_LINES) { + break; + } + } + + return selected; +} + +export function distillToolResult(input: { toolName?: string; content: string }): string | undefined { + const picked = pickSalientLines(input.content); + if (picked.length === 0) { + return undefined; + } + + const prefix = `[distilled ${input.toolName ?? "tool"} output]`; + return `${prefix} ${picked.join("; ")}`.slice(0, MAX_SUMMARY_LENGTH); +} diff --git a/.pi/agent/extensions/context-manager/src/extension.test.ts b/.pi/agent/extensions/context-manager/src/extension.test.ts new file mode 100644 index 0000000..3ed6437 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/extension.test.ts @@ -0,0 +1,833 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import contextManagerExtension from "../index.ts"; +import { deserializeLatestSnapshot, SNAPSHOT_ENTRY_TYPE, serializeSnapshot, type RuntimeSnapshot } from "./persist.ts"; + +type EventHandler = (event: any, ctx: any) => Promise | any; +type RegisteredCommand = { description: string; handler: (args: string, ctx: any) => Promise | void }; + +type SessionEntry = + | { + type: "message"; + id: string; + parentId: string | null; + timestamp: string; + message: any; + } + | { + type: "custom"; + id: string; + parentId: string | null; + timestamp: string; + customType: string; + data: unknown; + } + | { + type: "compaction"; + id: string; + parentId: string | null; + timestamp: string; + summary: string; + firstKeptEntryId: string; + tokensBefore: number; + } + | { + type: "branch_summary"; + id: string; + parentId: string | null; + timestamp: string; + fromId: string; + summary: string; + }; + +function createUsage(tokens: number) { + return { + tokens, + contextWindow: 200_000, + percent: tokens / 200_000, + }; +} + +function createUserMessage(content: string, timestamp: number) { + return { + role: "user", + content, + timestamp, + }; +} + +function createAssistantMessage(content: string, timestamp: number) { + return { + role: "assistant", + content: [{ type: "text", text: content }], + api: "openai-responses", + provider: "openai", + model: "gpt-5", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp, + }; +} + +function createToolResultMessage(content: string, timestamp: number) { + return { + role: "toolResult", + toolCallId: `tool-${timestamp}`, + toolName: "read", + content: [{ type: "text", text: content }], + isError: false, + timestamp, + }; +} + +function createMessageEntry(id: string, parentId: string | null, message: any): SessionEntry { + return { + type: "message", + id, + parentId, + timestamp: new Date(message.timestamp).toISOString(), + message, + }; +} + +function createSnapshotEntry( + id: string, + parentId: string | null, + options: { + text: string; + mode?: RuntimeSnapshot["mode"]; + lastZone?: RuntimeSnapshot["lastZone"]; + lastObservedTokens?: number; + lastCompactionSummary?: string; + lastBranchSummary?: string; + ledgerItems?: RuntimeSnapshot["ledger"]["items"]; + rollingSummary?: string; + }, +): SessionEntry { + const { + text, + mode = "aggressive", + lastZone = "red", + lastObservedTokens = 150_000, + lastCompactionSummary = "existing compaction summary", + lastBranchSummary = "existing branch summary", + ledgerItems, + rollingSummary = "stale ledger", + } = options; + + return { + type: "custom", + id, + parentId, + timestamp: new Date(1).toISOString(), + customType: SNAPSHOT_ENTRY_TYPE, + data: serializeSnapshot({ + mode, + lastZone, + lastObservedTokens, + lastCompactionSummary, + lastBranchSummary, + ledger: { + items: ledgerItems ?? [ + { + id: `goal:session:root-goal:${id}`, + kind: "goal", + subject: "root-goal", + text, + scope: "session", + sourceEntryId: "old-user", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary, + }, + }), + }; +} + +function createHarness(initialBranch: SessionEntry[], options?: { usageTokens?: number }) { + const commands = new Map(); + const handlers = new Map(); + const appendedEntries: Array<{ customType: string; data: unknown }> = []; + const statuses: Array<{ key: string; value: string }> = []; + let branch = [...initialBranch]; + let entries = [...initialBranch]; + + const ctx = { + model: { contextWindow: 200_000 }, + sessionManager: { + getBranch() { + return branch; + }, + getEntries() { + return entries; + }, + }, + ui: { + setStatus(key: string, value: string) { + statuses.push({ key, value }); + }, + notify() {}, + editor: async () => {}, + }, + getContextUsage() { + return options?.usageTokens === undefined ? undefined : createUsage(options.usageTokens); + }, + compact() {}, + }; + + contextManagerExtension({ + registerCommand(name: string, command: RegisteredCommand) { + commands.set(name, command); + }, + on(name: string, handler: EventHandler) { + handlers.set(name, handler); + }, + appendEntry(customType: string, data: unknown) { + appendedEntries.push({ customType, data }); + const entry = { + type: "custom" as const, + id: `custom-${appendedEntries.length}`, + parentId: branch.at(-1)?.id ?? null, + timestamp: new Date(10_000 + appendedEntries.length).toISOString(), + customType, + data, + }; + branch.push(entry); + entries.push(entry); + }, + } as any); + + return { + commands, + handlers, + appendedEntries, + statuses, + ctx, + setBranch(nextBranch: SessionEntry[]) { + branch = [...nextBranch]; + const byId = new Map(entries.map((entry) => [entry.id, entry])); + for (const entry of nextBranch) { + byId.set(entry.id, entry); + } + entries = [...byId.values()]; + }, + }; +} + +test("the extension registers the expected hooks and commands", () => { + const harness = createHarness([]); + + assert.deepEqual([...harness.commands.keys()].sort(), ["ctx-compact", "ctx-memory", "ctx-mode", "ctx-refresh", "ctx-status"]); + assert.deepEqual( + [...harness.handlers.keys()].sort(), + ["context", "session_before_compact", "session_before_tree", "session_compact", "session_start", "session_tree", "tool_result", "turn_end"], + ); +}); + +test("turn_end persists a rebuilt snapshot that includes branch user and assistant facts", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { text: "Stale snapshot fact" }), + createMessageEntry("user-1", "snapshot-1", createUserMessage("Goal: Fix Task 6\nPrefer keeping the public API stable", 2)), + createMessageEntry( + "assistant-1", + "user-1", + createAssistantMessage("Decision: rebuild from ctx.sessionManager.getBranch()\nNext: add integration tests", 3), + ), + createMessageEntry( + "tool-1", + "assistant-1", + createToolResultMessage("Opened .pi/agent/extensions/context-manager/index.ts", 4), + ), + ]; + + const harness = createHarness(branch, { usageTokens: 120_000 }); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("turn_end")?.( + { + type: "turn_end", + turnIndex: 1, + message: createAssistantMessage("done", 5), + toolResults: [], + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.length, 1); + assert.equal(harness.appendedEntries[0]?.customType, SNAPSHOT_ENTRY_TYPE); + + const snapshot = harness.appendedEntries[0]!.data as any; + const activeTexts = snapshot.ledger.items.filter((item: any) => item.active).map((item: any) => item.text); + + assert.equal(snapshot.mode, "aggressive"); + assert.equal(snapshot.lastCompactionSummary, "existing compaction summary"); + assert.equal(snapshot.lastBranchSummary, "existing branch summary"); + assert.equal(snapshot.lastObservedTokens, 120_000); + assert.equal(snapshot.lastZone, "yellow"); + assert.deepEqual(activeTexts, ["Stale snapshot fact", "Fix Task 6", "Prefer keeping the public API stable", "rebuild from ctx.sessionManager.getBranch()", "add integration tests", ".pi/agent/extensions/context-manager/index.ts"]); + assert.deepEqual(harness.statuses.at(-1), { key: "context-manager", value: "ctx yellow" }); +}); + +test("session_tree rebuilds runtime from snapshot-only branches before injecting the next packet", async () => { + const oldBranch: SessionEntry[] = [createSnapshotEntry("snapshot-old", null, { text: "Old branch goal" })]; + const newBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-new", null, { + text: "Snapshot-only branch goal", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-new", + kind: "goal", + subject: "root-goal", + text: "Snapshot-only branch goal", + scope: "session", + sourceEntryId: "snapshot-new", + sourceType: "user", + timestamp: 11, + confidence: 1, + freshness: 11, + active: true, + supersedesId: undefined, + }, + { + id: "decision:branch:branch-decision:snapshot-new", + kind: "decision", + subject: "branch-decision", + text: "Use the snapshot-backed branch state immediately", + scope: "branch", + sourceEntryId: "snapshot-new", + sourceType: "assistant", + timestamp: 12, + confidence: 0.9, + freshness: 12, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary: "snapshot-only branch state", + }), + ]; + + const harness = createHarness(oldBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch(newBranch); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-old", + newLeafId: "snapshot-new", + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { + type: "context", + messages: [createUserMessage("What should happen next?", 13)], + }, + harness.ctx, + ); + + assert.ok(result); + assert.equal(result.messages[0]?.role, "custom"); + assert.equal(result.messages[0]?.customType, "context-manager.resume"); + assert.match(result.messages[0]?.content, /Snapshot-only branch goal/); + assert.match(result.messages[0]?.content, /Use the snapshot-backed branch state immediately/); + assert.doesNotMatch(result.messages[0]?.content, /Old branch goal/); + assert.deepEqual(harness.statuses.at(-1), { key: "context-manager", value: "ctx red" }); +}); + +test("context keeps a distilled stale tool result visible after pruning bulky output", async () => { + const bulkyFailure = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + ...Array.from({ length: 220 }, () => "stack frame"), + ].join("\n"); + + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("context")?.( + { + type: "context", + messages: [ + createUserMessage("turn 1", 1), + createToolResultMessage(bulkyFailure, 2), + createAssistantMessage("observed turn 1", 3), + createUserMessage("turn 2", 4), + createAssistantMessage("observed turn 2", 5), + createUserMessage("turn 3", 6), + createAssistantMessage("observed turn 3", 7), + createUserMessage("turn 4", 8), + createAssistantMessage("observed turn 4", 9), + createUserMessage("turn 5", 10), + ], + }, + harness.ctx, + ); + + const toolResult = result.messages.find((message: any) => message.role === "toolResult"); + assert.ok(toolResult); + assert.match(toolResult.content[0].text, /missing export createFocusMatcher/); + assert.ok(toolResult.content[0].text.length < 320); +}); + +test("session_tree preserves session-scoped facts but drops stale branch handoff metadata on an empty destination branch", async () => { + const sourceBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-session", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const harness = createHarness(sourceBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([]); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-session", + newLeafId: null, + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 30)] }, + harness.ctx, + ); + + assert.equal(result.messages[0]?.customType, "context-manager.packet"); + assert.match(result.messages[0]?.content, /Ship the context manager extension/); + assert.doesNotMatch(result.messages[0]?.content, /Do not leak branch-local goals/); + assert.doesNotMatch(result.messages[0]?.content, /Keep summaries deterministic/); +}); + +test("session_tree overlays newer session-scoped facts onto a destination branch with an older snapshot", async () => { + const newerSessionSnapshot = createSnapshotEntry("snapshot-newer", null, { + text: "Ship the context manager extension", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-newer", + kind: "goal", + subject: "root-goal", + text: "Ship the context manager extension", + scope: "session", + sourceEntryId: "snapshot-newer", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + { + id: "constraint:session:must-session-newer:2", + kind: "constraint", + subject: "must-session-newer", + text: "Prefer concise reports across the whole session.", + scope: "session", + sourceEntryId: "snapshot-newer", + sourceType: "user", + timestamp: 2, + confidence: 0.9, + freshness: 2, + active: true, + supersedesId: undefined, + }, + ], + lastCompactionSummary: "", + lastBranchSummary: "", + }); + const olderBranchSnapshot = createSnapshotEntry("snapshot-older", null, { + text: "Ship the context manager extension", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-older", + kind: "goal", + subject: "root-goal", + text: "Ship the context manager extension", + scope: "session", + sourceEntryId: "snapshot-older", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + lastCompactionSummary: "", + lastBranchSummary: "", + }); + + const harness = createHarness([newerSessionSnapshot]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([olderBranchSnapshot]); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-newer", + newLeafId: "snapshot-older", + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 32)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /Prefer concise reports across the whole session/); +}); + +test("ctx-refresh preserves session memory without leaking old handoff summaries on a snapshot-less branch", async () => { + const sourceBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-refresh", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const harness = createHarness(sourceBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([]); + await harness.commands.get("ctx-refresh")?.handler("", harness.ctx); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 31)] }, + harness.ctx, + ); + + assert.equal(result.messages[0]?.customType, "context-manager.packet"); + assert.match(result.messages[0]?.content, /Ship the context manager extension/); + assert.doesNotMatch(result.messages[0]?.content, /Do not leak branch-local goals/); + assert.doesNotMatch(result.messages[0]?.content, /Keep summaries deterministic/); +}); + +test("session_start replays default pi compaction blockers into resume state", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-default", null, { + text: "Ship the context manager extension", + lastCompactionSummary: undefined, + lastBranchSummary: undefined, + }), + { + type: "compaction", + id: "compaction-default-1", + parentId: "snapshot-default", + timestamp: new Date(40).toISOString(), + summary: [ + "## Progress", + "### Blocked", + "- confirm whether /tree replaceInstructions should override defaults", + ].join("\n"), + firstKeptEntryId: "snapshot-default", + tokensBefore: 123_000, + }, + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 41)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /confirm whether \/tree replaceInstructions should override defaults/); +}); + +test("session_before_compact honors preparation inputs and custom focus", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("session_before_compact")?.( + { + type: "session_before_compact", + customInstructions: "Focus on decisions and relevant files.", + preparation: { + messagesToSummarize: [createUserMessage("Decision: keep compaction summaries deterministic", 1)], + turnPrefixMessages: [createToolResultMessage("Opened .pi/agent/extensions/context-manager/index.ts", 2)], + previousSummary: "## Goal\n- Ship the context manager extension", + fileOps: { + readFiles: [".pi/agent/extensions/context-manager/index.ts"], + modifiedFiles: [".pi/agent/extensions/context-manager/src/summaries.ts"], + }, + tokensBefore: 120_000, + firstKeptEntryId: "keep-1", + }, + branchEntries: [], + signal: AbortSignal.timeout(1_000), + }, + harness.ctx, + ); + + assert.equal(result.compaction.firstKeptEntryId, "keep-1"); + assert.equal(result.compaction.tokensBefore, 120_000); + assert.match(result.compaction.summary, /keep compaction summaries deterministic/); + assert.match(result.compaction.summary, /index.ts/); +}); + +test("session_before_tree honors abandoned-branch entries and focus text", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("session_before_tree")?.( + { + type: "session_before_tree", + preparation: { + targetId: "target-1", + oldLeafId: "old-1", + commonAncestorId: "root", + userWantsSummary: true, + customInstructions: "Focus on goals and decisions.", + replaceInstructions: false, + entriesToSummarize: [ + createMessageEntry("user-1", null, createUserMessage("Goal: explore tree handoff", 1)), + createMessageEntry("assistant-1", "user-1", createAssistantMessage("Decision: do not leak branch-local goals", 2)), + ], + }, + signal: AbortSignal.timeout(1_000), + }, + harness.ctx, + ); + + assert.ok(result?.summary?.summary); + assert.match(result.summary.summary, /explore tree handoff/); + assert.match(result.summary.summary, /do not leak branch-local goals/); +}); + +test("session_compact persists the latest compaction summary into a fresh snapshot and injects a resume packet once", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("session_compact")?.( + { + type: "session_compact", + fromExtension: true, + compactionEntry: { + type: "compaction", + id: "cmp-1", + parentId: "prev", + timestamp: new Date(10).toISOString(), + summary: "## Key Decisions\n- Keep summaries deterministic.\n\n## Open questions and blockers\n- Verify /tree replaceInstructions behavior.", + firstKeptEntryId: "keep-1", + tokensBefore: 140_000, + }, + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.at(-1)?.customType, SNAPSHOT_ENTRY_TYPE); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 11)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Keep summaries deterministic/); + assert.match(context.messages[0]?.content, /Verify \/tree replaceInstructions behavior/); + + const nextContext = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue again", 12)] }, + harness.ctx, + ); + assert.equal(nextContext.messages[0]?.customType, "context-manager.packet"); + assert.doesNotMatch(nextContext.messages[0]?.content ?? "", /## Latest compaction handoff/); +}); + +test("session_tree replays branch summaries newer than the latest snapshot before the next packet is injected", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { text: "Ship the context manager extension" }), + { + type: "branch_summary", + id: "branch-summary-1", + parentId: "snapshot-1", + timestamp: new Date(20).toISOString(), + fromId: "old-leaf", + summary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }, + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("what next", 21)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Do not leak branch-local goals/); +}); + + +test("session_tree records event summaryEntry before persisting the next snapshot", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Ship the context manager extension", + lastCompactionSummary: "", + lastBranchSummary: "", + }), + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + fromExtension: true, + summaryEntry: { + type: "branch_summary", + id: "branch-summary-event", + parentId: "snapshot-1", + timestamp: new Date(20).toISOString(), + fromId: "old-leaf", + summary: "# Handoff for branch\n\n## Key Decisions\n- Preserve the latest branch summary from the event payload.", + }, + }, + harness.ctx, + ); + + const snapshot = harness.appendedEntries.at(-1)?.data as RuntimeSnapshot | undefined; + assert.match(snapshot?.lastBranchSummary ?? "", /Preserve the latest branch summary/); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("what changed", 21)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Preserve the latest branch summary/); +}); + +test("ctx-status reports mode, zone, packet size, and summary-artifact presence", async () => { + const branch = [ + createSnapshotEntry("snapshot-1", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const notifications: string[] = []; + const harness = createHarness(branch); + harness.ctx.ui.notify = (message: string) => notifications.push(message); + + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + await harness.commands.get("ctx-status")?.handler("", harness.ctx); + + assert.match(notifications.at(-1) ?? "", /mode=balanced/); + assert.match(notifications.at(-1) ?? "", /zone=yellow/); + assert.match(notifications.at(-1) ?? "", /compaction=yes/); + assert.match(notifications.at(-1) ?? "", /branch=yes/); +}); + +test("ctx-refresh rebuilds runtime from the current branch instead of only re-rendering the packet", async () => { + const harness = createHarness([createSnapshotEntry("snapshot-1", null, { text: "Old goal" })]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([ + createSnapshotEntry("snapshot-2", null, { + text: "New branch goal", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Use the new branch immediately.", + }), + ]); + + await harness.commands.get("ctx-refresh")?.handler("", harness.ctx); + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 3)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /New branch goal/); + assert.doesNotMatch(result.messages[0]?.content, /Old goal/); +}); + +test("ctx-mode persists the updated mode immediately without waiting for turn_end", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Persist the updated mode", + mode: "balanced", + lastZone: "green", + lastObservedTokens: 90_000, + }), + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + assert.equal(deserializeLatestSnapshot(harness.ctx.sessionManager.getBranch())?.mode, "balanced"); + + const modeCommand = harness.commands.get("ctx-mode"); + assert.ok(modeCommand); + await modeCommand.handler("aggressive", harness.ctx); + + assert.equal(harness.appendedEntries.length, 1); + assert.equal(harness.appendedEntries[0]?.customType, SNAPSHOT_ENTRY_TYPE); + assert.equal(deserializeLatestSnapshot(harness.ctx.sessionManager.getBranch())?.mode, "aggressive"); +}); + +test("ctx-mode changes survive turn_end and persist into the next snapshot", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Persist the updated mode", + mode: "balanced", + lastZone: "green", + lastObservedTokens: 90_000, + }), + ]; + + const harness = createHarness(branch, { usageTokens: 105_000 }); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const modeCommand = harness.commands.get("ctx-mode"); + assert.ok(modeCommand); + await modeCommand.handler("aggressive", harness.ctx); + + await harness.handlers.get("turn_end")?.( + { + type: "turn_end", + turnIndex: 1, + message: createAssistantMessage("done", 5), + toolResults: [], + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.length, 2); + + const immediateSnapshot = harness.appendedEntries[0]!.data as any; + assert.equal(immediateSnapshot.mode, "aggressive"); + assert.equal(immediateSnapshot.lastObservedTokens, 90_000); + assert.equal(immediateSnapshot.lastZone, "green"); + + const snapshot = harness.appendedEntries[1]!.data as any; + assert.equal(snapshot.mode, "aggressive"); + assert.equal(snapshot.lastObservedTokens, 105_000); + assert.equal(snapshot.lastZone, "yellow"); +}); diff --git a/.pi/agent/extensions/context-manager/src/extract.test.ts b/.pi/agent/extensions/context-manager/src/extract.test.ts new file mode 100644 index 0000000..9c521a7 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/extract.test.ts @@ -0,0 +1,280 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { extractCandidates } from "./extract.ts"; +import { createEmptyLedger, getActiveItems, mergeCandidates } from "./ledger.ts"; + +test("extractCandidates pulls goals, constraints, decisions, next steps, and file references", () => { + const candidates = extractCandidates({ + entryId: "u1", + role: "user", + text: [ + "Goal: Build a context manager extension for pi.", + "We must adapt to the active model context window.", + "Decision: keep the MVP quiet and avoid new LLM-facing tools.", + "Next: inspect .pi/agent/extensions/web-search/index.ts and docs/extensions.md.", + ].join("\n"), + timestamp: 1, + }); + + assert.deepEqual( + candidates.map((candidate) => [ + candidate.kind, + candidate.subject, + candidate.scope, + candidate.sourceEntryId, + candidate.sourceType, + candidate.timestamp, + ]), + [ + ["goal", "root-goal", "session", "u1", "user", 1], + ["constraint", "must-u1-0", "branch", "u1", "user", 1], + ["decision", "decision-u1-0", "branch", "u1", "user", 1], + ["activeTask", "next-step-u1-0", "branch", "u1", "user", 1], + ["relevantFile", ".pi/agent/extensions/web-search/index.ts", "branch", "u1", "user", 1], + ["relevantFile", "docs/extensions.md", "branch", "u1", "user", 1], + ], + ); +}); + +test("extractCandidates promotes only the first durable goal to session scope", () => { + const firstGoal = extractCandidates( + { + entryId: "u-goal-1", + role: "user", + text: "Goal: Ship the context manager extension.", + timestamp: 10, + }, + { hasSessionGoal: false }, + ); + + const branchGoal = extractCandidates( + { + entryId: "u-goal-2", + role: "user", + text: "Goal: prototype a branch-local tree handoff.", + timestamp: 11, + }, + { hasSessionGoal: true }, + ); + + assert.deepEqual( + firstGoal.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "root-goal", "session", "Ship the context manager extension."]], + ); + assert.deepEqual( + branchGoal.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "goal-u-goal-2-0", "branch", "prototype a branch-local tree handoff."]], + ); +}); + +test("mergeCandidates keeps independently extracted decisions, constraints, and next steps active", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + ...extractCandidates({ + entryId: "u1", + role: "user", + text: [ + "We must adapt to the active model context window.", + "Decision: keep snapshots tiny.", + "Next: inspect src/extract.ts.", + ].join("\n"), + timestamp: 1, + }), + ...extractCandidates({ + entryId: "u2", + role: "user", + text: [ + "We prefer concise reports across the whole session.", + "Decision: persist snapshots after each turn_end.", + "Task: add regression coverage.", + ].join("\n"), + timestamp: 2, + }), + ]); + + assert.deepEqual( + getActiveItems(ledger, "constraint").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["must-u1-0", "u1", "We must adapt to the active model context window."], + ["must-u2-0", "u2", "We prefer concise reports across the whole session."], + ], + ); + assert.deepEqual( + getActiveItems(ledger, "decision").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["decision-u1-0", "u1", "keep snapshots tiny."], + ["decision-u2-0", "u2", "persist snapshots after each turn_end."], + ], + ); + assert.deepEqual( + getActiveItems(ledger, "activeTask").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["next-step-u1-0", "u1", "inspect src/extract.ts."], + ["next-step-u2-0", "u2", "add regression coverage."], + ], + ); +}); + +test("user constraints default to branch scope unless they explicitly signal durable session scope", () => { + const candidates = extractCandidates({ + entryId: "u3", + role: "user", + text: [ + "We should keep this branch experimental for now.", + "We should keep the MVP branch experimental.", + "We should rename the context window helper in this module.", + "Avoid touching docs/extensions.md.", + "Avoid touching docs/extensions.md across the whole session.", + "Prefer concise reports across the whole session.", + ].join("\n"), + timestamp: 3, + }); + + assert.deepEqual( + candidates + .filter((candidate) => candidate.kind === "constraint") + .map((candidate) => [candidate.text, candidate.scope, candidate.subject]), + [ + ["We should keep this branch experimental for now.", "branch", "must-u3-0"], + ["We should keep the MVP branch experimental.", "branch", "must-u3-1"], + ["We should rename the context window helper in this module.", "branch", "must-u3-2"], + ["Avoid touching docs/extensions.md.", "branch", "must-u3-3"], + ["Avoid touching docs/extensions.md across the whole session.", "session", "must-u3-4"], + ["Prefer concise reports across the whole session.", "session", "must-u3-5"], + ], + ); +}); + +test("extractCandidates treats spelled-out do not as a constraint trigger", () => { + const candidates = extractCandidates({ + entryId: "u4", + role: "user", + text: "Do not add new LLM-facing tools across the whole session.", + timestamp: 4, + }); + + assert.deepEqual( + candidates.map((candidate) => [candidate.kind, candidate.text, candidate.scope, candidate.subject]), + [["constraint", "Do not add new LLM-facing tools across the whole session.", "session", "must-u4-0"]], + ); +}); + +test("extractCandidates keeps compaction goals branch-scoped unless they are explicitly session-wide", () => { + const candidates = extractCandidates( + { + entryId: "cmp-goal-1", + role: "compaction", + text: "## Goal\n- prototype a branch-local tree handoff.", + timestamp: 19, + }, + { hasSessionGoal: false }, + ); + + assert.deepEqual( + candidates.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "goal-cmp-goal-1-0", "branch", "prototype a branch-local tree handoff."]], + ); +}); + +test("extractCandidates captures blockers from direct lines, tool errors, and structured summaries", () => { + const direct = extractCandidates( + { + entryId: "a-blocked-1", + role: "assistant", + text: "Blocked: confirm whether /tree summaries should replace instructions.", + timestamp: 20, + }, + { hasSessionGoal: true }, + ); + + const tool = extractCandidates( + { + entryId: "t-blocked-1", + role: "toolResult", + text: "Error: missing export createFocusMatcher\nstack...", + timestamp: 21, + isError: true, + }, + { hasSessionGoal: true }, + ); + + const summary = extractCandidates( + { + entryId: "cmp-1", + role: "compaction", + text: [ + "## Open questions and blockers", + "- Need to confirm whether /tree summaries should replace instructions.", + "", + "## Relevant files", + "- .pi/agent/extensions/context-manager/index.ts", + ].join("\n"), + timestamp: 22, + }, + { hasSessionGoal: true }, + ); + + assert.deepEqual( + direct.map((candidate) => [candidate.kind, candidate.subject, candidate.text]), + [["openQuestion", "open-question-a-blocked-1-0", "confirm whether /tree summaries should replace instructions."]], + ); + assert.equal(tool[0]?.kind, "openQuestion"); + assert.match(tool[0]?.text ?? "", /missing export createFocusMatcher/); + assert.deepEqual( + summary.map((candidate) => [candidate.kind, candidate.text]), + [ + ["openQuestion", "Need to confirm whether /tree summaries should replace instructions."], + ["relevantFile", ".pi/agent/extensions/context-manager/index.ts"], + ], + ); +}); + +test("extractCandidates parses pi fallback progress and blocked summary sections", () => { + const candidates = extractCandidates( + { + entryId: "cmp-default-1", + role: "compaction", + text: [ + "## Constraints and preferences", + "- Keep the public API stable.", + "", + "## Progress", + "### In Progress", + "- Wire runtime hydration.", + "", + "### Blocked", + "- confirm whether /tree replaceInstructions should override defaults", + ].join("\n"), + timestamp: 23, + }, + { hasSessionGoal: true }, + ); + + assert.ok(candidates.some((candidate) => candidate.kind === "constraint" && candidate.text === "Keep the public API stable.")); + assert.ok(candidates.some((candidate) => candidate.kind === "activeTask" && candidate.text === "Wire runtime hydration.")); + assert.ok( + candidates.some( + (candidate) => candidate.kind === "openQuestion" && candidate.text === "confirm whether /tree replaceInstructions should override defaults", + ), + ); +}); + +test("assistant decisions and tool-result file references are extracted as branch facts", () => { + const assistant = extractCandidates({ + entryId: "a1", + role: "assistant", + text: "Decision: persist snapshots after each turn_end.", + timestamp: 2, + }); + + const tool = extractCandidates({ + entryId: "t1", + role: "toolResult", + text: "Updated file: .pi/agent/extensions/context-manager/src/runtime.ts", + timestamp: 3, + }); + + assert.equal(assistant[0]?.kind, "decision"); + assert.equal(assistant[0]?.subject, "decision-a1-0"); + assert.equal(assistant[0]?.scope, "branch"); + assert.equal(tool[0]?.kind, "relevantFile"); +}); diff --git a/.pi/agent/extensions/context-manager/src/extract.ts b/.pi/agent/extensions/context-manager/src/extract.ts new file mode 100644 index 0000000..4aa53fb --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/extract.ts @@ -0,0 +1,314 @@ +import type { MemoryCandidate, MemoryScope, MemorySourceType } from "./ledger.ts"; + +export interface ExtractOptions { + hasSessionGoal?: boolean; +} + +export interface TranscriptSlice { + entryId: string; + role: "user" | "assistant" | "toolResult" | "compaction" | "branchSummary"; + text: string; + timestamp: number; + isError?: boolean; +} + +const FILE_RE = /(?:\.?\/?[A-Za-z0-9_./-]+\.(?:ts|tsx|js|mjs|json|md))/g; +const BRANCH_LOCAL_CONSTRAINT_RE = + /\b(?:this|current)\s+(?:branch|task|change|step|file|module|test|command|implementation|worktree)\b|\b(?:for now|right now|in this branch|on this branch|in this file|in this module|here)\b/i; +const DURABLE_SESSION_CONSTRAINT_RE = + /\b(?:whole|entire|rest of (?:the )?|remaining)\s+(?:session|project|codebase)\b|\bacross (?:the )?(?:whole )?(?:session|project|codebase)\b|\bacross (?:all |every )?branches\b|\b(?:session|project|codebase)[-\s]?wide\b|\bthroughout (?:the )?(?:session|project|codebase)\b/i; +const CONSTRAINT_RE = /\b(?:must|should|don't|do not|avoid|prefer)\b/i; +const GOAL_RE = /^(goal|session goal|overall goal):/i; +const OPEN_QUESTION_RE = /^(?:blocked|blocker|open question|question):/i; +const ERROR_LINE_RE = /\b(?:error|failed|failure|missing|undefined|exception)\b/i; + +type SummarySectionKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; + +function sourceTypeForRole(role: TranscriptSlice["role"]): MemorySourceType { + if (role === "compaction") return "compaction"; + if (role === "branchSummary") return "branchSummary"; + return role; +} + +function pushCandidate( + list: MemoryCandidate[], + candidate: Omit, + slice: TranscriptSlice, +) { + list.push({ + ...candidate, + sourceEntryId: slice.entryId, + sourceType: sourceTypeForRole(slice.role), + timestamp: slice.timestamp, + }); +} + +function createIndexedSubject(prefix: string, slice: TranscriptSlice, index: number): string { + return `${prefix}-${slice.entryId}-${index}`; +} + +function inferConstraintScope(slice: TranscriptSlice, line: string): MemoryScope { + if (slice.role !== "user") { + return "branch"; + } + + if (BRANCH_LOCAL_CONSTRAINT_RE.test(line)) { + return "branch"; + } + + if (DURABLE_SESSION_CONSTRAINT_RE.test(line)) { + return "session"; + } + + if ((line.match(FILE_RE) ?? []).length > 0) { + return "branch"; + } + + return "branch"; +} + +function nextGoalCandidate( + line: string, + slice: TranscriptSlice, + options: ExtractOptions, + index: number, +): Omit { + const text = line.replace(GOAL_RE, "").trim(); + const explicitSessionGoal = /^(session goal|overall goal):/i.test(line); + const canSeedSessionGoal = slice.role === "user"; + const shouldPromoteRootGoal = explicitSessionGoal || (!options.hasSessionGoal && canSeedSessionGoal); + + if (shouldPromoteRootGoal) { + return { + kind: "goal", + subject: "root-goal", + text, + scope: "session", + confidence: 1, + }; + } + + return { + kind: "goal", + subject: createIndexedSubject("goal", slice, index), + text, + scope: "branch", + confidence: 0.9, + }; +} + +function nextOpenQuestionCandidate( + text: string, + slice: TranscriptSlice, + index: number, +): Omit { + return { + kind: "openQuestion", + subject: createIndexedSubject("open-question", slice, index), + text, + scope: "branch", + confidence: slice.role === "toolResult" ? 0.85 : 0.8, + }; +} + +function summarySectionToKind(line: string): SummarySectionKind | undefined { + const heading = line.replace(/^##\s+/i, "").trim().toLowerCase(); + + if (heading === "goal") return "goal"; + if (heading === "constraints" || heading === "constraints & preferences" || heading === "constraints and preferences") { + return "constraint"; + } + if (heading === "decisions" || heading === "key decisions") return "decision"; + if (heading === "active work" || heading === "next steps" || heading === "current task" || heading === "progress") { + return "activeTask"; + } + if (heading === "open questions and blockers" || heading === "open questions / blockers") return "openQuestion"; + if (heading === "relevant files" || heading === "critical context") return "relevantFile"; + + return undefined; +} + +function pushRelevantFiles(list: MemoryCandidate[], slice: TranscriptSlice, line: string) { + const fileMatches = line.match(FILE_RE) ?? []; + for (const match of fileMatches) { + pushCandidate( + list, + { + kind: "relevantFile", + subject: match, + text: match, + scope: "branch", + confidence: 0.7, + }, + slice, + ); + } +} + +export function extractCandidates(slice: TranscriptSlice, options: ExtractOptions = {}): MemoryCandidate[] { + const out: MemoryCandidate[] = []; + const lines = slice.text + .split(/\n+/) + .map((line) => line.trim()) + .filter(Boolean); + + let currentSection: SummarySectionKind | undefined; + let goalIndex = 0; + let decisionIndex = 0; + let nextStepIndex = 0; + let mustIndex = 0; + let openQuestionIndex = 0; + + for (const line of lines) { + if (/^##\s+/i.test(line)) { + currentSection = summarySectionToKind(line); + continue; + } + + if (/^###\s+/i.test(line)) { + const subheading = line.replace(/^###\s+/i, "").trim().toLowerCase(); + if (subheading === "blocked") { + currentSection = "openQuestion"; + } else if (subheading === "in progress" || subheading === "done") { + currentSection = "activeTask"; + } + continue; + } + + const bullet = line.match(/^-\s+(.*)$/)?.[1]?.trim(); + const isGoal = GOAL_RE.test(line); + const isDecision = /^decision:/i.test(line); + const isNextStep = /^(next|task):/i.test(line); + const isOpenQuestion = OPEN_QUESTION_RE.test(line); + + if (isGoal) { + pushCandidate(out, nextGoalCandidate(line, slice, options, goalIndex++), slice); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isOpenQuestion) { + pushCandidate( + out, + nextOpenQuestionCandidate(line.replace(OPEN_QUESTION_RE, "").trim(), slice, openQuestionIndex++), + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isDecision) { + pushCandidate( + out, + { + kind: "decision", + subject: createIndexedSubject("decision", slice, decisionIndex++), + text: line.replace(/^decision:\s*/i, "").trim(), + scope: "branch", + confidence: 0.9, + }, + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isNextStep) { + pushCandidate( + out, + { + kind: "activeTask", + subject: createIndexedSubject("next-step", slice, nextStepIndex++), + text: line.replace(/^(next|task):\s*/i, "").trim(), + scope: "branch", + confidence: 0.8, + }, + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (bullet && currentSection === "goal") { + pushCandidate(out, nextGoalCandidate(`Goal: ${bullet}`, slice, options, goalIndex++), slice); + continue; + } + + if (bullet && currentSection === "constraint") { + pushCandidate( + out, + { + kind: "constraint", + subject: createIndexedSubject("must", slice, mustIndex++), + text: bullet, + scope: inferConstraintScope(slice, bullet), + confidence: 0.8, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "decision") { + pushCandidate( + out, + { + kind: "decision", + subject: createIndexedSubject("decision", slice, decisionIndex++), + text: bullet, + scope: "branch", + confidence: 0.9, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "activeTask") { + pushCandidate( + out, + { + kind: "activeTask", + subject: createIndexedSubject("next-step", slice, nextStepIndex++), + text: bullet, + scope: "branch", + confidence: 0.8, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "openQuestion") { + pushCandidate(out, nextOpenQuestionCandidate(bullet, slice, openQuestionIndex++), slice); + continue; + } + + if (bullet && currentSection === "relevantFile") { + pushRelevantFiles(out, slice, bullet); + continue; + } + + if (slice.role === "toolResult" && (slice.isError || ERROR_LINE_RE.test(line))) { + pushCandidate(out, nextOpenQuestionCandidate(line, slice, openQuestionIndex++), slice); + } + + if (CONSTRAINT_RE.test(line)) { + pushCandidate( + out, + { + kind: "constraint", + subject: createIndexedSubject("must", slice, mustIndex++), + text: line, + scope: inferConstraintScope(slice, line), + confidence: 0.8, + }, + slice, + ); + } + + pushRelevantFiles(out, slice, line); + } + + return out; +} diff --git a/.pi/agent/extensions/context-manager/src/ledger.test.ts b/.pi/agent/extensions/context-manager/src/ledger.test.ts new file mode 100644 index 0000000..d69aca9 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/ledger.test.ts @@ -0,0 +1,132 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createEmptyLedger, getActiveItems, mergeCandidates, type MemoryCandidate } from "./ledger.ts"; + +const base: Omit = { + scope: "branch", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 0.9, +}; + +test("mergeCandidates adds new active items to an empty ledger", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "goal", subject: "root-goal", text: "Build a pi context manager extension" }, + ]); + + assert.equal(getActiveItems(ledger).length, 1); + assert.equal(getActiveItems(ledger)[0]?.text, "Build a pi context manager extension"); +}); + +test("mergeCandidates archives older items when a new item supersedes the same subject", () => { + const first = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + ]); + + const second = mergeCandidates(first, [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 2 }, + ]); + + const active = getActiveItems(second, "decision"); + assert.equal(active.length, 1); + assert.equal(active[0]?.text, "Persist snapshots after each turn_end"); + assert.equal(active[0]?.supersedesId, "decision:branch:persistence:1"); + assert.equal(second.items.find((item) => item.id === "decision:branch:persistence:1")?.active, false); +}); + +test("mergeCandidates keeps the newest item active when same-slot candidates arrive out of order", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 2 }, + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + ]); + + const active = getActiveItems(ledger, "decision"); + const stale = ledger.items.find((item) => item.text === "Persist snapshots with appendEntry()"); + + assert.equal(active.length, 1); + assert.equal(active[0]?.text, "Persist snapshots after each turn_end"); + assert.equal(stale?.active, false); + assert.equal(stale?.supersedesId, undefined); +}); + +test("mergeCandidates gives same-slot same-timestamp candidates distinct ids", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 1 }, + ]); + + const ids = ledger.items.map((item) => item.id); + const active = getActiveItems(ledger, "decision")[0]; + const archived = ledger.items.find((item) => item.text === "Persist snapshots with appendEntry()"); + + assert.equal(new Set(ids).size, ledger.items.length); + assert.equal(active?.text, "Persist snapshots after each turn_end"); + assert.equal(active?.supersedesId, archived?.id); + assert.notEqual(active?.id, active?.supersedesId); +}); + +test("mergeCandidates keeps same-slot same-timestamp snapshots deterministic regardless of input order", () => { + const appendEntryCandidate = { + ...base, + kind: "decision" as const, + subject: "persistence", + text: "Persist snapshots with appendEntry()", + timestamp: 1, + }; + const turnEndCandidate = { + ...base, + kind: "decision" as const, + subject: "persistence", + text: "Persist snapshots after each turn_end", + timestamp: 1, + }; + + const forward = mergeCandidates(createEmptyLedger(), [appendEntryCandidate, turnEndCandidate]); + const reversed = mergeCandidates(createEmptyLedger(), [turnEndCandidate, appendEntryCandidate]); + + assert.deepEqual(forward, reversed); + assert.deepEqual(forward.items, [ + { + ...turnEndCandidate, + id: "decision:branch:persistence:1", + freshness: 1, + active: true, + supersedesId: "decision:branch:persistence:1:2", + }, + { + ...appendEntryCandidate, + id: "decision:branch:persistence:1:2", + freshness: 1, + active: false, + supersedesId: undefined, + }, + ]); +}); + +test("session-scoped memory can coexist with branch-scoped memory for the same kind", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { + kind: "constraint", + subject: "llm-tools", + text: "Do not add new LLM-facing tools in the MVP", + scope: "session", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 1, + }, + { + kind: "constraint", + subject: "branch-policy", + text: "Keep branch A experimental", + scope: "branch", + sourceEntryId: "u2", + sourceType: "user", + timestamp: 2, + confidence: 0.8, + }, + ]); + + assert.equal(getActiveItems(ledger, "constraint").length, 2); +}); diff --git a/.pi/agent/extensions/context-manager/src/ledger.ts b/.pi/agent/extensions/context-manager/src/ledger.ts new file mode 100644 index 0000000..2ab41fe --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/ledger.ts @@ -0,0 +1,196 @@ +export type MemoryKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; +export type MemoryScope = "branch" | "session"; +export type MemorySourceType = "user" | "assistant" | "toolResult" | "compaction" | "branchSummary"; + +export interface MemoryCandidate { + kind: MemoryKind; + subject: string; + text: string; + scope: MemoryScope; + sourceEntryId: string; + sourceType: MemorySourceType; + timestamp: number; + confidence: number; +} + +export interface MemoryItem extends MemoryCandidate { + id: string; + freshness: number; + active: boolean; + supersedesId?: string; +} + +export interface LedgerState { + items: MemoryItem[]; + rollingSummary: string; +} + +type MemorySlot = Pick; + +export function createEmptyLedger(): LedgerState { + return { items: [], rollingSummary: "" }; +} + +function createId(candidate: MemoryCandidate): string { + return `${candidate.kind}:${candidate.scope}:${candidate.subject}:${candidate.timestamp}`; +} + +function ensureUniqueId(items: Pick[], baseId: string): string { + let id = baseId; + let suffix = 2; + + while (items.some((item) => item.id === id)) { + id = `${baseId}:${suffix}`; + suffix += 1; + } + + return id; +} + +function sameSlot(left: MemorySlot, right: MemorySlot) { + return left.kind === right.kind && left.scope === right.scope && left.subject === right.subject; +} + +function createSlotKey(slot: MemorySlot): string { + return `${slot.kind}\u0000${slot.scope}\u0000${slot.subject}`; +} + +function compareStrings(left: string, right: string): number { + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; +} + +function compareSameTimestampCandidates( + left: Pick, + right: Pick +): number { + // Exact-timestamp ties should resolve the same way no matter which candidate is processed first. + const textComparison = compareStrings(left.text, right.text); + if (textComparison !== 0) { + return textComparison; + } + + const sourceTypeComparison = compareStrings(left.sourceType, right.sourceType); + if (sourceTypeComparison !== 0) { + return sourceTypeComparison; + } + + const sourceEntryIdComparison = compareStrings(left.sourceEntryId, right.sourceEntryId); + if (sourceEntryIdComparison !== 0) { + return sourceEntryIdComparison; + } + + if (left.confidence !== right.confidence) { + return left.confidence > right.confidence ? -1 : 1; + } + + return 0; +} + +function candidateSupersedesPrevious(candidate: MemoryCandidate, previous?: MemoryItem): boolean { + if (!previous) { + return true; + } + + if (candidate.timestamp !== previous.timestamp) { + return candidate.timestamp > previous.timestamp; + } + + return compareSameTimestampCandidates(candidate, previous) < 0; +} + +function compareSlotItems(left: MemoryItem, right: MemoryItem): number { + if (left.timestamp !== right.timestamp) { + return right.timestamp - left.timestamp; + } + + return compareSameTimestampCandidates(left, right); +} + +function normalizeSlotItems(items: MemoryItem[], slot: MemorySlot): MemoryItem[] { + const slotIndices: number[] = []; + const slotItems: MemoryItem[] = []; + + items.forEach((item, index) => { + if (!sameSlot(item, slot)) { + return; + } + + slotIndices.push(index); + slotItems.push(item); + }); + + if (slotItems.length <= 1) { + return items; + } + + const sortedSlotItems = [...slotItems].sort(compareSlotItems); + const slotIds = new Map(); + const sortedSlotItemsWithIds = sortedSlotItems.map((item) => { + const baseId = createId(item); + const nextSlotIdCount = (slotIds.get(baseId) ?? 0) + 1; + slotIds.set(baseId, nextSlotIdCount); + + return { + item, + id: nextSlotIdCount === 1 ? baseId : `${baseId}:${nextSlotIdCount}`, + }; + }); + + const normalizedSlotItems = sortedSlotItemsWithIds.map(({ item, id }, index) => ({ + ...item, + id, + freshness: index === 0 ? item.timestamp : sortedSlotItemsWithIds[index - 1]!.item.timestamp, + active: index === 0, + supersedesId: sortedSlotItemsWithIds[index + 1]?.id, + })); + + const normalizedItems = [...items]; + slotIndices.forEach((slotIndex, index) => { + normalizedItems[slotIndex] = normalizedSlotItems[index]!; + }); + + return normalizedItems; +} + +export function mergeCandidates(state: LedgerState, candidates: MemoryCandidate[]): LedgerState { + let items = [...state.items]; + const affectedSlots = new Map(); + + for (const candidate of candidates) { + const previousIndex = items.findIndex((item) => item.active && sameSlot(item, candidate)); + const previous = previousIndex === -1 ? undefined : items[previousIndex]; + const candidateIsNewest = candidateSupersedesPrevious(candidate, previous); + + if (previous && candidateIsNewest) { + items[previousIndex] = { ...previous, active: false, freshness: candidate.timestamp }; + } + + items.push({ + ...candidate, + id: ensureUniqueId(items, createId(candidate)), + freshness: candidate.timestamp, + active: candidateIsNewest, + supersedesId: candidateIsNewest ? previous?.id : undefined, + }); + + affectedSlots.set(createSlotKey(candidate), { + kind: candidate.kind, + scope: candidate.scope, + subject: candidate.subject, + }); + } + + for (const slot of affectedSlots.values()) { + items = normalizeSlotItems(items, slot); + } + + return { ...state, items }; +} + +export function getActiveItems(state: LedgerState, kind?: MemoryKind): MemoryItem[] { + return state.items.filter((item) => item.active && (kind ? item.kind === kind : true)); +} diff --git a/.pi/agent/extensions/context-manager/src/packet.test.ts b/.pi/agent/extensions/context-manager/src/packet.test.ts new file mode 100644 index 0000000..e50bd5c --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/packet.test.ts @@ -0,0 +1,130 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolvePolicy } from "./config.ts"; +import { buildContextPacket } from "./packet.ts"; +import { createEmptyLedger, mergeCandidates, type MemoryCandidate } from "./ledger.ts"; + +const baseCandidate: Omit = { + scope: "session", + sourceEntryId: "seed", + sourceType: "user", + timestamp: 1, + confidence: 1, +}; + +function estimateTokens(text: string) { + return Math.ceil(text.length / 4); +} + +function memory(candidate: Pick & Partial>): MemoryCandidate { + return { + ...baseCandidate, + ...candidate, + sourceEntryId: candidate.sourceEntryId ?? candidate.subject, + }; +} + +function buildPolicy(packetTokenCap: number) { + return { + ...resolvePolicy({ mode: "balanced", contextWindow: 200_000 }), + packetTokenCap, + }; +} + +test("buildContextPacket keeps top-ranked facts from a section when the cap is tight", () => { + const expected = [ + "## Active goal", + "- Keep packets compact.", + "", + "## Constraints", + "- Preserve the highest-priority constraint.", + "", + "## Key decisions", + "- Render selected sections in stable order.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep packets compact." }), + memory({ kind: "constraint", subject: "constraint-a", text: "Preserve the highest-priority constraint.", confidence: 1, timestamp: 3 }), + memory({ + kind: "constraint", + subject: "constraint-b", + text: "Avoid dropping every constraint just because one extra bullet is too large for a tight packet cap.", + confidence: 0.6, + timestamp: 2, + }), + memory({ + kind: "decision", + subject: "decision-a", + text: "Render selected sections in stable order.", + confidence: 0.9, + timestamp: 4, + sourceType: "assistant", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); + +test("buildContextPacket uses cross-kind weights when only one lower-priority section can fit", () => { + const expected = [ + "## Active goal", + "- Keep the agent moving.", + "", + "## Current task", + "- Fix packet trimming.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep the agent moving." }), + memory({ + kind: "decision", + subject: "decision-a", + text: "Keep logs concise.", + confidence: 1, + timestamp: 2, + sourceType: "assistant", + }), + memory({ + kind: "activeTask", + subject: "task-a", + text: "Fix packet trimming.", + confidence: 1, + timestamp: 2, + sourceType: "assistant", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); + +test("buildContextPacket keeps a goal ahead of newer low-priority facts at realistic timestamp scales", () => { + const expected = [ + "## Active goal", + "- Keep the agent on track.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep the agent on track.", timestamp: 1_000_000 }), + memory({ + kind: "relevantFile", + subject: "runtime-file", + text: "src/runtime.ts", + timestamp: 10_000_000, + confidence: 1, + sourceType: "assistant", + scope: "branch", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); diff --git a/.pi/agent/extensions/context-manager/src/packet.ts b/.pi/agent/extensions/context-manager/src/packet.ts new file mode 100644 index 0000000..817ed17 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/packet.ts @@ -0,0 +1,91 @@ +import type { Policy } from "./config.ts"; +import { getActiveItems, type LedgerState, type MemoryItem, type MemoryKind } from "./ledger.ts"; + +const SECTION_ORDER: Array<{ kind: MemoryKind; title: string }> = [ + { kind: "goal", title: "Active goal" }, + { kind: "constraint", title: "Constraints" }, + { kind: "decision", title: "Key decisions" }, + { kind: "activeTask", title: "Current task" }, + { kind: "relevantFile", title: "Relevant files" }, + { kind: "openQuestion", title: "Open questions / blockers" }, +]; + +const WEIGHTS: Record = { + goal: 100, + constraint: 90, + decision: 80, + activeTask: 85, + relevantFile: 60, + openQuestion: 70, +}; + +const SECTION_INDEX = new Map(SECTION_ORDER.map((section, index) => [section.kind, index])); + +function estimateTokens(text: string): number { + return Math.ceil(text.length / 4); +} + +function compareByPriority(left: MemoryItem, right: MemoryItem): number { + const weightDifference = WEIGHTS[right.kind] - WEIGHTS[left.kind]; + if (weightDifference !== 0) { + return weightDifference; + } + + if (left.confidence !== right.confidence) { + return right.confidence - left.confidence; + } + + const sectionDifference = SECTION_INDEX.get(left.kind)! - SECTION_INDEX.get(right.kind)!; + if (sectionDifference !== 0) { + return sectionDifference; + } + + if (left.freshness !== right.freshness) { + return right.freshness - left.freshness; + } + + return left.id.localeCompare(right.id); +} + +function sortByPriority(items: MemoryItem[]) { + return [...items].sort(compareByPriority); +} + +function renderPacket(itemsByKind: Map, selectedIds: Set) { + const lines: string[] = []; + + for (const section of SECTION_ORDER) { + const items = itemsByKind.get(section.kind)?.filter((item) => selectedIds.has(item.id)) ?? []; + if (items.length === 0) continue; + + lines.push(`## ${section.title}`, ...items.map((item) => `- ${item.text}`), ""); + } + + return lines.join("\n").trim(); +} + +export function buildContextPacket(ledger: LedgerState, policy: Policy): { text: string; estimatedTokens: number } { + const itemsByKind = new Map(); + for (const section of SECTION_ORDER) { + itemsByKind.set(section.kind, sortByPriority(getActiveItems(ledger, section.kind))); + } + + const candidates = sortByPriority(getActiveItems(ledger)); + const selectedIds = new Set(); + let text = ""; + + for (const item of candidates) { + const tentativeSelectedIds = new Set(selectedIds); + tentativeSelectedIds.add(item.id); + + const tentative = renderPacket(itemsByKind, tentativeSelectedIds); + if (estimateTokens(tentative) > policy.packetTokenCap) { + continue; + } + + selectedIds.add(item.id); + text = tentative; + } + + return { text, estimatedTokens: estimateTokens(text) }; +} diff --git a/.pi/agent/extensions/context-manager/src/persist.test.ts b/.pi/agent/extensions/context-manager/src/persist.test.ts new file mode 100644 index 0000000..4475c74 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/persist.test.ts @@ -0,0 +1,67 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { deserializeLatestSnapshot, SNAPSHOT_ENTRY_TYPE, serializeSnapshot } from "./persist.ts"; + +function createSnapshot(lastZone: "green" | "yellow" | "red" | "compact", lastCompactionSummary: string) { + return serializeSnapshot({ + mode: "balanced", + lastZone, + lastCompactionSummary, + lastBranchSummary: undefined, + ledger: { + items: [ + { + id: `goal:session:root:${lastCompactionSummary}`, + kind: "goal", + subject: "root", + text: `Goal ${lastCompactionSummary}`, + scope: "session", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary: "summary", + }, + }); +} + +test("deserializeLatestSnapshot restores the newest matching custom entry", () => { + const first = createSnapshot("yellow", "old"); + const second = createSnapshot("red", "new"); + + const restored = deserializeLatestSnapshot([ + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: first }, + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: second }, + ]); + + assert.equal(restored?.lastZone, "red"); + assert.equal(restored?.lastCompactionSummary, "new"); +}); + +test("deserializeLatestSnapshot skips malformed newer entries and clones the accepted snapshot", () => { + const valid = createSnapshot("yellow", "valid"); + + const restored = deserializeLatestSnapshot([ + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: valid }, + { + type: "custom", + customType: SNAPSHOT_ENTRY_TYPE, + data: { + mode: "balanced", + lastZone: "red", + ledger: { items: "not-an-array", rollingSummary: "broken" }, + }, + }, + ]); + + assert.deepEqual(restored, valid); + assert.notStrictEqual(restored, valid); + + restored!.ledger.items[0]!.text = "mutated"; + assert.equal(valid.ledger.items[0]!.text, "Goal valid"); +}); diff --git a/.pi/agent/extensions/context-manager/src/persist.ts b/.pi/agent/extensions/context-manager/src/persist.ts new file mode 100644 index 0000000..5cac54e --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/persist.ts @@ -0,0 +1,142 @@ +import type { ContextMode, ContextZone } from "./config.ts"; +import type { LedgerState, MemoryItem, MemoryKind, MemoryScope, MemorySourceType } from "./ledger.ts"; + +export const SNAPSHOT_ENTRY_TYPE = "context-manager.snapshot"; + +export interface RuntimeSnapshot { + mode: ContextMode; + lastZone: ContextZone; + lastObservedTokens?: number; + lastCompactionSummary?: string; + lastBranchSummary?: string; + ledger: LedgerState; +} + +const CONTEXT_MODES = new Set(["conservative", "balanced", "aggressive"]); +const CONTEXT_ZONES = new Set(["green", "yellow", "red", "compact"]); +const MEMORY_KINDS = new Set(["goal", "constraint", "decision", "activeTask", "openQuestion", "relevantFile"]); +const MEMORY_SCOPES = new Set(["branch", "session"]); +const MEMORY_SOURCE_TYPES = new Set(["user", "assistant", "toolResult", "compaction", "branchSummary"]); + +export function serializeSnapshot(snapshot: RuntimeSnapshot): RuntimeSnapshot { + return structuredClone(snapshot); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function isFiniteNumber(value: unknown): value is number { + return typeof value === "number" && Number.isFinite(value); +} + +function isOptionalString(value: unknown): value is string | undefined { + return value === undefined || typeof value === "string"; +} + +function parseMemoryItem(value: unknown): MemoryItem | undefined { + if (!isRecord(value)) { + return undefined; + } + + if ( + typeof value.id !== "string" || + !MEMORY_KINDS.has(value.kind as MemoryKind) || + typeof value.subject !== "string" || + typeof value.text !== "string" || + !MEMORY_SCOPES.has(value.scope as MemoryScope) || + typeof value.sourceEntryId !== "string" || + !MEMORY_SOURCE_TYPES.has(value.sourceType as MemorySourceType) || + !isFiniteNumber(value.timestamp) || + !isFiniteNumber(value.confidence) || + !isFiniteNumber(value.freshness) || + typeof value.active !== "boolean" || + !isOptionalString(value.supersedesId) + ) { + return undefined; + } + + return { + id: value.id, + kind: value.kind as MemoryKind, + subject: value.subject, + text: value.text, + scope: value.scope as MemoryScope, + sourceEntryId: value.sourceEntryId, + sourceType: value.sourceType as MemorySourceType, + timestamp: value.timestamp, + confidence: value.confidence, + freshness: value.freshness, + active: value.active, + supersedesId: value.supersedesId, + }; +} + +function parseLedgerState(value: unknown): LedgerState | undefined { + if (!isRecord(value) || !Array.isArray(value.items) || typeof value.rollingSummary !== "string") { + return undefined; + } + + const items: MemoryItem[] = []; + for (const item of value.items) { + const parsed = parseMemoryItem(item); + if (!parsed) { + return undefined; + } + + items.push(parsed); + } + + return { + items, + rollingSummary: value.rollingSummary, + }; +} + +function parseRuntimeSnapshot(value: unknown): RuntimeSnapshot | undefined { + if ( + !isRecord(value) || + !CONTEXT_MODES.has(value.mode as ContextMode) || + !CONTEXT_ZONES.has(value.lastZone as ContextZone) || + !isOptionalString(value.lastCompactionSummary) || + !isOptionalString(value.lastBranchSummary) || + (value.lastObservedTokens !== undefined && !isFiniteNumber(value.lastObservedTokens)) + ) { + return undefined; + } + + const ledger = parseLedgerState(value.ledger); + if (!ledger) { + return undefined; + } + + const snapshot: RuntimeSnapshot = { + mode: value.mode as ContextMode, + lastZone: value.lastZone as ContextZone, + lastCompactionSummary: value.lastCompactionSummary, + lastBranchSummary: value.lastBranchSummary, + ledger, + }; + + if (value.lastObservedTokens !== undefined) { + snapshot.lastObservedTokens = value.lastObservedTokens; + } + + return snapshot; +} + +export function deserializeLatestSnapshot(entries: Array<{ type: string; customType?: string; data?: unknown }>): RuntimeSnapshot | undefined { + for (let index = entries.length - 1; index >= 0; index -= 1) { + const entry = entries[index]!; + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = parseRuntimeSnapshot(entry.data); + if (snapshot) { + return snapshot; + } + } + + return undefined; +} diff --git a/.pi/agent/extensions/context-manager/src/prune.test.ts b/.pi/agent/extensions/context-manager/src/prune.test.ts new file mode 100644 index 0000000..747a415 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/prune.test.ts @@ -0,0 +1,131 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolvePolicy } from "./config.ts"; +import { pruneContextMessages } from "./prune.ts"; + +const bulky = "line\n".repeat(300); +const boundaryBulky = "boundary\n".repeat(300); +const thresholdWithTrailingNewline = "threshold\n".repeat(150); + +function buildPolicy(recentUserTurns = 4) { + return { + ...resolvePolicy({ mode: "balanced", contextWindow: 200_000 }), + recentUserTurns, + }; +} + +test("pruneContextMessages replaces old bulky tool results with distilled summaries instead of deleting them", () => { + const policy = buildPolicy(2); + const bulkyFailure = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + ...Array.from({ length: 220 }, () => "stack frame"), + ].join("\n"); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "bash", content: bulkyFailure }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + const distilled = pruned.find((message) => message.role === "toolResult"); + assert.ok(distilled); + assert.match(distilled!.content, /missing export createFocusMatcher/); + assert.doesNotMatch(distilled!.content, /stack frame\nstack frame\nstack frame/); +}); + +test("aggressive mode distills an older bulky tool result sooner than conservative mode", () => { + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "after turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + { role: "assistant", content: "after turn 3" }, + { role: "user", content: "turn 4" }, + ]; + + const conservativePruned = pruneContextMessages(messages, conservative); + const aggressivePruned = pruneContextMessages(messages, aggressive); + + assert.equal(conservativePruned[1]?.content, bulky); + assert.notEqual(aggressivePruned[1]?.content, bulky); + assert.match(aggressivePruned[1]?.content ?? "", /^\[distilled read output\]/); +}); + +test("pruneContextMessages keeps recent bulky tool results inside the recent-turn window", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages keeps old non-bulky tool results outside the recent-turn window", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: "short output" }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages keeps exactly-150-line tool results with a trailing newline", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: thresholdWithTrailingNewline }, + { role: "assistant", content: "after threshold output" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages honors the recent-user-turn boundary", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "after turn 1" }, + { role: "user", content: "turn 2" }, + { role: "toolResult", toolName: "read", content: boundaryBulky }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.equal(pruned[1]?.role, "toolResult"); + assert.match(pruned[1]?.content ?? "", /^\[distilled read output\]/); + assert.deepEqual( + pruned.map((message) => message.content), + ["turn 1", pruned[1]!.content, "after turn 1", "turn 2", boundaryBulky, "after turn 2", "turn 3"] + ); +}); diff --git a/.pi/agent/extensions/context-manager/src/prune.ts b/.pi/agent/extensions/context-manager/src/prune.ts new file mode 100644 index 0000000..768243e --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/prune.ts @@ -0,0 +1,54 @@ +import type { Policy } from "./config.ts"; +import { distillToolResult } from "./distill.ts"; + +export interface ContextMessage { + role: string; + content: string; + toolName?: string; + original?: unknown; + distilled?: boolean; +} + +function isBulky(content: string, policy: Policy) { + const bytes = Buffer.byteLength(content, "utf8"); + const parts = content.split("\n"); + const lines = content.endsWith("\n") ? parts.length - 1 : parts.length; + return bytes > policy.bulkyBytes || lines > policy.bulkyLines; +} + +export function pruneContextMessages(messages: ContextMessage[], policy: Policy): ContextMessage[] { + let seenUserTurns = 0; + const keep = new Set(); + + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]!; + keep.add(index); + if (message.role === "user") { + seenUserTurns += 1; + if (seenUserTurns >= policy.recentUserTurns) { + break; + } + } + } + + const next: ContextMessage[] = []; + for (const [index, message] of messages.entries()) { + if (keep.has(index) || message.role !== "toolResult" || !isBulky(message.content, policy)) { + next.push(message); + continue; + } + + const distilled = distillToolResult({ toolName: message.toolName, content: message.content }); + if (!distilled) { + continue; + } + + next.push({ + ...message, + content: distilled, + distilled: true, + }); + } + + return next; +} diff --git a/.pi/agent/extensions/context-manager/src/runtime.test.ts b/.pi/agent/extensions/context-manager/src/runtime.test.ts new file mode 100644 index 0000000..7fe3a52 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/runtime.test.ts @@ -0,0 +1,178 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { SNAPSHOT_ENTRY_TYPE, deserializeLatestSnapshot } from "./persist.ts"; +import { createContextManagerRuntime } from "./runtime.ts"; + +test("runtime ingests transcript slices and updates pressure state", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u1", role: "user", text: "Goal: Build a pi context manager. Next: wire hooks.", timestamp: 1 }); + runtime.observeTokens(150_000); + + const packet = runtime.buildPacket(); + assert.match(packet.text, /Build a pi context manager/); + assert.equal(runtime.getSnapshot().lastZone, "red"); +}); + +test("runtime keeps the session root goal while allowing later branch-local goals", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u-root-goal", role: "user", text: "Goal: Ship the context manager extension.", timestamp: 10 }); + runtime.ingest({ entryId: "u-branch-goal", role: "user", text: "Goal: prototype a branch-local tree handoff.", timestamp: 11 }); + + const packet = runtime.buildPacket(); + assert.match(packet.text, /Ship the context manager extension/); + assert.match(packet.text, /prototype a branch-local tree handoff/); + assert.equal( + runtime + .getSnapshot() + .ledger.items.filter((item) => item.active && item.kind === "goal" && item.scope === "session").length, + 1, + ); +}); + +test("recordCompactionSummary and recordBranchSummary update snapshot state and resume output", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u-artifact-1", role: "user", text: "Goal: Ship the context manager extension.", timestamp: 20 }); + runtime.recordCompactionSummary( + "## Key Decisions\n- Keep summaries deterministic.\n\n## Open questions and blockers\n- Verify /tree replaceInstructions behavior.", + ); + runtime.recordBranchSummary("# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals."); + + const snapshot = runtime.getSnapshot(); + assert.match(snapshot.lastCompactionSummary ?? "", /Keep summaries deterministic/); + assert.match(snapshot.lastBranchSummary ?? "", /Do not leak branch-local goals/); + assert.match(runtime.buildResumePacket(), /Verify \/tree replaceInstructions behavior/); + assert.match(runtime.buildResumePacket(), /Do not leak branch-local goals/); +}); + +test("buildPacket tightens the live packet after pressure reaches the compact zone", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.restore({ + mode: "balanced", + lastZone: "green", + ledger: { + rollingSummary: "", + items: [ + { id: "goal:session:root-goal:1", kind: "goal", subject: "root-goal", text: "Ship the context manager extension with deterministic handoffs and predictable branch-boundary behavior.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 1, freshness: 1, active: true }, + { id: "constraint:session:must-1:1", kind: "constraint", subject: "must-1", text: "Keep the public API stable while hardening branch-boundary state carryover, fallback summary replay, and resume injection behavior.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 2, confidence: 0.9, freshness: 2, active: true }, + { id: "decision:branch:decision-1:1", kind: "decision", subject: "decision-1", text: "Persist summary artifacts, replay them after the latest snapshot, and surface them through the next hidden resume packet before normal packet injection resumes.", scope: "branch", sourceEntryId: "a1", sourceType: "assistant", timestamp: 3, confidence: 0.9, freshness: 3, active: true }, + { id: "activeTask:branch:task-1:1", kind: "activeTask", subject: "task-1", text: "Verify mode-dependent pruning, packet tightening under pressure, and snapshot-less branch rehydration without stale handoff leakage.", scope: "branch", sourceEntryId: "a2", sourceType: "assistant", timestamp: 4, confidence: 0.8, freshness: 4, active: true }, + { id: "openQuestion:branch:question-1:1", kind: "openQuestion", subject: "question-1", text: "Confirm whether default pi fallback summaries preserve blockers and active work end to end when custom compaction falls back.", scope: "branch", sourceEntryId: "a3", sourceType: "assistant", timestamp: 5, confidence: 0.8, freshness: 5, active: true }, + { id: "relevantFile:branch:file-1:1", kind: "relevantFile", subject: "file-1", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-1.ts", scope: "branch", sourceEntryId: "t1", sourceType: "toolResult", timestamp: 6, confidence: 0.7, freshness: 6, active: true }, + { id: "relevantFile:branch:file-2:1", kind: "relevantFile", subject: "file-2", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-2.ts", scope: "branch", sourceEntryId: "t2", sourceType: "toolResult", timestamp: 7, confidence: 0.7, freshness: 7, active: true }, + { id: "relevantFile:branch:file-3:1", kind: "relevantFile", subject: "file-3", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-3.ts", scope: "branch", sourceEntryId: "t3", sourceType: "toolResult", timestamp: 8, confidence: 0.7, freshness: 8, active: true }, + { id: "relevantFile:branch:file-4:1", kind: "relevantFile", subject: "file-4", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-4.ts", scope: "branch", sourceEntryId: "t4", sourceType: "toolResult", timestamp: 9, confidence: 0.7, freshness: 9, active: true }, + { id: "relevantFile:branch:file-5:1", kind: "relevantFile", subject: "file-5", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-5.ts", scope: "branch", sourceEntryId: "t5", sourceType: "toolResult", timestamp: 10, confidence: 0.7, freshness: 10, active: true }, + { id: "relevantFile:branch:file-6:1", kind: "relevantFile", subject: "file-6", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-6.ts", scope: "branch", sourceEntryId: "t6", sourceType: "toolResult", timestamp: 11, confidence: 0.7, freshness: 11, active: true }, + { id: "relevantFile:branch:file-7:1", kind: "relevantFile", subject: "file-7", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-7.ts", scope: "branch", sourceEntryId: "t7", sourceType: "toolResult", timestamp: 12, confidence: 0.7, freshness: 12, active: true }, + { id: "relevantFile:branch:file-8:1", kind: "relevantFile", subject: "file-8", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-8.ts", scope: "branch", sourceEntryId: "t8", sourceType: "toolResult", timestamp: 13, confidence: 0.7, freshness: 13, active: true }, + { id: "relevantFile:branch:file-9:1", kind: "relevantFile", subject: "file-9", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-9.ts", scope: "branch", sourceEntryId: "t9", sourceType: "toolResult", timestamp: 14, confidence: 0.7, freshness: 14, active: true }, + { id: "relevantFile:branch:file-10:1", kind: "relevantFile", subject: "file-10", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-10.ts", scope: "branch", sourceEntryId: "t10", sourceType: "toolResult", timestamp: 15, confidence: 0.7, freshness: 15, active: true }, + { id: "relevantFile:branch:file-11:1", kind: "relevantFile", subject: "file-11", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-11.ts", scope: "branch", sourceEntryId: "t11", sourceType: "toolResult", timestamp: 16, confidence: 0.7, freshness: 16, active: true }, + { id: "relevantFile:branch:file-12:1", kind: "relevantFile", subject: "file-12", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-12.ts", scope: "branch", sourceEntryId: "t12", sourceType: "toolResult", timestamp: 17, confidence: 0.7, freshness: 17, active: true }, + { id: "relevantFile:branch:file-13:1", kind: "relevantFile", subject: "file-13", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-13.ts", scope: "branch", sourceEntryId: "t13", sourceType: "toolResult", timestamp: 18, confidence: 0.7, freshness: 18, active: true }, + { id: "relevantFile:branch:file-14:1", kind: "relevantFile", subject: "file-14", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-14.ts", scope: "branch", sourceEntryId: "t14", sourceType: "toolResult", timestamp: 19, confidence: 0.7, freshness: 19, active: true }, + { id: "relevantFile:branch:file-15:1", kind: "relevantFile", subject: "file-15", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-15.ts", scope: "branch", sourceEntryId: "t15", sourceType: "toolResult", timestamp: 20, confidence: 0.7, freshness: 20, active: true }, + { id: "relevantFile:branch:file-16:1", kind: "relevantFile", subject: "file-16", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-16.ts", scope: "branch", sourceEntryId: "t16", sourceType: "toolResult", timestamp: 21, confidence: 0.7, freshness: 21, active: true }, + { id: "relevantFile:branch:file-17:1", kind: "relevantFile", subject: "file-17", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-17.ts", scope: "branch", sourceEntryId: "t17", sourceType: "toolResult", timestamp: 22, confidence: 0.7, freshness: 22, active: true }, + { id: "relevantFile:branch:file-18:1", kind: "relevantFile", subject: "file-18", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-18.ts", scope: "branch", sourceEntryId: "t18", sourceType: "toolResult", timestamp: 23, confidence: 0.7, freshness: 23, active: true }, + { id: "relevantFile:branch:file-19:1", kind: "relevantFile", subject: "file-19", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-19.ts", scope: "branch", sourceEntryId: "t19", sourceType: "toolResult", timestamp: 24, confidence: 0.7, freshness: 24, active: true }, + { id: "relevantFile:branch:file-20:1", kind: "relevantFile", subject: "file-20", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-20.ts", scope: "branch", sourceEntryId: "t20", sourceType: "toolResult", timestamp: 25, confidence: 0.7, freshness: 25, active: true }, + { id: "relevantFile:branch:file-21:1", kind: "relevantFile", subject: "file-21", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-21.ts", scope: "branch", sourceEntryId: "t21", sourceType: "toolResult", timestamp: 26, confidence: 0.7, freshness: 26, active: true }, + { id: "relevantFile:branch:file-22:1", kind: "relevantFile", subject: "file-22", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-22.ts", scope: "branch", sourceEntryId: "t22", sourceType: "toolResult", timestamp: 27, confidence: 0.7, freshness: 27, active: true }, + ], + }, + }); + + const before = runtime.buildPacket(); + runtime.observeTokens(170_000); + const after = runtime.buildPacket(); + + assert.ok(after.estimatedTokens < before.estimatedTokens); +}); + +test("runtime recomputes lastZone when setContextWindow and setMode change policy", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.observeTokens(150_000); + assert.equal(runtime.getSnapshot().lastZone, "red"); + + runtime.setContextWindow(300_000); + assert.equal(runtime.getSnapshot().lastZone, "green"); + + runtime.setMode("aggressive"); + assert.equal(runtime.getSnapshot().lastZone, "yellow"); +}); + +test("restore recomputes lastZone against the receiving runtime policy", () => { + const source = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + source.observeTokens(150_000); + + const target = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + target.restore(source.getSnapshot()); + + assert.equal(target.getSnapshot().lastZone, "green"); +}); + +test("restore resets legacy lastZone when the snapshot lacks lastObservedTokens", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + + runtime.restore({ + mode: "balanced", + lastZone: "red", + ledger: { items: [], rollingSummary: "" }, + }); + + const restored = runtime.getSnapshot(); + assert.equal(restored.lastZone, "green"); + assert.equal(restored.lastObservedTokens, undefined); +}); + +test("legacy snapshot deserialization plus restore clears stale lastZone", () => { + const snapshot = deserializeLatestSnapshot([ + { + type: "custom", + customType: SNAPSHOT_ENTRY_TYPE, + data: { + mode: "balanced", + lastZone: "red", + ledger: { items: [], rollingSummary: "" }, + }, + }, + ]); + + assert.ok(snapshot); + + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + runtime.restore(snapshot); + + assert.equal(runtime.getSnapshot().lastZone, "green"); +}); + +test("getPolicy returns a clone and restore detaches from external snapshot objects", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + const policy = runtime.getPolicy(); + policy.packetTokenCap = 1; + policy.redAtTokens = 1; + + const currentPolicy = runtime.getPolicy(); + assert.equal(currentPolicy.packetTokenCap, 1_200); + assert.equal(currentPolicy.redAtTokens, 140_000); + + const snapshot = runtime.getSnapshot(); + snapshot.mode = "aggressive"; + snapshot.ledger.rollingSummary = "before restore"; + + runtime.restore(snapshot); + + snapshot.mode = "conservative"; + snapshot.ledger.rollingSummary = "mutated after restore"; + + const restored = runtime.getSnapshot(); + assert.equal(restored.mode, "aggressive"); + assert.equal(restored.ledger.rollingSummary, "before restore"); +}); diff --git a/.pi/agent/extensions/context-manager/src/runtime.ts b/.pi/agent/extensions/context-manager/src/runtime.ts new file mode 100644 index 0000000..9a4ef50 --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/runtime.ts @@ -0,0 +1,127 @@ +import { adjustPolicyForZone, resolvePolicy, zoneForTokens, type ContextMode, type Policy } from "./config.ts"; +import { extractCandidates, type TranscriptSlice } from "./extract.ts"; +import { createEmptyLedger, getActiveItems, mergeCandidates } from "./ledger.ts"; +import { buildContextPacket } from "./packet.ts"; +import { buildBranchSummary, buildCompactionSummary, buildResumePacket as renderResumePacket } from "./summaries.ts"; +import type { RuntimeSnapshot } from "./persist.ts"; + +function syncSnapshotZone(snapshot: RuntimeSnapshot, policy: Policy): RuntimeSnapshot { + if (snapshot.lastObservedTokens === undefined) { + return snapshot.lastZone === "green" + ? snapshot + : { + ...snapshot, + lastZone: "green", + }; + } + + return { + ...snapshot, + lastZone: zoneForTokens(snapshot.lastObservedTokens, policy), + }; +} + +export function createContextManagerRuntime(input: { mode?: ContextMode; contextWindow: number }) { + let contextWindow = input.contextWindow; + let policy = resolvePolicy({ mode: input.mode ?? "balanced", contextWindow }); + let snapshot: RuntimeSnapshot = { + mode: policy.mode, + lastZone: "green", + ledger: createEmptyLedger(), + }; + + function applyPolicy(nextPolicy: Policy) { + policy = nextPolicy; + snapshot = syncSnapshotZone(snapshot, policy); + } + + function hasSessionGoal() { + return getActiveItems(snapshot.ledger, "goal").some((item) => item.scope === "session" && item.subject === "root-goal"); + } + + function ingest(slice: TranscriptSlice) { + snapshot = { + ...snapshot, + ledger: mergeCandidates(snapshot.ledger, extractCandidates(slice, { hasSessionGoal: hasSessionGoal() })), + }; + } + + function observeTokens(tokens: number) { + snapshot = { + ...snapshot, + lastObservedTokens: tokens, + lastZone: zoneForTokens(tokens, policy), + }; + } + + function buildPacket() { + return buildContextPacket(snapshot.ledger, adjustPolicyForZone(policy, snapshot.lastZone)); + } + + function mergeArtifact(role: "compaction" | "branchSummary", text: string, entryId: string, timestamp: number) { + snapshot = { + ...snapshot, + lastCompactionSummary: role === "compaction" ? text : snapshot.lastCompactionSummary, + lastBranchSummary: role === "branchSummary" ? text : snapshot.lastBranchSummary, + ledger: mergeCandidates(snapshot.ledger, extractCandidates({ entryId, role, text, timestamp }, { hasSessionGoal: hasSessionGoal() })), + }; + } + + function recordCompactionSummary(text: string, entryId = `compaction-${Date.now()}`, timestamp = Date.now()) { + mergeArtifact("compaction", text, entryId, timestamp); + } + + function recordBranchSummary(text: string, entryId = `branch-${Date.now()}`, timestamp = Date.now()) { + mergeArtifact("branchSummary", text, entryId, timestamp); + } + + function buildResumePacket() { + const lines: string[] = []; + + if (snapshot.lastCompactionSummary) { + lines.push("## Latest compaction handoff", snapshot.lastCompactionSummary, ""); + } + + if (snapshot.lastBranchSummary) { + lines.push("## Latest branch handoff", snapshot.lastBranchSummary, ""); + } + + const livePacket = renderResumePacket(snapshot.ledger); + if (livePacket) { + lines.push(livePacket); + } + + return lines.join("\n").trim(); + } + + function setContextWindow(nextContextWindow: number) { + contextWindow = Math.max(nextContextWindow, 50_000); + applyPolicy(resolvePolicy({ mode: snapshot.mode, contextWindow })); + } + + function setMode(mode: ContextMode) { + snapshot = { ...snapshot, mode }; + applyPolicy(resolvePolicy({ mode, contextWindow })); + } + + function restore(next: RuntimeSnapshot) { + snapshot = structuredClone(next); + applyPolicy(resolvePolicy({ mode: snapshot.mode, contextWindow })); + } + + return { + ingest, + observeTokens, + buildPacket, + buildCompactionSummary: () => buildCompactionSummary(snapshot.ledger), + buildBranchSummary: (label: string) => buildBranchSummary(snapshot.ledger, label), + buildResumePacket, + recordCompactionSummary, + recordBranchSummary, + setContextWindow, + setMode, + getPolicy: () => structuredClone(policy), + getSnapshot: () => structuredClone(snapshot), + restore, + }; +} diff --git a/.pi/agent/extensions/context-manager/src/summaries.test.ts b/.pi/agent/extensions/context-manager/src/summaries.test.ts new file mode 100644 index 0000000..ee76a5f --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/summaries.test.ts @@ -0,0 +1,139 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createEmptyLedger, mergeCandidates } from "./ledger.ts"; +import { + buildBranchSummary, + buildBranchSummaryFromEntries, + buildCompactionSummary, + buildCompactionSummaryFromPreparation, + buildResumePacket, +} from "./summaries.ts"; + +const ledger = mergeCandidates(createEmptyLedger(), [ + { kind: "goal", subject: "root-goal", text: "Build a pi context manager", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 1 }, + { kind: "constraint", subject: "must-u1-0", text: "Must adapt to the active model context window.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 0.9 }, + { kind: "decision", subject: "decision-a1-0", text: "Keep the MVP quiet.", scope: "branch", sourceEntryId: "a1", sourceType: "assistant", timestamp: 2, confidence: 0.9 }, + { kind: "activeTask", subject: "next-step-a2-0", text: "Wire hooks into pi.", scope: "branch", sourceEntryId: "a2", sourceType: "assistant", timestamp: 3, confidence: 0.8 }, + { kind: "relevantFile", subject: "runtime-ts", text: "src/runtime.ts", scope: "branch", sourceEntryId: "a3", sourceType: "assistant", timestamp: 4, confidence: 0.7 }, +]); + +test("buildCompactionSummary renders the exact section order and content", () => { + const summary = buildCompactionSummary(ledger); + + assert.equal( + summary, + [ + "## Goal", + "- Build a pi context manager", + "", + "## Constraints", + "- Must adapt to the active model context window.", + "", + "## Decisions", + "- Keep the MVP quiet.", + "", + "## Active work", + "- Wire hooks into pi.", + "", + "## Relevant files", + "- src/runtime.ts", + "", + "## Next steps", + "- Wire hooks into pi.", + ].join("\n") + ); +}); + +test("buildBranchSummary renders the handoff header and sections in order", () => { + const summary = buildBranchSummary(ledger, "experimental branch"); + + assert.equal( + summary, + [ + "# Handoff for experimental branch", + "", + "## Goal", + "- Build a pi context manager", + "", + "## Decisions", + "- Keep the MVP quiet.", + "", + "## Active work", + "- Wire hooks into pi.", + "", + "## Relevant files", + "- src/runtime.ts", + ].join("\n") + ); +}); + +test("buildCompactionSummaryFromPreparation uses preparation messages, previous summary, file ops, and focus text", () => { + const previousSummary = [ + "## Goal", + "- Ship the context manager extension", + "", + "## Key Decisions", + "- Keep the public API stable.", + ].join("\n"); + + const summary = buildCompactionSummaryFromPreparation({ + messagesToSummarize: [ + { role: "user", content: "Decision: keep compaction summaries deterministic", timestamp: 1 }, + { role: "assistant", content: [{ type: "text", text: "Blocked: verify /tree replaceInstructions behavior" }], timestamp: 2 }, + ], + turnPrefixMessages: [ + { role: "toolResult", toolName: "read", content: [{ type: "text", text: "Opened .pi/agent/extensions/context-manager/index.ts" }], isError: false, timestamp: 3 }, + ], + previousSummary, + fileOps: { + readFiles: [".pi/agent/extensions/context-manager/index.ts"], + modifiedFiles: [".pi/agent/extensions/context-manager/src/summaries.ts"], + }, + customInstructions: "Focus on decisions, blockers, and relevant files.", + }); + + assert.match(summary, /## Key Decisions/); + assert.match(summary, /keep compaction summaries deterministic/); + assert.match(summary, /## Open questions and blockers/); + assert.match(summary, /verify \/tree replaceInstructions behavior/); + assert.match(summary, /[\s\S]*index.ts[\s\S]*<\/read-files>/); + assert.match(summary, /[\s\S]*src\/summaries.ts[\s\S]*<\/modified-files>/); +}); + +test("buildBranchSummaryFromEntries uses only the abandoned branch entries and custom focus", () => { + const summary = buildBranchSummaryFromEntries({ + branchLabel: "abandoned branch", + entriesToSummarize: [ + { type: "message", id: "user-1", parentId: null, timestamp: new Date(1).toISOString(), message: { role: "user", content: "Goal: explore tree handoff" } }, + { type: "message", id: "assistant-1", parentId: "user-1", timestamp: new Date(2).toISOString(), message: { role: "assistant", content: [{ type: "text", text: "Decision: do not leak branch-local goals" }] } }, + ], + customInstructions: "Focus on goals and decisions.", + replaceInstructions: false, + commonAncestorId: "root", + }); + + assert.match(summary, /# Handoff for abandoned branch/); + assert.match(summary, /explore tree handoff/); + assert.match(summary, /do not leak branch-local goals/); +}); + +test("buildResumePacket renders restart guidance in the expected order", () => { + const summary = buildResumePacket(ledger); + + assert.equal( + summary, + [ + "## Goal", + "- Build a pi context manager", + "", + "## Current task", + "- Wire hooks into pi.", + "", + "## Constraints", + "- Must adapt to the active model context window.", + "", + "## Key decisions", + "- Keep the MVP quiet.", + ].join("\n") + ); +}); diff --git a/.pi/agent/extensions/context-manager/src/summaries.ts b/.pi/agent/extensions/context-manager/src/summaries.ts new file mode 100644 index 0000000..b4a722b --- /dev/null +++ b/.pi/agent/extensions/context-manager/src/summaries.ts @@ -0,0 +1,251 @@ +import { createEmptyLedger, getActiveItems, mergeCandidates, type LedgerState } from "./ledger.ts"; +import { extractCandidates, type TranscriptSlice } from "./extract.ts"; + +function lines(title: string, items: string[]) { + if (items.length === 0) return [`## ${title}`, "- none", ""]; + return [`## ${title}`, ...items.map((item) => `- ${item}`), ""]; +} + +function isTextPart(part: unknown): part is { type: "text"; text?: string } { + return typeof part === "object" && part !== null && "type" in part && (part as { type?: unknown }).type === "text"; +} + +function toText(content: unknown): string { + if (typeof content === "string") { + return content; + } + + if (!Array.isArray(content)) { + return ""; + } + + return content + .map((part) => { + if (!isTextPart(part)) return ""; + return typeof part.text === "string" ? part.text : ""; + }) + .join("\n") + .trim(); +} + +type FocusKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; +type SummarySection = { kind: FocusKind; title: string; items: string[] }; + +function hasSessionGoal(ledger: LedgerState) { + return getActiveItems(ledger, "goal").some((item) => item.scope === "session" && item.subject === "root-goal"); +} + +function buildLedgerFromSlices(slices: TranscriptSlice[], previousSummary?: string) { + let ledger = createEmptyLedger(); + + if (previousSummary) { + ledger = mergeCandidates( + ledger, + extractCandidates( + { + entryId: "previous-summary", + role: "compaction", + text: previousSummary, + timestamp: 0, + }, + { hasSessionGoal: false }, + ), + ); + } + + for (const slice of slices) { + ledger = mergeCandidates(ledger, extractCandidates(slice, { hasSessionGoal: hasSessionGoal(ledger) })); + } + + return ledger; +} + +function parseFocus(customInstructions?: string): Set { + const text = (customInstructions ?? "").toLowerCase(); + const focus = new Set(); + + if (/\bgoal/.test(text)) focus.add("goal"); + if (/\bconstraint|preference/.test(text)) focus.add("constraint"); + if (/\bdecision/.test(text)) focus.add("decision"); + if (/\btask|next step|progress/.test(text)) focus.add("activeTask"); + if (/\bblocker|blocked|open question/.test(text)) focus.add("openQuestion"); + if (/\bfile|read-files|modified-files/.test(text)) focus.add("relevantFile"); + + return focus; +} + +function buildStructuredSections(ledger: LedgerState): SummarySection[] { + return [ + { kind: "goal", title: "Goal", items: getActiveItems(ledger, "goal").map((item) => item.text) }, + { + kind: "constraint", + title: "Constraints & Preferences", + items: getActiveItems(ledger, "constraint").map((item) => item.text), + }, + { + kind: "activeTask", + title: "Progress", + items: getActiveItems(ledger, "activeTask").map((item) => item.text), + }, + { + kind: "decision", + title: "Key Decisions", + items: getActiveItems(ledger, "decision").map((item) => item.text), + }, + { + kind: "openQuestion", + title: "Open questions and blockers", + items: getActiveItems(ledger, "openQuestion").map((item) => item.text), + }, + { + kind: "relevantFile", + title: "Critical Context", + items: getActiveItems(ledger, "relevantFile").map((item) => item.text), + }, + { + kind: "activeTask", + title: "Next Steps", + items: getActiveItems(ledger, "activeTask").map((item) => item.text), + }, + ]; +} + +function sortSectionsForFocus(sections: SummarySection[], focus: Set): SummarySection[] { + if (focus.size === 0) { + return sections; + } + + return [ + ...sections.filter((section) => focus.has(section.kind)), + ...sections.filter((section) => !focus.has(section.kind)), + ]; +} + +function unique(values: string[]) { + return [...new Set(values)]; +} + +function fileTag(name: "read-files" | "modified-files", values: string[]) { + if (values.length === 0) { + return [] as string[]; + } + + return [`<${name}>`, ...values, ``, ""]; +} + +function renderStructuredSummary( + ledger: LedgerState, + options?: { + header?: string; + focus?: Set; + readFiles?: string[]; + modifiedFiles?: string[]; + }, +) { + const sections = sortSectionsForFocus(buildStructuredSections(ledger), options?.focus ?? new Set()); + return [ + ...(options?.header ? [options.header, ""] : []), + ...sections.flatMap((section) => lines(section.title, section.items)), + ...fileTag("read-files", unique(options?.readFiles ?? [])), + ...fileTag("modified-files", unique(options?.modifiedFiles ?? [])), + ].join("\n").trim(); +} + +function messageToSlice(message: any, entryId: string, timestampFallback: number): TranscriptSlice | undefined { + if (!message || typeof message !== "object") { + return undefined; + } + + if (message.role !== "user" && message.role !== "assistant" && message.role !== "toolResult") { + return undefined; + } + + return { + entryId, + role: message.role, + text: toText(message.content), + timestamp: typeof message.timestamp === "number" ? message.timestamp : timestampFallback, + isError: message.role === "toolResult" ? message.isError : undefined, + }; +} + +export function buildCompactionSummary(ledger: LedgerState): string { + return [ + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Constraints", getActiveItems(ledger, "constraint").map((item) => item.text)), + ...lines("Decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ...lines("Active work", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Relevant files", getActiveItems(ledger, "relevantFile").map((item) => item.text)), + ...lines("Next steps", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildBranchSummary(ledger: LedgerState, branchLabel: string): string { + return [ + `# Handoff for ${branchLabel}`, + "", + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ...lines("Active work", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Relevant files", getActiveItems(ledger, "relevantFile").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildResumePacket(ledger: LedgerState): string { + return [ + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Current task", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Constraints", getActiveItems(ledger, "constraint").map((item) => item.text)), + ...lines("Key decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildCompactionSummaryFromPreparation(input: { + messagesToSummarize: any[]; + turnPrefixMessages: any[]; + previousSummary?: string; + fileOps?: { readFiles?: string[]; modifiedFiles?: string[] }; + customInstructions?: string; +}): string { + const slices = [...input.messagesToSummarize, ...input.turnPrefixMessages] + .map((message, index) => messageToSlice(message, `compaction-${index}`, index)) + .filter((slice): slice is TranscriptSlice => Boolean(slice)); + + const ledger = buildLedgerFromSlices(slices, input.previousSummary); + return renderStructuredSummary(ledger, { + focus: parseFocus(input.customInstructions), + readFiles: input.fileOps?.readFiles, + modifiedFiles: input.fileOps?.modifiedFiles, + }); +} + +export function buildBranchSummaryFromEntries(input: { + branchLabel: string; + entriesToSummarize: Array<{ type: string; id: string; timestamp: string; message?: any; summary?: string }>; + customInstructions?: string; + replaceInstructions?: boolean; + commonAncestorId?: string | null; +}): string { + const slices = input.entriesToSummarize.flatMap((entry) => { + if (entry.type === "message") { + const slice = messageToSlice(entry.message, entry.id, Date.parse(entry.timestamp)); + return slice ? [slice] : []; + } + + if (entry.type === "compaction" && typeof entry.summary === "string") { + return [{ entryId: entry.id, role: "compaction", text: entry.summary, timestamp: Date.parse(entry.timestamp) } satisfies TranscriptSlice]; + } + + if (entry.type === "branch_summary" && typeof entry.summary === "string") { + return [{ entryId: entry.id, role: "branchSummary", text: entry.summary, timestamp: Date.parse(entry.timestamp) } satisfies TranscriptSlice]; + } + + return []; + }); + + const ledger = buildLedgerFromSlices(slices); + return renderStructuredSummary(ledger, { + header: `# Handoff for ${input.branchLabel}`, + focus: parseFocus(input.customInstructions), + }); +} diff --git a/.pi/agent/extensions/dev-tools/index.ts b/.pi/agent/extensions/dev-tools/index.ts new file mode 100644 index 0000000..f724dcc --- /dev/null +++ b/.pi/agent/extensions/dev-tools/index.ts @@ -0,0 +1,50 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { createCommandFormatterRunner } from "./src/formatting/command-runner.ts"; +import { createCommandDiagnosticsBackend } from "./src/diagnostics/command-backend.ts"; +import { createLspClientManager } from "./src/diagnostics/lsp-client.ts"; +import { createSetupSuggestTool } from "./src/tools/setup-suggest.ts"; +import { probeProject } from "./src/project-probe.ts"; +import { createFormattedWriteTool } from "./src/tools/write.ts"; +import { createFormattedEditTool } from "./src/tools/edit.ts"; +import { createDevToolsRuntime } from "./src/runtime.ts"; + +export default function devTools(pi: ExtensionAPI) { + const cwd = process.cwd(); + const agentDir = process.env.PI_CODING_AGENT_DIR ?? `${process.env.HOME}/.pi/agent`; + + const runtime = createDevToolsRuntime({ + cwd, + agentDir, + formatterRunner: createCommandFormatterRunner({ + execCommand: async (command, args, options) => { + const result = await pi.exec(command, args, { timeout: options.timeout }); + return { code: result.code ?? 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }; + }, + }), + commandBackend: createCommandDiagnosticsBackend({ + execCommand: async (command, args, options) => { + const result = await pi.exec(command, args, { timeout: options.timeout }); + return { code: result.code ?? 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }; + }, + }), + lspBackend: createLspClientManager(), + probeProject, + }); + + pi.registerTool(createFormattedEditTool(cwd, runtime)); + pi.registerTool(createFormattedWriteTool(cwd, runtime)); + pi.registerTool(createSetupSuggestTool({ + suggestSetup: async () => { + const probe = await probeProject({ cwd }); + return probe.summary; + }, + })); + + pi.on("before_agent_start", async (event) => { + const block = runtime.getPromptBlock(); + if (!block) return; + return { + systemPrompt: `${event.systemPrompt}\n\n${block}`, + }; + }); +} diff --git a/.pi/agent/extensions/dev-tools/package-lock.json b/.pi/agent/extensions/dev-tools/package-lock.json new file mode 100644 index 0000000..baa554a --- /dev/null +++ b/.pi/agent/extensions/dev-tools/package-lock.json @@ -0,0 +1,4386 @@ +{ + "name": "pi-dev-tools-extension", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-dev-tools-extension", + "version": "0.0.0", + "dependencies": { + "@sinclair/typebox": "^0.34.49", + "picomatch": "^4.0.2", + "vscode-jsonrpc": "^8.2.1" + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-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.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1027.0.tgz", + "integrity": "sha512-Qcda5Z5Vb3LPVt7zNycEiiAo9Blk0JpEPJwz/sUBJby6/0zvTlo+/FIXlwYZ3TJHSgKCYiCaBqAB0WRlWDfLfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/eventstream-handler-node": "^3.972.13", + "@aws-sdk/middleware-eventstream": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/token-providers": "3.1027.0", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", + "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", + "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", + "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-format-url": "^3.972.9", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1027.0.tgz", + "integrity": "sha512-mI3Jm14cM5sNKc7aNX3cqJe/rFQ2Zzx7x5W8WUtxj2lVxcH2RGYhqI3hK9nnImY6Ec5MeGXCVPjl/q6Mz5HmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", + "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.49.0.tgz", + "integrity": "sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", + "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/jiti": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", + "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "std-env": "^3.10.0", + "yoctocolors": "^2.1.2" + }, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@mariozechner/pi-agent-core": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.66.1.tgz", + "integrity": "sha512-Nj54A7SuB/EQi8r3Gs+glFOr9wz/a9uxYFf0pCLf2DE7VmzA9O7WSejrvArna17K6auftLSdNyRRe2bIO0qezg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/pi-ai": "^0.66.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.66.1.tgz", + "integrity": "sha512-7IZHvpsFdKEBkTmjNrdVL7JLUJVIpha6bwTr12cZ5XyDrxij06wP6Ncpnf4HT5BXAzD5w2JnoqTOSbMEIZj3dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "1.14.1", + "@sinclair/typebox": "^0.34.41", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-coding-agent": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.66.1.tgz", + "integrity": "sha512-cNmatT+5HvYzQ78cRhRih00wCeUTH/fFx9ecJh5AbN7axgWU+bwiZYy0cjrTsGVgMGF4xMYlPRn/Nze9JEB+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/jiti": "^2.6.2", + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@silvia-odwyer/photon-node": "^0.3.4", + "ajv": "^8.17.1", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "undici": "^7.19.1", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.2" + } + }, + "node_modules/@mariozechner/pi-tui": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.66.1.tgz", + "integrity": "sha512-hNFN42ebjwtfGooqoUwM+QaPR1XCyqPuueuP3aLOWS1bZ2nZP/jq8MBuGNrmMw1cgiDcotvOlSNj3BatzEOGsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", + "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "dev": true, + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "license": "MIT" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.6.tgz", + "integrity": "sha512-WQBpM5uo74UQ17UpsFN+PUOrQQg4/nYdey4SGVluQun2drYYfePziLLWdSmFb4wSdWlJC1aimXQnjhPCheRKuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", + "integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "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 + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/.pi/agent/extensions/dev-tools/package.json b/.pi/agent/extensions/dev-tools/package.json new file mode 100644 index 0000000..831e6d6 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/package.json @@ -0,0 +1,23 @@ +{ + "name": "pi-dev-tools-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test": "tsx --test src/*.test.ts src/**/*.test.ts" + }, + "pi": { + "extensions": ["./index.ts"] + }, + "dependencies": { + "@sinclair/typebox": "^0.34.49", + "picomatch": "^4.0.2", + "vscode-jsonrpc": "^8.2.1" + }, + "devDependencies": { + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } +} diff --git a/.pi/agent/extensions/dev-tools/src/config.test.ts b/.pi/agent/extensions/dev-tools/src/config.test.ts new file mode 100644 index 0000000..efb7d0c --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/config.test.ts @@ -0,0 +1,37 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mergeDevToolsConfig } from "./config.ts"; + +test("mergeDevToolsConfig lets project defaults override global defaults and replace same-name profiles", () => { + const merged = mergeDevToolsConfig( + { + defaults: { formatTimeoutMs: 8000, maxDiagnosticsPerFile: 10 }, + profiles: [ + { + name: "typescript", + match: ["**/*.ts"], + workspaceRootMarkers: ["package.json"], + formatter: { kind: "command", command: ["prettier", "--write", "{file}"] }, + diagnostics: [], + }, + ], + }, + { + defaults: { formatTimeoutMs: 3000 }, + profiles: [ + { + name: "typescript", + match: ["src/**/*.ts"], + workspaceRootMarkers: ["tsconfig.json"], + formatter: { kind: "command", command: ["biome", "format", "--write", "{file}"] }, + diagnostics: [], + }, + ], + }, + ); + + assert.equal(merged.defaults.formatTimeoutMs, 3000); + assert.equal(merged.defaults.maxDiagnosticsPerFile, 10); + assert.deepEqual(merged.profiles.map((profile) => profile.name), ["typescript"]); + assert.deepEqual(merged.profiles[0]?.match, ["src/**/*.ts"]); +}); diff --git a/.pi/agent/extensions/dev-tools/src/config.ts b/.pi/agent/extensions/dev-tools/src/config.ts new file mode 100644 index 0000000..464fb05 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/config.ts @@ -0,0 +1,38 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { Value } from "@sinclair/typebox/value"; +import { DevToolsConfigSchema, type DevToolsConfig } from "./schema.ts"; + +export function mergeDevToolsConfig(globalConfig?: DevToolsConfig, projectConfig?: DevToolsConfig): DevToolsConfig { + const defaults = { + ...(globalConfig?.defaults ?? {}), + ...(projectConfig?.defaults ?? {}), + }; + + const globalProfiles = new Map((globalConfig?.profiles ?? []).map((profile) => [profile.name, profile])); + const mergedProfiles = [...(projectConfig?.profiles ?? [])]; + + for (const profile of globalProfiles.values()) { + if (!mergedProfiles.some((candidate) => candidate.name === profile.name)) { + mergedProfiles.push(profile); + } + } + + return { defaults, profiles: mergedProfiles }; +} + +function readConfigIfPresent(path: string): DevToolsConfig | undefined { + if (!existsSync(path)) return undefined; + const parsed = JSON.parse(readFileSync(path, "utf8")); + if (!Value.Check(DevToolsConfigSchema, parsed)) { + const [firstError] = [...Value.Errors(DevToolsConfigSchema, parsed)]; + throw new Error(`Invalid dev-tools config at ${path}: ${firstError?.message ?? "validation failed"}`); + } + return parsed as DevToolsConfig; +} + +export function loadDevToolsConfig(cwd: string, agentDir: string): DevToolsConfig | undefined { + const globalPath = resolve(agentDir, "dev-tools.json"); + const projectPath = resolve(cwd, ".pi/dev-tools.json"); + return mergeDevToolsConfig(readConfigIfPresent(globalPath), readConfigIfPresent(projectPath)); +} diff --git a/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.test.ts b/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.test.ts new file mode 100644 index 0000000..d71b7d1 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.test.ts @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createCommandDiagnosticsBackend } from "./command-backend.ts"; + +test("eslint-json parser returns normalized diagnostics", async () => { + const backend = createCommandDiagnosticsBackend({ + execCommand: async () => ({ + code: 1, + stdout: JSON.stringify([ + { + filePath: "/repo/src/app.ts", + messages: [ + { + ruleId: "no-console", + severity: 2, + message: "Unexpected console statement.", + line: 2, + column: 3, + }, + ], + }, + ]), + stderr: "", + }), + }); + + const result = await backend.collect({ + absolutePath: "/repo/src/app.ts", + workspaceRoot: "/repo", + backend: { + kind: "command", + parser: "eslint-json", + command: ["eslint", "--format", "json", "{file}"], + }, + }); + + assert.equal(result.status, "ok"); + assert.equal(result.items[0]?.severity, "error"); + assert.equal(result.items[0]?.message, "Unexpected console statement."); +}); diff --git a/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.ts b/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.ts new file mode 100644 index 0000000..1573049 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/diagnostics/command-backend.ts @@ -0,0 +1,45 @@ +import type { DiagnosticsConfig } from "../schema.ts"; +import type { DiagnosticsState, NormalizedDiagnostic } from "./types.ts"; + +function parseEslintJson(stdout: string): NormalizedDiagnostic[] { + const parsed = JSON.parse(stdout) as Array; + return parsed.flatMap((entry) => + (entry.messages ?? []).map((message: any) => ({ + severity: message.severity === 2 ? "error" : "warning", + message: message.message, + line: message.line, + column: message.column, + source: "eslint", + code: message.ruleId ?? undefined, + })), + ); +} + +export function createCommandDiagnosticsBackend(deps: { + execCommand: ( + command: string, + args: string[], + options: { cwd: string; timeout?: number }, + ) => Promise<{ code: number; stdout: string; stderr: string }>; +}) { + return { + async collect(input: { + absolutePath: string; + workspaceRoot: string; + backend: Extract; + timeoutMs?: number; + }): Promise { + const [command, ...args] = input.backend.command.map((part) => part.replaceAll("{file}", input.absolutePath)); + const result = await deps.execCommand(command, args, { cwd: input.workspaceRoot, timeout: input.timeoutMs }); + + try { + if (input.backend.parser === "eslint-json") { + return { status: "ok", items: parseEslintJson(result.stdout) }; + } + return { status: "unavailable", items: [], message: `Unsupported diagnostics parser: ${input.backend.parser}` }; + } catch (error) { + return { status: "unavailable", items: [], message: (error as Error).message }; + } + }, + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.test.ts b/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.test.ts new file mode 100644 index 0000000..18918a0 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.test.ts @@ -0,0 +1,41 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createLspClientManager } from "./lsp-client.ts"; + +test("collectForFile sends initialize + didOpen and resolves publishDiagnostics", async () => { + const notifications: Array<{ method: string; params: any }> = []; + + const manager = createLspClientManager({ + createConnection: async () => ({ + async initialize() {}, + async openTextDocument(params) { + notifications.push({ method: "textDocument/didOpen", params }); + }, + async collectDiagnostics() { + return [ + { + severity: "error", + message: "Type 'number' is not assignable to type 'string'.", + line: 1, + column: 7, + source: "tsserver", + }, + ]; + }, + async dispose() {}, + }), + }); + + const result = await manager.collectForFile({ + key: "typescript:/repo", + absolutePath: "/repo/src/app.ts", + workspaceRoot: "/repo", + languageId: "typescript", + text: "const x: string = 1\n", + command: ["typescript-language-server", "--stdio"], + }); + + assert.equal(result.status, "ok"); + assert.equal(result.items[0]?.source, "tsserver"); + assert.equal(notifications[0]?.method, "textDocument/didOpen"); +}); diff --git a/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.ts b/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.ts new file mode 100644 index 0000000..9b60626 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/diagnostics/lsp-client.ts @@ -0,0 +1,102 @@ +import { spawn } from "node:child_process"; +import { pathToFileURL } from "node:url"; +import * as rpc from "vscode-jsonrpc/node"; +import type { DiagnosticsState } from "./types.ts"; + +const INITIALIZE = new rpc.RequestType("initialize"); +const DID_OPEN = new rpc.NotificationType("textDocument/didOpen"); +const INITIALIZED = new rpc.NotificationType("initialized"); +const PUBLISH_DIAGNOSTICS = new rpc.NotificationType("textDocument/publishDiagnostics"); + +type LspConnection = { + initialize(): Promise; + openTextDocument(params: any): Promise; + collectDiagnostics(): Promise; + dispose(): Promise; +}; + +export function createLspClientManager(deps: { + createConnection?: (input: { workspaceRoot: string; command: string[] }) => Promise; +} = {}) { + const clients = new Map(); + + async function defaultCreateConnection(input: { workspaceRoot: string; command: string[] }): Promise { + const [command, ...args] = input.command; + const child = spawn(command, args, { + cwd: input.workspaceRoot, + stdio: ["pipe", "pipe", "pipe"], + }); + + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(child.stdout), + new rpc.StreamMessageWriter(child.stdin), + ); + + let lastDiagnostics: DiagnosticsState["items"] = []; + connection.onNotification(PUBLISH_DIAGNOSTICS, (params: any) => { + lastDiagnostics = (params.diagnostics ?? []).map((diagnostic: any) => ({ + severity: diagnostic.severity === 1 ? "error" : diagnostic.severity === 2 ? "warning" : "info", + message: diagnostic.message, + line: diagnostic.range?.start?.line !== undefined ? diagnostic.range.start.line + 1 : undefined, + column: diagnostic.range?.start?.character !== undefined ? diagnostic.range.start.character + 1 : undefined, + source: diagnostic.source ?? "lsp", + code: diagnostic.code ? String(diagnostic.code) : undefined, + })); + }); + + connection.listen(); + await connection.sendRequest(INITIALIZE, { + processId: process.pid, + rootUri: pathToFileURL(input.workspaceRoot).href, + capabilities: {}, + }); + connection.sendNotification(INITIALIZED, {}); + + return { + async initialize() {}, + async openTextDocument(params: any) { + connection.sendNotification(DID_OPEN, params); + }, + async collectDiagnostics() { + await new Promise((resolve) => setTimeout(resolve, 100)); + return lastDiagnostics; + }, + async dispose() { + connection.dispose(); + child.kill(); + }, + }; + } + + return { + async collectForFile(input: { + key: string; + absolutePath: string; + workspaceRoot: string; + languageId: string; + text: string; + command: string[]; + }): Promise { + let client = clients.get(input.key); + if (!client) { + client = await (deps.createConnection ?? defaultCreateConnection)({ + workspaceRoot: input.workspaceRoot, + command: input.command, + }); + clients.set(input.key, client); + await client.initialize(); + } + + await client.openTextDocument({ + textDocument: { + uri: pathToFileURL(input.absolutePath).href, + languageId: input.languageId, + version: 1, + text: input.text, + }, + }); + + return { status: "ok", items: await client.collectDiagnostics() }; + }, + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/diagnostics/types.ts b/.pi/agent/extensions/dev-tools/src/diagnostics/types.ts new file mode 100644 index 0000000..f92f9f7 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/diagnostics/types.ts @@ -0,0 +1,19 @@ +export interface NormalizedDiagnostic { + severity: "error" | "warning" | "info"; + message: string; + line?: number; + column?: number; + source: string; + code?: string; +} + +export interface DiagnosticsState { + status: "ok" | "unavailable"; + items: NormalizedDiagnostic[]; + message?: string; +} + +export interface CapabilityGap { + path: string; + message: string; +} diff --git a/.pi/agent/extensions/dev-tools/src/extension.test.ts b/.pi/agent/extensions/dev-tools/src/extension.test.ts new file mode 100644 index 0000000..e27e9af --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/extension.test.ts @@ -0,0 +1,16 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import devToolsExtension from "../index.ts"; + +test("the extension entrypoint registers edit, write, and setup suggestion tools", () => { + const registeredTools: string[] = []; + + devToolsExtension({ + registerTool(tool: { name: string }) { + registeredTools.push(tool.name); + }, + on() {}, + } as any); + + assert.deepEqual(registeredTools.sort(), ["dev_tools_suggest_setup", "edit", "write"]); +}); diff --git a/.pi/agent/extensions/dev-tools/src/formatting/command-runner.test.ts b/.pi/agent/extensions/dev-tools/src/formatting/command-runner.test.ts new file mode 100644 index 0000000..a0d564f --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/formatting/command-runner.test.ts @@ -0,0 +1,27 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createCommandFormatterRunner } from "./command-runner.ts"; + +test("formatFile expands {file} and executes in the workspace root", async () => { + let captured: { command: string; args: string[]; cwd?: string } | undefined; + + const runner = createCommandFormatterRunner({ + execCommand: async (command, args, options) => { + captured = { command, args, cwd: options.cwd }; + return { code: 0, stdout: "", stderr: "" }; + }, + }); + + const result = await runner.formatFile({ + absolutePath: "/repo/src/app.ts", + workspaceRoot: "/repo", + formatter: { kind: "command", command: ["biome", "format", "--write", "{file}"] }, + }); + + assert.equal(result.status, "formatted"); + assert.deepEqual(captured, { + command: "biome", + args: ["format", "--write", "/repo/src/app.ts"], + cwd: "/repo", + }); +}); diff --git a/.pi/agent/extensions/dev-tools/src/formatting/command-runner.ts b/.pi/agent/extensions/dev-tools/src/formatting/command-runner.ts new file mode 100644 index 0000000..fac5211 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/formatting/command-runner.ts @@ -0,0 +1,33 @@ +import type { FormatterConfig } from "../schema.ts"; + +export function createCommandFormatterRunner(deps: { + execCommand: ( + command: string, + args: string[], + options: { cwd: string; timeout?: number }, + ) => Promise<{ code: number; stdout: string; stderr: string }>; +}) { + return { + async formatFile(input: { + absolutePath: string; + workspaceRoot: string; + formatter: FormatterConfig; + timeoutMs?: number; + }) { + const [command, ...args] = input.formatter.command.map((part) => part.replaceAll("{file}", input.absolutePath)); + const result = await deps.execCommand(command, args, { + cwd: input.workspaceRoot, + timeout: input.timeoutMs, + }); + + if (result.code !== 0) { + return { + status: "failed" as const, + message: (result.stderr || result.stdout || `formatter exited with ${result.code}`).trim(), + }; + } + + return { status: "formatted" as const }; + }, + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/profiles.test.ts b/.pi/agent/extensions/dev-tools/src/profiles.test.ts new file mode 100644 index 0000000..a57f1d0 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/profiles.test.ts @@ -0,0 +1,26 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolveProfileForPath } from "./profiles.ts"; + +test("resolveProfileForPath finds the first matching profile and nearest workspace root", () => { + const result = resolveProfileForPath( + { + defaults: {}, + profiles: [ + { + name: "typescript", + match: ["src/**/*.ts"], + workspaceRootMarkers: ["package.json", "tsconfig.json"], + formatter: { kind: "command", command: ["biome", "format", "--write", "{file}"] }, + diagnostics: [], + }, + ], + }, + "/repo/src/app.ts", + "/repo", + ["/repo/package.json", "/repo/src/app.ts"], + ); + + assert.equal(result?.profile.name, "typescript"); + assert.equal(result?.workspaceRoot, "/repo"); +}); diff --git a/.pi/agent/extensions/dev-tools/src/profiles.ts b/.pi/agent/extensions/dev-tools/src/profiles.ts new file mode 100644 index 0000000..b068766 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/profiles.ts @@ -0,0 +1,47 @@ +import { dirname, relative, resolve } from "node:path"; +import picomatch from "picomatch"; +import type { DevToolsConfig, DevToolsProfile } from "./schema.ts"; + +export interface ResolvedProfileMatch { + profile: DevToolsProfile; + workspaceRoot: string; +} + +export function resolveProfileForPath( + config: DevToolsConfig, + absolutePath: string, + cwd: string, + knownPaths: string[] = [], +): ResolvedProfileMatch | undefined { + const normalizedPath = resolve(absolutePath); + const relativePath = relative(cwd, normalizedPath).replace(/\\/g, "/"); + + for (const profile of config.profiles) { + if (!profile.match.some((pattern) => picomatch(pattern)(relativePath))) { + continue; + } + + const workspaceRoot = findWorkspaceRoot(normalizedPath, cwd, profile.workspaceRootMarkers, knownPaths); + return { profile, workspaceRoot }; + } + + return undefined; +} + +function findWorkspaceRoot(filePath: string, cwd: string, markers: string[], knownPaths: string[]): string { + let current = dirname(filePath); + const root = resolve(cwd); + + while (current.startsWith(root)) { + for (const marker of markers) { + if (knownPaths.includes(resolve(current, marker))) { + return current; + } + } + const next = dirname(current); + if (next === current) break; + current = next; + } + + return root; +} diff --git a/.pi/agent/extensions/dev-tools/src/project-probe.test.ts b/.pi/agent/extensions/dev-tools/src/project-probe.test.ts new file mode 100644 index 0000000..a9e795b --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/project-probe.test.ts @@ -0,0 +1,14 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { probeProject } from "./project-probe.ts"; + +test("probeProject recognizes a TypeScript workspace and suggests biome + tsserver", async () => { + const result = await probeProject({ + cwd: "/repo", + exists: async (path) => ["/repo/package.json", "/repo/tsconfig.json"].includes(path), + }); + + assert.equal(result.ecosystem, "typescript"); + assert.match(result.summary, /Biome/); + assert.match(result.summary, /typescript-language-server/); +}); diff --git a/.pi/agent/extensions/dev-tools/src/project-probe.ts b/.pi/agent/extensions/dev-tools/src/project-probe.ts new file mode 100644 index 0000000..2735832 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/project-probe.ts @@ -0,0 +1,68 @@ +import { access } from "node:fs/promises"; +import { resolve } from "node:path"; + +export interface ProjectProbeResult { + ecosystem: string; + summary: string; +} + +export async function probeProject(deps: { + cwd: string; + exists?: (path: string) => Promise; +}): Promise { + const exists = deps.exists ?? (async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } + }); + const cwd = resolve(deps.cwd); + + const hasPackageJson = await exists(resolve(cwd, "package.json")); + const hasTsconfig = await exists(resolve(cwd, "tsconfig.json")); + const hasPyproject = await exists(resolve(cwd, "pyproject.toml")); + const hasCargo = await exists(resolve(cwd, "Cargo.toml")); + const hasGoMod = await exists(resolve(cwd, "go.mod")); + + if (hasPackageJson && hasTsconfig) { + return { + ecosystem: "typescript", + summary: "TypeScript project detected. Recommended: Biome for formatting/linting and typescript-language-server for diagnostics.", + }; + } + + if (hasPyproject) { + return { + ecosystem: "python", + summary: "Python project detected. Recommended: Ruff for formatting/linting and basedpyright or pylsp for diagnostics.", + }; + } + + if (hasCargo) { + return { + ecosystem: "rust", + summary: "Rust project detected. Recommended: rustfmt + cargo clippy and rust-analyzer.", + }; + } + + if (hasGoMod) { + return { + ecosystem: "go", + summary: "Go project detected. Recommended: gofmt/goimports and gopls.", + }; + } + + if (hasPackageJson) { + return { + ecosystem: "javascript", + summary: "JavaScript project detected. Recommended: Biome or Prettier+ESLint, plus TypeScript language tooling if applicable.", + }; + } + + return { + ecosystem: "unknown", + summary: "No known project toolchain markers detected. Add dev-tools profiles for your formatter, linter, and language server.", + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/runtime.test.ts b/.pi/agent/extensions/dev-tools/src/runtime.test.ts new file mode 100644 index 0000000..55c756f --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/runtime.test.ts @@ -0,0 +1,44 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createDevToolsRuntime } from "./runtime.ts"; + +test("refreshDiagnostics falls back to command diagnostics when LSP is unavailable", async () => { + const runtime = createDevToolsRuntime({ + cwd: "/repo", + agentDir: "/agent", + loadConfig: () => ({ + defaults: { maxDiagnosticsPerFile: 5 }, + profiles: [ + { + name: "typescript", + match: ["src/**/*.ts"], + languageId: "typescript", + workspaceRootMarkers: ["package.json"], + formatter: { kind: "command", command: ["biome", "format", "--write", "{file}"] }, + diagnostics: [ + { kind: "lsp", command: ["typescript-language-server", "--stdio"] }, + { kind: "command", parser: "eslint-json", command: ["eslint", "--format", "json", "{file}"] }, + ], + }, + ], + }), + knownPaths: ["/repo/package.json"], + formatterRunner: { formatFile: async () => ({ status: "skipped" }) }, + lspBackend: { + collectForFile: async () => ({ status: "unavailable", items: [], message: "spawn ENOENT" }), + }, + commandBackend: { + collect: async () => ({ + status: "ok", + items: [{ severity: "error", message: "Unexpected console statement.", line: 2, column: 3, source: "eslint" }], + }), + }, + probeProject: async () => ({ ecosystem: "typescript", summary: "TypeScript project detected." }), + }); + + await runtime.refreshDiagnosticsForPath("/repo/src/app.ts", "console.log('x')\n"); + + const promptBlock = runtime.getPromptBlock() ?? ""; + assert.match(promptBlock, /Unexpected console statement/); + assert.match(promptBlock, /spawn ENOENT/); +}); diff --git a/.pi/agent/extensions/dev-tools/src/runtime.ts b/.pi/agent/extensions/dev-tools/src/runtime.ts new file mode 100644 index 0000000..c7b6a44 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/runtime.ts @@ -0,0 +1,134 @@ +import { readFile } from "node:fs/promises"; +import { loadDevToolsConfig } from "./config.ts"; +import type { CapabilityGap, DiagnosticsState } from "./diagnostics/types.ts"; +import { probeProject } from "./project-probe.ts"; +import { resolveProfileForPath } from "./profiles.ts"; +import { buildPromptBlock } from "./summary.ts"; + +export interface FormatResult { + status: "formatted" | "skipped" | "failed"; + message?: string; +} + +export interface DevToolsRuntime { + formatAfterMutation(absolutePath: string): Promise; + noteMutation(absolutePath: string, formatResult: FormatResult): void; + setDiagnostics(path: string, state: DiagnosticsState): void; + recordCapabilityGap(path: string, message: string): void; + getPromptBlock(): string | undefined; + refreshDiagnosticsForPath(absolutePath: string, text?: string): Promise; +} + +type LoadedConfig = ReturnType; + +export function createDevToolsRuntime(deps: { + cwd: string; + agentDir: string; + loadConfig?: () => LoadedConfig; + knownPaths?: string[]; + formatterRunner: { formatFile: (input: any) => Promise }; + lspBackend: { collectForFile: (input: any) => Promise }; + commandBackend: { collect: (input: any) => Promise }; + probeProject?: typeof probeProject; +}): DevToolsRuntime { + const diagnosticsByFile = new Map(); + const capabilityGaps: CapabilityGap[] = []; + const getConfig = () => deps.loadConfig?.() ?? loadDevToolsConfig(deps.cwd, deps.agentDir); + + function setDiagnostics(path: string, state: DiagnosticsState) { + diagnosticsByFile.set(path, state); + } + + function recordCapabilityGap(path: string, message: string) { + if (!capabilityGaps.some((gap) => gap.path === path && gap.message === message)) { + capabilityGaps.push({ path, message }); + } + } + + function getPromptBlock() { + const config = getConfig(); + const maxDiagnosticsPerFile = config?.defaults?.maxDiagnosticsPerFile ?? 10; + if (diagnosticsByFile.size === 0 && capabilityGaps.length === 0) return undefined; + return buildPromptBlock({ + maxDiagnosticsPerFile, + diagnosticsByFile, + capabilityGaps, + }); + } + + async function refreshDiagnosticsForPath(absolutePath: string, text?: string) { + const config = getConfig(); + if (!config) { + recordCapabilityGap(absolutePath, "No dev-tools config found."); + return; + } + + const match = resolveProfileForPath(config, absolutePath, deps.cwd, deps.knownPaths ?? []); + if (!match) { + const probe = await (deps.probeProject ?? probeProject)({ cwd: deps.cwd }); + recordCapabilityGap(absolutePath, `No profile matched. ${probe.summary}`); + return; + } + + const fileText = text ?? await readFile(absolutePath, "utf8"); + + for (const backend of match.profile.diagnostics) { + if (backend.kind === "lsp") { + const lspResult = await deps.lspBackend.collectForFile({ + key: `${match.profile.languageId ?? "plain"}:${match.workspaceRoot}`, + absolutePath, + workspaceRoot: match.workspaceRoot, + languageId: match.profile.languageId ?? "plaintext", + text: fileText, + command: backend.command, + }); + + if (lspResult.status === "ok") { + setDiagnostics(absolutePath, lspResult); + if (lspResult.message) recordCapabilityGap(absolutePath, lspResult.message); + return; + } + + recordCapabilityGap(absolutePath, lspResult.message ?? "LSP diagnostics unavailable."); + continue; + } + + const commandResult = await deps.commandBackend.collect({ + absolutePath, + workspaceRoot: match.workspaceRoot, + backend, + timeoutMs: config.defaults?.diagnosticTimeoutMs, + }); + + setDiagnostics(absolutePath, commandResult); + return; + } + + recordCapabilityGap(absolutePath, "No diagnostics backend succeeded."); + } + + return { + async formatAfterMutation(absolutePath: string) { + const config = getConfig(); + if (!config) return { status: "skipped" as const }; + const match = resolveProfileForPath(config, absolutePath, deps.cwd, deps.knownPaths ?? []); + if (!match?.profile.formatter) return { status: "skipped" as const }; + return deps.formatterRunner.formatFile({ + absolutePath, + workspaceRoot: match.workspaceRoot, + formatter: match.profile.formatter, + timeoutMs: config.defaults?.formatTimeoutMs, + }); + }, + noteMutation(absolutePath: string, formatResult: FormatResult) { + if (formatResult.status === "failed") { + recordCapabilityGap(absolutePath, formatResult.message ?? "Formatter failed."); + } + void refreshDiagnosticsForPath(absolutePath); + }, + setDiagnostics, + recordCapabilityGap, + getPromptBlock, + refreshDiagnosticsForPath, + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/schema.ts b/.pi/agent/extensions/dev-tools/src/schema.ts new file mode 100644 index 0000000..e809464 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/schema.ts @@ -0,0 +1,40 @@ +import { Type, type Static } from "@sinclair/typebox"; + +const CommandSchema = Type.Object({ + kind: Type.Literal("command"), + command: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), +}); + +const LspSchema = Type.Object({ + kind: Type.Literal("lsp"), + command: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), +}); + +const CommandDiagnosticsSchema = Type.Object({ + kind: Type.Literal("command"), + parser: Type.String({ minLength: 1 }), + command: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), +}); + +export const DevToolsProfileSchema = Type.Object({ + name: Type.String({ minLength: 1 }), + match: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), + languageId: Type.Optional(Type.String({ minLength: 1 })), + workspaceRootMarkers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), + formatter: Type.Optional(CommandSchema), + diagnostics: Type.Array(Type.Union([LspSchema, CommandDiagnosticsSchema])), +}); + +export const DevToolsConfigSchema = Type.Object({ + defaults: Type.Optional(Type.Object({ + formatTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })), + diagnosticTimeoutMs: Type.Optional(Type.Integer({ minimum: 1 })), + maxDiagnosticsPerFile: Type.Optional(Type.Integer({ minimum: 1 })), + })), + profiles: Type.Array(DevToolsProfileSchema, { minItems: 1 }), +}); + +export type DevToolsProfile = Static; +export type DevToolsConfig = Static; +export type FormatterConfig = NonNullable; +export type DiagnosticsConfig = DevToolsProfile["diagnostics"][number]; diff --git a/.pi/agent/extensions/dev-tools/src/summary.test.ts b/.pi/agent/extensions/dev-tools/src/summary.test.ts new file mode 100644 index 0000000..2043fca --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/summary.test.ts @@ -0,0 +1,27 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { buildPromptBlock } from "./summary.ts"; + +test("buildPromptBlock caps diagnostics per file and includes capability gaps", () => { + const block = buildPromptBlock({ + maxDiagnosticsPerFile: 1, + diagnosticsByFile: new Map([ + [ + "/repo/src/app.ts", + { + status: "ok", + items: [ + { severity: "error", message: "Unexpected console statement.", line: 2, column: 3, source: "eslint" }, + { severity: "warning", message: "Unused variable.", line: 4, column: 9, source: "eslint" }, + ], + }, + ], + ]), + capabilityGaps: [{ path: "/repo/src/app.ts", message: "Configured executable `eslint` not found in PATH." }], + }); + + assert.match(block, /app.ts: 1 error, 1 warning/); + assert.match(block, /Unexpected console statement/); + assert.doesNotMatch(block, /Unused variable/); + assert.match(block, /not found in PATH/); +}); diff --git a/.pi/agent/extensions/dev-tools/src/summary.ts b/.pi/agent/extensions/dev-tools/src/summary.ts new file mode 100644 index 0000000..c19f3fc --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/summary.ts @@ -0,0 +1,31 @@ +import type { CapabilityGap, DiagnosticsState } from "./diagnostics/types.ts"; + +export function buildPromptBlock(input: { + maxDiagnosticsPerFile: number; + diagnosticsByFile: Map; + capabilityGaps: CapabilityGap[]; +}) { + const lines = ["Current changed-file diagnostics:"]; + + for (const [path, state] of input.diagnosticsByFile) { + if (state.status === "unavailable") { + lines.push(`- ${path}: diagnostics unavailable (${state.message ?? "unknown error"})`); + continue; + } + + const errors = state.items.filter((item) => item.severity === "error"); + const warnings = state.items.filter((item) => item.severity === "warning"); + lines.push(`- ${path}: ${errors.length} error${errors.length === 1 ? "" : "s"}, ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`); + + for (const item of state.items.slice(0, input.maxDiagnosticsPerFile)) { + const location = item.line ? `:${item.line}${item.column ? `:${item.column}` : ""}` : ""; + lines.push(` - ${item.severity.toUpperCase()}${location} ${item.message}`); + } + } + + for (const gap of input.capabilityGaps) { + lines.push(`- setup gap for ${gap.path}: ${gap.message}`); + } + + return lines.join("\n"); +} diff --git a/.pi/agent/extensions/dev-tools/src/tools/edit.test.ts b/.pi/agent/extensions/dev-tools/src/tools/edit.test.ts new file mode 100644 index 0000000..474f4f4 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/edit.test.ts @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { createFormattedEditTool } from "./edit.ts"; + +test("edit applies the replacement and then formats the file", async () => { + const dir = await mkdtemp(join(tmpdir(), "dev-tools-edit-")); + const path = join(dir, "app.ts"); + await writeFile(path, "const x=1\n", "utf8"); + + const tool = createFormattedEditTool(dir, { + formatAfterMutation: async (absolutePath) => { + await writeFile(absolutePath, "const x = 2;\n", "utf8"); + return { status: "formatted" }; + }, + noteMutation() {}, + }); + + await tool.execute( + "tool-1", + { path, edits: [{ oldText: "const x=1", newText: "const x=2" }] }, + undefined, + undefined, + undefined, + ); + + assert.equal(await readFile(path, "utf8"), "const x = 2;\n"); +}); + +test("edit preserves the changed file when formatter fails", async () => { + const dir = await mkdtemp(join(tmpdir(), "dev-tools-edit-")); + const path = join(dir, "app.ts"); + await writeFile(path, "const x=1\n", "utf8"); + + const tool = createFormattedEditTool(dir, { + formatAfterMutation: async () => ({ status: "failed", message: "formatter not found" }), + noteMutation() {}, + }); + + await assert.rejects( + () => tool.execute( + "tool-1", + { path, edits: [{ oldText: "const x=1", newText: "const x=2" }] }, + undefined, + undefined, + undefined, + ), + /Auto-format failed/, + ); + + assert.equal(await readFile(path, "utf8"), "const x=2\n"); +}); diff --git a/.pi/agent/extensions/dev-tools/src/tools/edit.ts b/.pi/agent/extensions/dev-tools/src/tools/edit.ts new file mode 100644 index 0000000..87d6ba6 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/edit.ts @@ -0,0 +1,21 @@ +import { createEditToolDefinition } from "@mariozechner/pi-coding-agent"; +import { constants } from "node:fs"; +import { access, readFile, writeFile } from "node:fs/promises"; +import type { DevToolsRuntime } from "../runtime.ts"; + +export function createFormattedEditTool(cwd: string, runtime: DevToolsRuntime) { + return createEditToolDefinition(cwd, { + operations: { + access: (absolutePath) => access(absolutePath, constants.R_OK | constants.W_OK), + readFile: (absolutePath) => readFile(absolutePath), + writeFile: async (absolutePath, content) => { + await writeFile(absolutePath, content, "utf8"); + const formatResult = await runtime.formatAfterMutation(absolutePath); + runtime.noteMutation(absolutePath, formatResult); + if (formatResult.status === "failed") { + throw new Error(`Auto-format failed for ${absolutePath}: ${formatResult.message}`); + } + }, + }, + }); +} diff --git a/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.test.ts b/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.test.ts new file mode 100644 index 0000000..dd0c7c5 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.test.ts @@ -0,0 +1,15 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createSetupSuggestTool } from "./setup-suggest.ts"; + +test("dev_tools_suggest_setup returns a concrete recommendation string", async () => { + const tool = createSetupSuggestTool({ + suggestSetup: async () => "TypeScript project detected. Recommended: bunx biome init and npm i -D typescript-language-server.", + }); + + const result = await tool.execute("tool-1", {}, undefined, undefined, undefined); + const text = result.content[0]?.type === "text" ? result.content[0].text : ""; + + assert.match(text, /TypeScript project detected/); + assert.match(text, /biome/); +}); diff --git a/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.ts b/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.ts new file mode 100644 index 0000000..46487ff --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/setup-suggest.ts @@ -0,0 +1,17 @@ +import { Type } from "@sinclair/typebox"; + +export function createSetupSuggestTool(deps: { suggestSetup: () => Promise }) { + return { + name: "dev_tools_suggest_setup", + label: "Dev Tools Suggest Setup", + description: "Suggest formatter/linter/LSP setup for the current project.", + parameters: Type.Object({}), + async execute() { + const text = await deps.suggestSetup(); + return { + content: [{ type: "text" as const, text }], + details: { suggestion: text }, + }; + }, + }; +} diff --git a/.pi/agent/extensions/dev-tools/src/tools/write.test.ts b/.pi/agent/extensions/dev-tools/src/tools/write.test.ts new file mode 100644 index 0000000..ab04c59 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/write.test.ts @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { createFormattedWriteTool } from "./write.ts"; + +test("write keeps the file when auto-format fails", async () => { + const dir = await mkdtemp(join(tmpdir(), "dev-tools-write-")); + const path = join(dir, "app.ts"); + + const tool = createFormattedWriteTool(dir, { + formatAfterMutation: async () => ({ status: "failed", message: "biome missing" }), + noteMutation() {}, + }); + + await assert.rejects( + () => tool.execute("tool-1", { path, content: "const x=1\n" }, undefined, undefined, undefined), + /Auto-format failed/, + ); + + assert.equal(await readFile(path, "utf8"), "const x=1\n"); +}); + +test("write calls formatting immediately after writing", async () => { + const dir = await mkdtemp(join(tmpdir(), "dev-tools-write-")); + const path = join(dir, "app.ts"); + + const tool = createFormattedWriteTool(dir, { + formatAfterMutation: async (absolutePath) => { + await writeFile(absolutePath, "const x = 1;\n", "utf8"); + return { status: "formatted" }; + }, + noteMutation() {}, + }); + + await tool.execute("tool-1", { path, content: "const x=1\n" }, undefined, undefined, undefined); + + assert.equal(await readFile(path, "utf8"), "const x = 1;\n"); +}); diff --git a/.pi/agent/extensions/dev-tools/src/tools/write.ts b/.pi/agent/extensions/dev-tools/src/tools/write.ts new file mode 100644 index 0000000..9236af7 --- /dev/null +++ b/.pi/agent/extensions/dev-tools/src/tools/write.ts @@ -0,0 +1,23 @@ +import { createWriteToolDefinition } from "@mariozechner/pi-coding-agent"; +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import type { DevToolsRuntime } from "../runtime.ts"; + +export function createFormattedWriteTool(cwd: string, runtime: DevToolsRuntime) { + const original = createWriteToolDefinition(cwd, { + operations: { + mkdir: (dir) => mkdir(dir, { recursive: true }).then(() => {}), + writeFile: async (absolutePath, content) => { + await mkdir(dirname(absolutePath), { recursive: true }); + await writeFile(absolutePath, content, "utf8"); + const formatResult = await runtime.formatAfterMutation(absolutePath); + runtime.noteMutation(absolutePath, formatResult); + if (formatResult.status === "failed") { + throw new Error(`Auto-format failed for ${absolutePath}: ${formatResult.message}`); + } + }, + }, + }); + + return original; +} diff --git a/.pi/agent/extensions/tmux-subagent/index.ts b/.pi/agent/extensions/tmux-subagent/index.ts new file mode 100644 index 0000000..3fef764 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/index.ts @@ -0,0 +1,99 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { createRunArtifacts } from "./src/artifacts.ts"; +import { monitorRun } from "./src/monitor.ts"; +import { listAvailableModelReferences } from "./src/models.ts"; +import { createTmuxSingleRunner } from "./src/runner.ts"; +import { + buildCurrentWindowArgs, + buildKillPaneArgs, + buildSplitWindowArgs, + buildWrapperShellCommand, + isInsideTmux, +} from "./src/tmux.ts"; +import { createSubagentParamsSchema } from "./src/schema.ts"; +import { createSubagentTool } from "./src/tool.ts"; + +const packageRoot = dirname(fileURLToPath(import.meta.url)); +const wrapperPath = join(packageRoot, "src", "wrapper", "cli.mjs"); + +export default function tmuxSubagentExtension(pi: ExtensionAPI) { + if (process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR === "agent") { + pi.registerProvider("github-copilot", { + headers: { "X-Initiator": "agent" }, + }); + } + + // In wrapper/child sessions spawned by the tmux runner we must not register the + // subagent tool (that would cause nested subagent registrations). Skip all + // subagent-tool registration logic when PI_TMUX_SUBAGENT_CHILD is set. Provider + // overrides (above) are still allowed in child runs, so the guard is placed + // after provider registration. + if (process.env.PI_TMUX_SUBAGENT_CHILD === "1") { + return; + } + + let lastRegisteredModelsKey: string | undefined; + + const runSingleTask = createTmuxSingleRunner({ + assertInsideTmux() { + if (!isInsideTmux()) throw new Error("tmux-backed subagents require pi to be running inside tmux."); + }, + async getCurrentWindowId() { + const result = await pi.exec("tmux", buildCurrentWindowArgs()); + return result.stdout.trim(); + }, + createArtifacts: createRunArtifacts, + buildWrapperCommand(metaPath: string) { + return buildWrapperShellCommand({ nodePath: process.execPath, wrapperPath, metaPath }); + }, + async createPane(input) { + const result = await pi.exec("tmux", buildSplitWindowArgs(input)); + return result.stdout.trim(); + }, + monitorRun, + async killPane(paneId: string) { + await pi.exec("tmux", buildKillPaneArgs(paneId)); + }, + }); + + const registerSubagentTool = (availableModels: string[]) => { + // Do not register a tool when no models are available. Remember that the + // last-registered key is different from the empty sentinel so that a later + // non-empty list will still trigger registration. + if (!availableModels || availableModels.length === 0) { + const emptyKey = "\u0000"; + if (lastRegisteredModelsKey === emptyKey) return; + lastRegisteredModelsKey = emptyKey; + return; + } + + // Create a deduplication key that is independent of the order of + // availableModels by sorting a lowercase copy. Do not mutate + // availableModels itself since we want to preserve the original order for + // schema enum values. + const key = [...availableModels].map((s) => s.toLowerCase()).sort().join("\u0000"); + if (key === lastRegisteredModelsKey) return; + + lastRegisteredModelsKey = key; + pi.registerTool( + createSubagentTool({ + parameters: createSubagentParamsSchema(availableModels), + runSingleTask, + }), + ); + }; + + const syncSubagentTool = (ctx: { modelRegistry: { getAvailable(): Array<{ provider: string; id: string }> } }) => { + registerSubagentTool(listAvailableModelReferences(ctx.modelRegistry)); + }; + + pi.on("session_start", (_event, ctx) => { + syncSubagentTool(ctx); + }); + + pi.on("before_agent_start", (_event, ctx) => { + syncSubagentTool(ctx); + }); +} diff --git a/.pi/agent/extensions/tmux-subagent/package-lock.json b/.pi/agent/extensions/tmux-subagent/package-lock.json new file mode 100644 index 0000000..787a301 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/package-lock.json @@ -0,0 +1,4365 @@ +{ + "name": "pi-tmux-subagent-extension", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-tmux-subagent-extension", + "version": "0.0.0", + "devDependencies": { + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-coding-agent": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@sinclair/typebox": "^0.34.49", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-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.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1027.0.tgz", + "integrity": "sha512-Qcda5Z5Vb3LPVt7zNycEiiAo9Blk0JpEPJwz/sUBJby6/0zvTlo+/FIXlwYZ3TJHSgKCYiCaBqAB0WRlWDfLfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/eventstream-handler-node": "^3.972.13", + "@aws-sdk/middleware-eventstream": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/token-providers": "3.1027.0", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", + "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", + "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", + "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-format-url": "^3.972.9", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1027.0.tgz", + "integrity": "sha512-mI3Jm14cM5sNKc7aNX3cqJe/rFQ2Zzx7x5W8WUtxj2lVxcH2RGYhqI3hK9nnImY6Ec5MeGXCVPjl/q6Mz5HmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", + "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.49.0.tgz", + "integrity": "sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", + "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/jiti": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", + "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "std-env": "^3.10.0", + "yoctocolors": "^2.1.2" + }, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@mariozechner/pi-agent-core": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.66.1.tgz", + "integrity": "sha512-Nj54A7SuB/EQi8r3Gs+glFOr9wz/a9uxYFf0pCLf2DE7VmzA9O7WSejrvArna17K6auftLSdNyRRe2bIO0qezg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/pi-ai": "^0.66.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.66.1.tgz", + "integrity": "sha512-7IZHvpsFdKEBkTmjNrdVL7JLUJVIpha6bwTr12cZ5XyDrxij06wP6Ncpnf4HT5BXAzD5w2JnoqTOSbMEIZj3dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "1.14.1", + "@sinclair/typebox": "^0.34.41", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-coding-agent": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.66.1.tgz", + "integrity": "sha512-cNmatT+5HvYzQ78cRhRih00wCeUTH/fFx9ecJh5AbN7axgWU+bwiZYy0cjrTsGVgMGF4xMYlPRn/Nze9JEB+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/jiti": "^2.6.2", + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@silvia-odwyer/photon-node": "^0.3.4", + "ajv": "^8.17.1", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "undici": "^7.19.1", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.2" + } + }, + "node_modules/@mariozechner/pi-tui": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.66.1.tgz", + "integrity": "sha512-hNFN42ebjwtfGooqoUwM+QaPR1XCyqPuueuP3aLOWS1bZ2nZP/jq8MBuGNrmMw1cgiDcotvOlSNj3BatzEOGsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", + "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "dev": true, + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.6.tgz", + "integrity": "sha512-WQBpM5uo74UQ17UpsFN+PUOrQQg4/nYdey4SGVluQun2drYYfePziLLWdSmFb4wSdWlJC1aimXQnjhPCheRKuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", + "integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "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 + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/.pi/agent/extensions/tmux-subagent/package.json b/.pi/agent/extensions/tmux-subagent/package.json new file mode 100644 index 0000000..e0f8732 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/package.json @@ -0,0 +1,23 @@ +{ + "name": "pi-tmux-subagent-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test": "tsx --test src/*.test.ts src/**/*.test.ts" + }, + "pi": { + "extensions": ["./index.ts"], + "prompts": ["./prompts/*.md"] + }, + "devDependencies": { + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-coding-agent": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@sinclair/typebox": "^0.34.49", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } +} diff --git a/.pi/agent/extensions/tmux-subagent/prompts/implement-and-review.md b/.pi/agent/extensions/tmux-subagent/prompts/implement-and-review.md new file mode 100644 index 0000000..63724a0 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/prompts/implement-and-review.md @@ -0,0 +1,10 @@ +--- +description: Implement, review, then revise using tmux-backed subagents +--- +Use the `subagent` tool in chain mode: + +1. `worker` to implement: $@ +2. `reviewer` to review `{previous}` and identify issues +3. `worker` to revise the implementation using `{previous}` + +User request: $@ diff --git a/.pi/agent/extensions/tmux-subagent/prompts/implement.md b/.pi/agent/extensions/tmux-subagent/prompts/implement.md new file mode 100644 index 0000000..37b8e9a --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/prompts/implement.md @@ -0,0 +1,10 @@ +--- +description: Scout, plan, and implement using tmux-backed subagents +--- +Use the `subagent` tool to handle this request in three stages: + +1. Run `scout` to inspect the codebase for: $@ +2. Run `planner` in chain mode, using `{previous}` from the scout output to produce a concrete implementation plan +3. Run `worker` in chain mode, using `{previous}` from the planner output to implement the approved plan + +User request: $@ diff --git a/.pi/agent/extensions/tmux-subagent/prompts/scout-and-plan.md b/.pi/agent/extensions/tmux-subagent/prompts/scout-and-plan.md new file mode 100644 index 0000000..cd8b0db --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/prompts/scout-and-plan.md @@ -0,0 +1,9 @@ +--- +description: Scout the codebase, then produce a plan using tmux-backed subagents +--- +Use the `subagent` tool in chain mode: + +1. `scout` to inspect the codebase for: $@ +2. `planner` to turn `{previous}` into an implementation plan + +User request: $@ diff --git a/.pi/agent/extensions/tmux-subagent/src/agents.test.ts b/.pi/agent/extensions/tmux-subagent/src/agents.test.ts new file mode 100644 index 0000000..299efbb --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/agents.test.ts @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdir, writeFile, mkdtemp } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { BUILTIN_AGENTS } from "./builtin-agents.ts"; +import { discoverAgents } from "./agents.ts"; + +test("discoverAgents returns built-ins and lets user markdown override by name", async () => { + const root = await mkdtemp(join(tmpdir(), "tmux-subagent-agents-")); + const agentDir = join(root, "agent-home"); + const userAgentsDir = join(agentDir, "agents"); + await mkdir(userAgentsDir, { recursive: true }); + await writeFile( + join(userAgentsDir, "scout.md"), + `---\nname: scout\ndescription: User scout\nmodel: openai/gpt-5\n---\nUser override prompt`, + "utf8", + ); + + const result = discoverAgents(join(root, "repo"), { + scope: "user", + agentDir, + builtins: BUILTIN_AGENTS, + }); + + const scout = result.agents.find((agent) => agent.name === "scout"); + assert.equal(scout?.source, "user"); + assert.equal(scout?.description, "User scout"); + assert.equal(scout?.model, "openai/gpt-5"); +}); + +test("discoverAgents lets project agents override user agents when scope is both", async () => { + const root = await mkdtemp(join(tmpdir(), "tmux-subagent-agents-")); + const repo = join(root, "repo"); + const agentDir = join(root, "agent-home"); + const userAgentsDir = join(agentDir, "agents"); + const projectAgentsDir = join(repo, ".pi", "agents"); + await mkdir(userAgentsDir, { recursive: true }); + await mkdir(projectAgentsDir, { recursive: true }); + + await writeFile(join(userAgentsDir, "worker.md"), `---\nname: worker\ndescription: User worker\n---\nUser worker`, "utf8"); + await writeFile(join(projectAgentsDir, "worker.md"), `---\nname: worker\ndescription: Project worker\n---\nProject worker`, "utf8"); + + const result = discoverAgents(repo, { + scope: "both", + agentDir, + builtins: BUILTIN_AGENTS, + }); + + const worker = result.agents.find((agent) => agent.name === "worker"); + assert.equal(worker?.source, "project"); + assert.equal(worker?.description, "Project worker"); + assert.equal(result.projectAgentsDir, projectAgentsDir); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/agents.ts b/.pi/agent/extensions/tmux-subagent/src/agents.ts new file mode 100644 index 0000000..ce4a5fa --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/agents.ts @@ -0,0 +1,91 @@ +import { existsSync, readdirSync, readFileSync, statSync, type Dirent } from "node:fs"; +import { dirname, join } from "node:path"; +import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent"; +import { BUILTIN_AGENTS, type AgentDefinition } from "./builtin-agents.ts"; +import type { AgentScope } from "./schema.ts"; + +export interface AgentDiscoveryOptions { + scope?: AgentScope; + agentDir?: string; + builtins?: AgentDefinition[]; +} + +export interface AgentDiscoveryResult { + agents: AgentDefinition[]; + projectAgentsDir: string | null; +} + +function loadMarkdownAgents(dir: string, source: "user" | "project"): AgentDefinition[] { + if (!existsSync(dir)) return []; + + let entries: Dirent[]; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return []; + } + + const agents: AgentDefinition[] = []; + for (const entry of entries) { + if (!entry.name.endsWith(".md")) continue; + if (!entry.isFile() && !entry.isSymbolicLink()) continue; + + const filePath = join(dir, entry.name); + const content = readFileSync(filePath, "utf8"); + const { frontmatter, body } = parseFrontmatter>(content); + if (!frontmatter.name || !frontmatter.description) continue; + + agents.push({ + name: frontmatter.name, + description: frontmatter.description, + tools: frontmatter.tools?.split(",").map((tool) => tool.trim()).filter(Boolean), + model: frontmatter.model, + systemPrompt: body.trim(), + source, + filePath, + }); + } + + return agents; +} + +function findNearestProjectAgentsDir(cwd: string): string | null { + let current = cwd; + while (true) { + const candidate = join(current, ".pi", "agents"); + try { + if (statSync(candidate).isDirectory()) return candidate; + } catch {} + + const parent = dirname(current); + if (parent === current) return null; + current = parent; + } +} + +export function discoverAgents(cwd: string, options: AgentDiscoveryOptions = {}): AgentDiscoveryResult { + const scope = options.scope ?? "user"; + const builtins = options.builtins ?? BUILTIN_AGENTS; + const userAgentDir = join(options.agentDir ?? getAgentDir(), "agents"); + const projectAgentsDir = findNearestProjectAgentsDir(cwd); + + const sources = new Map(); + for (const agent of builtins) sources.set(agent.name, agent); + + if (scope !== "project") { + for (const agent of loadMarkdownAgents(userAgentDir, "user")) { + sources.set(agent.name, agent); + } + } + + if (scope !== "user" && projectAgentsDir) { + for (const agent of loadMarkdownAgents(projectAgentsDir, "project")) { + sources.set(agent.name, agent); + } + } + + return { + agents: [...sources.values()], + projectAgentsDir, + }; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/artifacts.test.ts b/.pi/agent/extensions/tmux-subagent/src/artifacts.test.ts new file mode 100644 index 0000000..4e49feb --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/artifacts.test.ts @@ -0,0 +1,21 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { createRunArtifacts } from "./artifacts.ts"; + +test("createRunArtifacts writes metadata and reserves stable artifact paths", async () => { + const cwd = await mkdtemp(join(tmpdir(), "tmux-subagent-run-")); + + const artifacts = await createRunArtifacts(cwd, { + runId: "run-1", + task: "inspect auth", + systemPrompt: "You are scout", + }); + + assert.equal(artifacts.runId, "run-1"); + assert.match(artifacts.dir, /\.pi\/subagents\/runs\/run-1$/); + assert.equal(JSON.parse(await readFile(artifacts.metaPath, "utf8")).task, "inspect auth"); + assert.equal(await readFile(artifacts.systemPromptPath, "utf8"), "You are scout"); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/artifacts.ts b/.pi/agent/extensions/tmux-subagent/src/artifacts.ts new file mode 100644 index 0000000..b4ffd73 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/artifacts.ts @@ -0,0 +1,66 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { randomUUID } from "node:crypto"; +import { join, resolve } from "node:path"; + +export interface RunArtifacts { + runId: string; + dir: string; + metaPath: string; + eventsPath: string; + resultPath: string; + stdoutPath: string; + stderrPath: string; + transcriptPath: string; + sessionPath: string; + systemPromptPath: string; +} + +export async function createRunArtifacts( + cwd: string, + meta: Record & { runId?: string; systemPrompt?: string }, +): Promise { + const runId = meta.runId ?? randomUUID(); + const dir = resolve(cwd, ".pi", "subagents", "runs", runId); + await mkdir(dir, { recursive: true }); + + const artifacts: RunArtifacts = { + runId, + dir, + metaPath: join(dir, "meta.json"), + eventsPath: join(dir, "events.jsonl"), + resultPath: join(dir, "result.json"), + stdoutPath: join(dir, "stdout.log"), + stderrPath: join(dir, "stderr.log"), + transcriptPath: join(dir, "transcript.log"), + sessionPath: join(dir, "child-session.jsonl"), + systemPromptPath: join(dir, "system-prompt.md"), + }; + + await writeFile( + artifacts.metaPath, + JSON.stringify( + { + ...meta, + runId, + sessionPath: artifacts.sessionPath, + eventsPath: artifacts.eventsPath, + resultPath: artifacts.resultPath, + stdoutPath: artifacts.stdoutPath, + stderrPath: artifacts.stderrPath, + transcriptPath: artifacts.transcriptPath, + systemPromptPath: artifacts.systemPromptPath, + }, + null, + 2, + ), + "utf8", + ); + + await writeFile(artifacts.systemPromptPath, typeof meta.systemPrompt === "string" ? meta.systemPrompt : "", "utf8"); + await writeFile(artifacts.eventsPath, "", "utf8"); + await writeFile(artifacts.stdoutPath, "", "utf8"); + await writeFile(artifacts.stderrPath, "", "utf8"); + await writeFile(artifacts.transcriptPath, "", "utf8"); + + return artifacts; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/builtin-agents.ts b/.pi/agent/extensions/tmux-subagent/src/builtin-agents.ts new file mode 100644 index 0000000..1848d90 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/builtin-agents.ts @@ -0,0 +1,43 @@ +export interface AgentDefinition { + name: string; + description: string; + tools?: string[]; + model?: string; + systemPrompt: string; + source: "builtin" | "user" | "project"; + filePath?: string; +} + +export const BUILTIN_AGENTS: AgentDefinition[] = [ + { + name: "scout", + description: "Fast codebase recon and compressed context gathering", + tools: ["read", "grep", "find", "ls", "bash"], + model: "claude-haiku-4-5", + systemPrompt: "You are a scout. Explore quickly, summarize clearly, and avoid implementation.", + source: "builtin", + }, + { + name: "planner", + description: "Turns exploration into implementation plans", + tools: ["read", "grep", "find", "ls"], + model: "claude-sonnet-4-5", + systemPrompt: "You are a planner. Produce implementation plans, file lists, and risks.", + source: "builtin", + }, + { + name: "reviewer", + description: "Reviews code and identifies correctness and quality issues", + tools: ["read", "grep", "find", "ls", "bash"], + model: "claude-sonnet-4-5", + systemPrompt: "You are a reviewer. Inspect code critically and report concrete issues.", + source: "builtin", + }, + { + name: "worker", + description: "General-purpose implementation agent", + model: "claude-sonnet-4-5", + systemPrompt: "You are a worker. Execute the delegated task completely and report final results clearly.", + source: "builtin", + }, +]; diff --git a/.pi/agent/extensions/tmux-subagent/src/extension.test.ts b/.pi/agent/extensions/tmux-subagent/src/extension.test.ts new file mode 100644 index 0000000..54e74ff --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/extension.test.ts @@ -0,0 +1,397 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import tmuxSubagentExtension from "../index.ts"; + +test("the extension entrypoint registers the subagent tool with the currently available models", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + + try { + const registeredTools: any[] = []; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool(tool: any) { + registeredTools.push(tool); + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "function"); + + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }, + }, + ); + + assert.equal(registeredTools.length, 1); + assert.equal(registeredTools[0]?.name, "subagent"); + assert.deepEqual(registeredTools[0]?.parameters.required, ["model"]); + assert.deepEqual(registeredTools[0]?.parameters.properties.model.enum, [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); + assert.deepEqual(registeredTools[0]?.parameters.properties.tasks.items.properties.model.enum, [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + +test("before_agent_start re-applies subagent registration when available models changed", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + + try { + const registeredTools: any[] = []; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool(tool: any) { + registeredTools.push(tool); + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "function"); + assert.equal(typeof handlers.before_agent_start, "function"); + + // initial registration with two models + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }, + }, + ); + + assert.equal(registeredTools.length, 1); + assert.deepEqual(registeredTools[0]?.parameters.properties.model.enum, [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); + + // then before agent start with a different model set — should re-register + await handlers.before_agent_start?.( + { reason: "about-to-start" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "openai", id: "gpt-6" }, + ], + }, + }, + ); + + assert.equal(registeredTools.length, 2); + assert.deepEqual(registeredTools[1]?.parameters.properties.model.enum, ["openai/gpt-6"]); + assert.deepEqual(registeredTools[1]?.parameters.properties.tasks.items.properties.model.enum, ["openai/gpt-6"]); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + +test("child subagent sessions skip registering the subagent tool", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + process.env.PI_TMUX_SUBAGENT_CHILD = "1"; + + try { + const registeredTools: any[] = []; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool(tool: any) { + registeredTools.push(tool); + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "undefined"); + assert.equal(typeof handlers.before_agent_start, "undefined"); + assert.equal(registeredTools.length, 0); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + +test("registers github-copilot provider override when PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR is set", () => { + const registeredProviders: Array<{ name: string; config: any }> = []; + const originalInitiator = process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR; + const originalChild = process.env.PI_TMUX_SUBAGENT_CHILD; + // Ensure we exercise the non-child code path for this test + if (originalChild !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = "agent"; + + try { + tmuxSubagentExtension({ + on() {}, + registerTool() {}, + registerProvider(name: string, config: any) { + registeredProviders.push({ name, config }); + }, + } as any); + } finally { + if (originalInitiator === undefined) delete process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR; + else process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = originalInitiator; + + if (originalChild === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = originalChild; + } + + assert.deepEqual(registeredProviders, [ + { + name: "github-copilot", + config: { headers: { "X-Initiator": "agent" } }, + }, + ]); +}); + +test("combined child+copilot run registers provider but no tools or startup handlers", () => { + const registeredProviders: Array<{ name: string; config: any }> = []; + const registeredTools: any[] = []; + const handlers: Record Promise | void> = {}; + + const originalInitiator = process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR; + const originalChild = process.env.PI_TMUX_SUBAGENT_CHILD; + process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = "agent"; + process.env.PI_TMUX_SUBAGENT_CHILD = "1"; + + try { + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool(tool: any) { + registeredTools.push(tool); + }, + registerProvider(name: string, config: any) { + registeredProviders.push({ name, config }); + }, + } as any); + + assert.deepEqual(registeredProviders, [ + { + name: "github-copilot", + config: { headers: { "X-Initiator": "agent" } }, + }, + ]); + + assert.equal(registeredTools.length, 0); + assert.equal(typeof handlers.session_start, "undefined"); + assert.equal(typeof handlers.before_agent_start, "undefined"); + } finally { + if (originalInitiator === undefined) delete process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR; + else process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = originalInitiator; + + if (originalChild === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = originalChild; + } +}); + +test("does not re-register the subagent tool when models list unchanged, but re-registers when changed", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + + try { + let registerToolCalls = 0; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool() { + registerToolCalls++; + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "function"); + assert.equal(typeof handlers.before_agent_start, "function"); + + // First registration with two models + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 1); + + // Second registration with the same models — should not increase count + await handlers.before_agent_start?.( + { reason: "about-to-start" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 1); + + // Third call with changed model list — should re-register + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "openai", id: "gpt-6" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 2); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + + +// New tests for robustness: order-independence and empty model handling + +test("same model set in different orders should NOT trigger re-registration", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + + try { + let registerToolCalls = 0; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool() { + registerToolCalls++; + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "function"); + assert.equal(typeof handlers.before_agent_start, "function"); + + // First registration with two models in one order + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 1); + + // Same models but reversed order — should NOT re-register + await handlers.before_agent_start?.( + { reason: "about-to-start" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "openai", id: "gpt-5" }, + { provider: "anthropic", id: "claude-sonnet-4-5" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 1); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + + +test("empty model list should NOT register the tool, but a later non-empty list should", async () => { + const original = process.env.PI_TMUX_SUBAGENT_CHILD; + if (original !== undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + + try { + let registerToolCalls = 0; + const handlers: Record Promise | void> = {}; + + tmuxSubagentExtension({ + on(event: string, handler: (event: any, ctx: any) => Promise | void) { + handlers[event] = handler; + }, + registerTool() { + registerToolCalls++; + }, + registerProvider() {}, + } as any); + + assert.equal(typeof handlers.session_start, "function"); + assert.equal(typeof handlers.before_agent_start, "function"); + + // empty list should not register + await handlers.session_start?.( + { reason: "startup" }, + { + modelRegistry: { + getAvailable: () => [], + }, + }, + ); + + assert.equal(registerToolCalls, 0); + + // later non-empty list should register + await handlers.before_agent_start?.( + { reason: "about-to-start" }, + { + modelRegistry: { + getAvailable: () => [ + { provider: "openai", id: "gpt-6" }, + ], + }, + }, + ); + + assert.equal(registerToolCalls, 1); + } finally { + if (original === undefined) delete process.env.PI_TMUX_SUBAGENT_CHILD; + else process.env.PI_TMUX_SUBAGENT_CHILD = original; + } +}); + diff --git a/.pi/agent/extensions/tmux-subagent/src/models.test.ts b/.pi/agent/extensions/tmux-subagent/src/models.test.ts new file mode 100644 index 0000000..e19d2a8 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/models.test.ts @@ -0,0 +1,44 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + formatModelReference, + listAvailableModelReferences, + normalizeAvailableModelReference, + resolveChildModel, +} from "./models.ts"; + +test("resolveChildModel prefers the per-task override over the required top-level model", () => { + const selection = resolveChildModel({ + taskModel: "openai/gpt-5", + topLevelModel: "anthropic/claude-sonnet-4-5", + }); + + assert.equal(selection.requestedModel, "openai/gpt-5"); + assert.equal(selection.resolvedModel, "openai/gpt-5"); +}); + +test("formatModelReference returns provider/id", () => { + const ref = formatModelReference({ provider: "anthropic", id: "claude-sonnet-4-5" }); + + assert.equal(ref, "anthropic/claude-sonnet-4-5"); +}); + +test("listAvailableModelReferences formats all configured available models", () => { + const refs = listAvailableModelReferences({ + getAvailable: () => [ + { provider: "anthropic", id: "claude-sonnet-4-5" }, + { provider: "openai", id: "gpt-5" }, + ], + }); + + assert.deepEqual(refs, ["anthropic/claude-sonnet-4-5", "openai/gpt-5"]); +}); + +test("normalizeAvailableModelReference matches canonical refs case-insensitively", () => { + const normalized = normalizeAvailableModelReference("OpenAI/GPT-5", [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); + + assert.equal(normalized, "openai/gpt-5"); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/models.ts b/.pi/agent/extensions/tmux-subagent/src/models.ts new file mode 100644 index 0000000..df9be58 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/models.ts @@ -0,0 +1,58 @@ +export interface ModelLike { + provider: string; + id: string; +} + +export interface AvailableModelRegistryLike { + getAvailable(): ModelLike[]; +} + +export interface ModelSelection { + requestedModel?: string; + resolvedModel?: string; +} + +export function formatModelReference(model: ModelLike): string { + return `${model.provider}/${model.id}`; +} + +export function listAvailableModelReferences(modelRegistry?: AvailableModelRegistryLike): string[] { + if (!modelRegistry) return []; + + const seen = new Set(); + const refs: string[] = []; + for (const model of modelRegistry.getAvailable()) { + const ref = formatModelReference(model); + const key = ref.toLowerCase(); + if (seen.has(key)) continue; + seen.add(key); + refs.push(ref); + } + + return refs; +} + +export function normalizeAvailableModelReference( + requestedModel: string | undefined, + availableModels: readonly string[], +): string | undefined { + if (typeof requestedModel !== "string") return undefined; + + const trimmed = requestedModel.trim(); + if (!trimmed) return undefined; + + const normalized = trimmed.toLowerCase(); + return availableModels.find((candidate) => candidate.toLowerCase() === normalized); +} + +export function resolveChildModel(input: { + taskModel?: string; + topLevelModel: string; +}): ModelSelection { + const requestedModel = input.taskModel ?? input.topLevelModel; + + return { + requestedModel, + resolvedModel: requestedModel, + }; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/monitor.test.ts b/.pi/agent/extensions/tmux-subagent/src/monitor.test.ts new file mode 100644 index 0000000..0191873 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/monitor.test.ts @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, writeFile, appendFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { monitorRun } from "./monitor.ts"; + +test("monitorRun streams normalized events and resolves when result.json appears", async () => { + const dir = await mkdtemp(join(tmpdir(), "tmux-subagent-monitor-")); + const eventsPath = join(dir, "events.jsonl"); + const resultPath = join(dir, "result.json"); + await writeFile(eventsPath, "", "utf8"); + + const seen: string[] = []; + const waiting = monitorRun({ + eventsPath, + resultPath, + onEvent(event) { + seen.push(event.type); + }, + }); + + await appendFile(eventsPath, `${JSON.stringify({ type: "tool_call", toolName: "read", args: { path: "a.ts" } })}\n`, "utf8"); + await writeFile( + resultPath, + JSON.stringify({ runId: "run-1", exitCode: 0, finalText: "done", agent: "scout", task: "inspect auth" }, null, 2), + "utf8", + ); + + const result = await waiting; + assert.deepEqual(seen, ["tool_call"]); + assert.equal(result.finalText, "done"); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/monitor.ts b/.pi/agent/extensions/tmux-subagent/src/monitor.ts new file mode 100644 index 0000000..a413587 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/monitor.ts @@ -0,0 +1,34 @@ +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; + +async function sleep(ms: number) { + await new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function monitorRun(input: { + eventsPath: string; + resultPath: string; + onEvent?: (event: any) => void; + pollMs?: number; +}) { + const pollMs = input.pollMs ?? 50; + let offset = 0; + + while (true) { + if (existsSync(input.eventsPath)) { + const text = await readFile(input.eventsPath, "utf8"); + const next = text.slice(offset); + offset = text.length; + + for (const line of next.split("\n").filter(Boolean)) { + input.onEvent?.(JSON.parse(line)); + } + } + + if (existsSync(input.resultPath)) { + return JSON.parse(await readFile(input.resultPath, "utf8")); + } + + await sleep(pollMs); + } +} diff --git a/.pi/agent/extensions/tmux-subagent/src/prompts.test.ts b/.pi/agent/extensions/tmux-subagent/src/prompts.test.ts new file mode 100644 index 0000000..9bc0f02 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/prompts.test.ts @@ -0,0 +1,20 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); + +test("package.json exposes the extension and workflow prompt templates", () => { + const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")); + + assert.deepEqual(packageJson.pi.extensions, ["./index.ts"]); + assert.deepEqual(packageJson.pi.prompts, ["./prompts/*.md"]); + + for (const name of ["implement.md", "scout-and-plan.md", "implement-and-review.md"]) { + const content = readFileSync(join(packageRoot, "prompts", name), "utf8"); + assert.match(content, /^---\ndescription:/m); + assert.match(content, /subagent/); + } +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/runner.test.ts b/.pi/agent/extensions/tmux-subagent/src/runner.test.ts new file mode 100644 index 0000000..2b92351 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/runner.test.ts @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createTmuxSingleRunner } from "./runner.ts"; + +test("createTmuxSingleRunner always kills the pane after monitor completion", async () => { + const killed: string[] = []; + + const runSingleTask = createTmuxSingleRunner({ + assertInsideTmux() {}, + getCurrentWindowId: async () => "@1", + createArtifacts: async () => ({ + metaPath: "/tmp/meta.json", + runId: "run-1", + eventsPath: "/tmp/events.jsonl", + resultPath: "/tmp/result.json", + sessionPath: "/tmp/child-session.jsonl", + stdoutPath: "/tmp/stdout.log", + stderrPath: "/tmp/stderr.log", + }), + buildWrapperCommand: () => "'node' '/wrapper.mjs' '/tmp/meta.json'", + createPane: async () => "%9", + monitorRun: async () => ({ finalText: "done", exitCode: 0 }), + killPane: async (paneId: string) => { + killed.push(paneId); + }, + }); + + const result = await runSingleTask({ cwd: "/repo", meta: { task: "inspect auth" } as any }); + + assert.equal(result.paneId, "%9"); + assert.equal(result.finalText, "done"); + assert.deepEqual(killed, ["%9"]); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/runner.ts b/.pi/agent/extensions/tmux-subagent/src/runner.ts new file mode 100644 index 0000000..2c384a8 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/runner.ts @@ -0,0 +1,44 @@ +export function createTmuxSingleRunner(deps: { + assertInsideTmux(): void; + getCurrentWindowId: () => Promise; + createArtifacts: (cwd: string, meta: Record) => Promise; + buildWrapperCommand: (metaPath: string) => string; + createPane: (input: { windowId: string; cwd: string; command: string }) => Promise; + monitorRun: (input: { eventsPath: string; resultPath: string; onEvent?: (event: any) => void }) => Promise; + killPane: (paneId: string) => Promise; +}) { + return async function runSingleTask(input: { + cwd: string; + meta: Record; + onEvent?: (event: any) => void; + }) { + deps.assertInsideTmux(); + + const artifacts = await deps.createArtifacts(input.cwd, input.meta); + const windowId = await deps.getCurrentWindowId(); + const command = deps.buildWrapperCommand(artifacts.metaPath); + const paneId = await deps.createPane({ windowId, cwd: input.cwd, command }); + + try { + const result = await deps.monitorRun({ + eventsPath: artifacts.eventsPath, + resultPath: artifacts.resultPath, + onEvent: input.onEvent, + }); + + return { + ...result, + runId: result.runId ?? artifacts.runId, + paneId, + windowId, + sessionPath: result.sessionPath ?? artifacts.sessionPath, + stdoutPath: result.stdoutPath ?? artifacts.stdoutPath, + stderrPath: result.stderrPath ?? artifacts.stderrPath, + resultPath: artifacts.resultPath, + eventsPath: artifacts.eventsPath, + }; + } finally { + await deps.killPane(paneId); + } + }; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/schema.ts b/.pi/agent/extensions/tmux-subagent/src/schema.ts new file mode 100644 index 0000000..0a8c68d --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/schema.ts @@ -0,0 +1,84 @@ +import { StringEnum } from "@mariozechner/pi-ai"; +import { Type, type Static } from "@sinclair/typebox"; + +function createTaskModelSchema(availableModels: readonly string[]) { + return Type.Optional( + StringEnum(availableModels, { + description: "Optional child model override. Must be one of the currently available models.", + }), + ); +} + +export function createTaskItemSchema(availableModels: readonly string[]) { + return Type.Object({ + agent: Type.String({ description: "Name of the agent to invoke" }), + task: Type.String({ description: "Task to delegate to the child agent" }), + model: createTaskModelSchema(availableModels), + cwd: Type.Optional(Type.String({ description: "Optional working directory override" })), + }); +} + +export function createChainItemSchema(availableModels: readonly string[]) { + return Type.Object({ + agent: Type.String({ description: "Name of the agent to invoke" }), + task: Type.String({ description: "Task with optional {previous} placeholder" }), + model: createTaskModelSchema(availableModels), + cwd: Type.Optional(Type.String({ description: "Optional working directory override" })), + }); +} + +export const TaskItemSchema = createTaskItemSchema([]); +export const ChainItemSchema = createChainItemSchema([]); + +export const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, { + description: "Which markdown agent sources to use", + default: "user", +}); + +export function createSubagentParamsSchema(availableModels: readonly string[]) { + return Type.Object({ + agent: Type.Optional(Type.String({ description: "Single-mode agent name" })), + task: Type.Optional(Type.String({ description: "Single-mode delegated task" })), + model: StringEnum(availableModels, { + description: "Required top-level child model. Must be one of the currently available models.", + }), + tasks: Type.Optional(Type.Array(createTaskItemSchema(availableModels), { description: "Parallel tasks" })), + chain: Type.Optional(Type.Array(createChainItemSchema(availableModels), { description: "Sequential tasks" })), + agentScope: Type.Optional(AgentScopeSchema), + confirmProjectAgents: Type.Optional(Type.Boolean({ default: true })), + cwd: Type.Optional(Type.String({ description: "Single-mode working directory override" })), + }); +} + +export const SubagentParamsSchema = createSubagentParamsSchema([]); + +export type TaskItem = Static; +export type ChainItem = Static; +export type SubagentParams = Static; +export type AgentScope = Static; + +export interface SubagentRunResult { + runId: string; + agent: string; + agentSource: "builtin" | "user" | "project" | "unknown"; + task: string; + requestedModel?: string; + resolvedModel?: string; + paneId?: string; + windowId?: string; + sessionPath?: string; + exitCode: number; + stopReason?: string; + finalText: string; + stdoutPath?: string; + stderrPath?: string; + resultPath?: string; + eventsPath?: string; +} + +export interface SubagentToolDetails { + mode: "single" | "parallel" | "chain"; + agentScope: AgentScope; + projectAgentsDir: string | null; + results: SubagentRunResult[]; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/tmux.test.ts b/.pi/agent/extensions/tmux-subagent/src/tmux.test.ts new file mode 100644 index 0000000..bcadde0 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tmux.test.ts @@ -0,0 +1,43 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + buildSplitWindowArgs, + buildWrapperShellCommand, + isInsideTmux, +} from "./tmux.ts"; + +test("isInsideTmux reads the TMUX environment variable", () => { + assert.equal(isInsideTmux({ TMUX: "/tmp/tmux-1000/default,123,0" } as NodeJS.ProcessEnv), true); + assert.equal(isInsideTmux({} as NodeJS.ProcessEnv), false); +}); + +test("buildWrapperShellCommand single-quotes paths safely", () => { + const command = buildWrapperShellCommand({ + nodePath: "/usr/local/bin/node", + wrapperPath: "/repo/tmux-subagent/src/wrapper/cli.mjs", + metaPath: "/repo/.pi/subagents/runs/run-1/meta.json", + }); + + assert.equal( + command, + "'/usr/local/bin/node' '/repo/tmux-subagent/src/wrapper/cli.mjs' '/repo/.pi/subagents/runs/run-1/meta.json'", + ); +}); + +test("buildSplitWindowArgs targets the current window and cwd", () => { + assert.deepEqual(buildSplitWindowArgs({ + windowId: "@7", + cwd: "/repo", + command: "'node' '/wrapper.mjs' '/meta.json'", + }), [ + "split-window", + "-P", + "-F", + "#{pane_id}", + "-t", + "@7", + "-c", + "/repo", + "'node' '/wrapper.mjs' '/meta.json'", + ]); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/tmux.ts b/.pi/agent/extensions/tmux-subagent/src/tmux.ts new file mode 100644 index 0000000..1dfee02 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tmux.ts @@ -0,0 +1,41 @@ +export function isInsideTmux(env: NodeJS.ProcessEnv = process.env): boolean { + return typeof env.TMUX === "string" && env.TMUX.length > 0; +} + +function shellEscape(value: string): string { + return `'${value.replaceAll("'", "'\\''")}'`; +} + +export function buildWrapperShellCommand(input: { + nodePath: string; + wrapperPath: string; + metaPath: string; +}): string { + return [input.nodePath, input.wrapperPath, input.metaPath].map(shellEscape).join(" "); +} + +export function buildSplitWindowArgs(input: { + windowId: string; + cwd: string; + command: string; +}): string[] { + return [ + "split-window", + "-P", + "-F", + "#{pane_id}", + "-t", + input.windowId, + "-c", + input.cwd, + input.command, + ]; +} + +export function buildKillPaneArgs(paneId: string): string[] { + return ["kill-pane", "-t", paneId]; +} + +export function buildCurrentWindowArgs(): string[] { + return ["display-message", "-p", "#{window_id}"]; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/tool-chain.test.ts b/.pi/agent/extensions/tmux-subagent/src/tool-chain.test.ts new file mode 100644 index 0000000..5b9c9cc --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tool-chain.test.ts @@ -0,0 +1,107 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createSubagentTool } from "./tool.ts"; + +test("chain mode substitutes {previous} into the next task", async () => { + const seenTasks: string[] = []; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [ + { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, + { name: "planner", description: "Planner", source: "builtin", systemPrompt: "Planner prompt" }, + ], + projectAgentsDir: null, + }), + runSingleTask: async ({ meta }: any) => { + seenTasks.push(meta.task); + return { + runId: `${meta.agent}-${seenTasks.length}`, + agent: meta.agent, + agentSource: meta.agentSource, + task: meta.task, + exitCode: 0, + finalText: meta.agent === "scout" ? "Scout output" : "Plan output", + }; + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + model: "anthropic/claude-sonnet-4-5", + chain: [ + { agent: "scout", task: "inspect auth" }, + { agent: "planner", task: "use this context: {previous}" }, + ], + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + assert.deepEqual(seenTasks, ["inspect auth", "use this context: Scout output"]); + assert.equal(result.content[0]?.type === "text" ? result.content[0].text : "", "Plan output"); +}); + +test("chain mode stops on the first failed step", async () => { + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [ + { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, + { name: "planner", description: "Planner", source: "builtin", systemPrompt: "Planner prompt" }, + ], + projectAgentsDir: null, + }), + runSingleTask: async ({ meta }: any) => { + if (meta.agent === "planner") { + return { + runId: "planner-2", + agent: meta.agent, + agentSource: meta.agentSource, + task: meta.task, + exitCode: 1, + finalText: "", + stopReason: "error", + }; + } + return { + runId: "scout-1", + agent: meta.agent, + agentSource: meta.agentSource, + task: meta.task, + exitCode: 0, + finalText: "Scout output", + }; + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + model: "anthropic/claude-sonnet-4-5", + chain: [ + { agent: "scout", task: "inspect auth" }, + { agent: "planner", task: "use this context: {previous}" }, + ], + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + assert.equal(result.isError, true); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /Chain stopped at step 2/); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/tool-parallel.test.ts b/.pi/agent/extensions/tmux-subagent/src/tool-parallel.test.ts new file mode 100644 index 0000000..b8f5d7b --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tool-parallel.test.ts @@ -0,0 +1,97 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createSubagentTool } from "./tool.ts"; + +test("parallel mode runs each task and uses the top-level model unless a task overrides it", async () => { + const requestedModels: Array = []; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [ + { name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }, + { name: "reviewer", description: "Reviewer", source: "builtin", systemPrompt: "Reviewer prompt" }, + ], + projectAgentsDir: null, + }), + resolveChildModel: ({ taskModel, topLevelModel }: any) => ({ + requestedModel: taskModel ?? topLevelModel, + resolvedModel: taskModel ?? topLevelModel, + }), + runSingleTask: async ({ meta }: any) => { + requestedModels.push(meta.requestedModel); + return { + runId: `${meta.agent}-${meta.task}`, + agent: meta.agent, + agentSource: meta.agentSource, + task: meta.task, + requestedModel: meta.requestedModel, + resolvedModel: meta.requestedModel, + exitCode: 0, + finalText: `${meta.agent}:${meta.task}`, + }; + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + model: "openai/gpt-5", + tasks: [ + { agent: "scout", task: "find auth code" }, + { agent: "reviewer", task: "review auth code", model: "anthropic/claude-opus-4-5" }, + ], + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [ + { provider: "openai", id: "gpt-5" }, + { provider: "anthropic", id: "claude-opus-4-5" }, + ], + }, + hasUI: false, + } as any, + ); + + const text = result.content[0]?.type === "text" ? result.content[0].text : ""; + assert.match(text, /2\/2 succeeded/); + assert.deepEqual(requestedModels, ["openai/gpt-5", "anthropic/claude-opus-4-5"]); +}); + +test("parallel mode rejects per-task model overrides that are not currently available", async () => { + let didRun = false; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [{ name: "scout", description: "Scout", source: "builtin", systemPrompt: "Scout prompt" }], + projectAgentsDir: null, + }), + runSingleTask: async () => { + didRun = true; + throw new Error("should not run"); + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + model: "anthropic/claude-sonnet-4-5", + tasks: [{ agent: "scout", task: "find auth code", model: "openai/gpt-5" }], + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + assert.equal(didRun, false); + assert.equal(result.isError, true); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /parallel task 1/i); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/tool.test.ts b/.pi/agent/extensions/tmux-subagent/src/tool.test.ts new file mode 100644 index 0000000..799c43b --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tool.test.ts @@ -0,0 +1,177 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createSubagentTool } from "./tool.ts"; + +test("single-mode subagent uses the required top-level model, emits progress, and returns final text plus metadata", async () => { + const updates: string[] = []; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [ + { + name: "scout", + description: "Scout", + model: "claude-haiku-4-5", + systemPrompt: "Scout prompt", + source: "builtin", + }, + ], + projectAgentsDir: null, + }), + runSingleTask: async ({ onEvent, meta }: any) => { + onEvent?.({ type: "tool_call", toolName: "read", args: { path: "src/auth.ts" } }); + return { + runId: "run-1", + agent: "scout", + agentSource: "builtin", + task: "inspect auth", + requestedModel: meta.requestedModel, + resolvedModel: meta.resolvedModel, + paneId: "%3", + windowId: "@1", + sessionPath: "/repo/.pi/subagents/runs/run-1/child-session.jsonl", + exitCode: 0, + finalText: "Auth code is in src/auth.ts", + }; + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + agent: "scout", + task: "inspect auth", + model: "anthropic/claude-sonnet-4-5", + }, + undefined, + (partial: any) => { + const first = partial.content?.[0]; + if (first?.type === "text") updates.push(first.text); + }, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + const text = result.content[0]?.type === "text" ? result.content[0].text : ""; + assert.equal(text, "Auth code is in src/auth.ts"); + assert.equal(result.details.results[0]?.paneId, "%3"); + assert.equal(result.details.results[0]?.requestedModel, "anthropic/claude-sonnet-4-5"); + assert.match(updates.join("\n"), /Running scout/); +}); + +test("single-mode subagent requires a top-level model even when execute is called directly", async () => { + let didRun = false; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [{ name: "scout", description: "Scout", systemPrompt: "Scout prompt", source: "builtin" }], + projectAgentsDir: null, + }), + runSingleTask: async () => { + didRun = true; + throw new Error("should not run"); + }, + } as any); + + const result = await tool.execute( + "tool-1", + { agent: "scout", task: "inspect auth" }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + assert.equal(didRun, false); + assert.equal(result.isError, true); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /top-level model/i); +}); + +test("single-mode subagent rejects models that are not currently available", async () => { + let didRun = false; + + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [{ name: "scout", description: "Scout", systemPrompt: "Scout prompt", source: "builtin" }], + projectAgentsDir: null, + }), + runSingleTask: async () => { + didRun = true; + throw new Error("should not run"); + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + agent: "scout", + task: "inspect auth", + model: "openai/gpt-5", + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: false, + } as any, + ); + + assert.equal(didRun, false); + assert.equal(result.isError, true); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /available models/i); +}); + +test("single-mode subagent asks before running a project-local agent", async () => { + const tool = createSubagentTool({ + discoverAgents: () => ({ + agents: [ + { + name: "reviewer", + description: "Reviewer", + systemPrompt: "Review prompt", + source: "project", + }, + ], + projectAgentsDir: "/repo/.pi/agents", + }), + runSingleTask: async () => { + throw new Error("should not run"); + }, + } as any); + + const result = await tool.execute( + "tool-1", + { + agent: "reviewer", + task: "review auth", + model: "anthropic/claude-sonnet-4-5", + agentScope: "both", + }, + undefined, + undefined, + { + cwd: "/repo", + modelRegistry: { + getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5" }], + }, + hasUI: true, + ui: { confirm: async () => false }, + } as any, + ); + + assert.equal(result.isError, true); + assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /not approved/); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/tool.ts b/.pi/agent/extensions/tmux-subagent/src/tool.ts new file mode 100644 index 0000000..be1a840 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/tool.ts @@ -0,0 +1,336 @@ +import { Text } from "@mariozechner/pi-tui"; +import { discoverAgents } from "./agents.ts"; +import { + listAvailableModelReferences, + normalizeAvailableModelReference, + resolveChildModel, +} from "./models.ts"; +import { + SubagentParamsSchema, + type AgentScope, + type SubagentRunResult, + type SubagentToolDetails, +} from "./schema.ts"; + +const MAX_PARALLEL_TASKS = 8; +const MAX_CONCURRENCY = 4; + +async function mapWithConcurrencyLimit( + items: TIn[], + concurrency: number, + fn: (item: TIn, index: number) => Promise, +): Promise { + const limit = Math.max(1, Math.min(concurrency, items.length || 1)); + const results = new Array(items.length); + let nextIndex = 0; + + await Promise.all( + Array.from({ length: limit }, async () => { + while (nextIndex < items.length) { + const index = nextIndex++; + results[index] = await fn(items[index], index); + } + }), + ); + + return results; +} + +function isFailure(result: Pick) { + return result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted"; +} + +function makeDetails( + mode: "single" | "parallel" | "chain", + agentScope: AgentScope, + projectAgentsDir: string | null, + results: SubagentRunResult[], +): SubagentToolDetails { + return { mode, agentScope, projectAgentsDir, results }; +} + +function makeErrorResult( + text: string, + mode: "single" | "parallel" | "chain", + agentScope: AgentScope, + projectAgentsDir: string | null, +) { + return { + content: [{ type: "text" as const, text }], + details: makeDetails(mode, agentScope, projectAgentsDir, []), + isError: true, + }; +} + +export function createSubagentTool(deps: { + discoverAgents?: typeof discoverAgents; + listAvailableModelReferences?: typeof listAvailableModelReferences; + normalizeAvailableModelReference?: typeof normalizeAvailableModelReference; + parameters?: typeof SubagentParamsSchema; + resolveChildModel?: typeof resolveChildModel; + runSingleTask?: (input: { + cwd: string; + meta: Record; + onEvent?: (event: any) => void; + }) => Promise; +} = {}) { + return { + name: "subagent", + label: "Subagent", + description: "Delegate tasks to specialized agents running in tmux panes.", + parameters: deps.parameters ?? SubagentParamsSchema, + async execute(_toolCallId: string, params: any, _signal: AbortSignal | undefined, onUpdate: any, ctx: any) { + const hasSingle = Boolean(params.agent && params.task); + const hasParallel = Boolean(params.tasks?.length); + const hasChain = Boolean(params.chain?.length); + const modeCount = Number(hasSingle) + Number(hasParallel) + Number(hasChain); + const mode = hasParallel ? "parallel" : hasChain ? "chain" : "single"; + const agentScope = (params.agentScope ?? "user") as AgentScope; + + if (modeCount !== 1) { + return makeErrorResult("Provide exactly one mode: single, parallel, or chain.", "single", agentScope, null); + } + + const discovery = (deps.discoverAgents ?? discoverAgents)(ctx.cwd, { scope: agentScope }); + const availableModelReferences = (deps.listAvailableModelReferences ?? listAvailableModelReferences)(ctx.modelRegistry); + const availableModelsText = availableModelReferences.join(", ") || "(none)"; + const normalizeModelReference = (requestedModel?: string) => + (deps.normalizeAvailableModelReference ?? normalizeAvailableModelReference)(requestedModel, availableModelReferences); + + if (availableModelReferences.length === 0) { + return makeErrorResult( + "No available models are configured. Configure at least one model before using subagent.", + mode, + agentScope, + discovery.projectAgentsDir, + ); + } + + const topLevelModel = normalizeModelReference(params.model); + if (!topLevelModel) { + const message = + typeof params.model !== "string" || params.model.trim().length === 0 + ? `Subagent requires a top-level model chosen from the available models: ${availableModelsText}` + : `Invalid top-level model "${params.model}". Choose one of the available models: ${availableModelsText}`; + return makeErrorResult(message, mode, agentScope, discovery.projectAgentsDir); + } + params.model = topLevelModel; + + for (const [index, task] of (params.tasks ?? []).entries()) { + if (task.model === undefined) continue; + + const normalizedTaskModel = normalizeModelReference(task.model); + if (!normalizedTaskModel) { + return makeErrorResult( + `Invalid model for parallel task ${index + 1} (${task.agent}): "${task.model}". Choose one of the available models: ${availableModelsText}`, + mode, + agentScope, + discovery.projectAgentsDir, + ); + } + task.model = normalizedTaskModel; + } + + for (const [index, step] of (params.chain ?? []).entries()) { + if (step.model === undefined) continue; + + const normalizedStepModel = normalizeModelReference(step.model); + if (!normalizedStepModel) { + return makeErrorResult( + `Invalid model for chain step ${index + 1} (${step.agent}): "${step.model}". Choose one of the available models: ${availableModelsText}`, + mode, + agentScope, + discovery.projectAgentsDir, + ); + } + step.model = normalizedStepModel; + } + + const requestedAgentNames = [ + ...(hasSingle ? [params.agent] : []), + ...((params.tasks ?? []).map((task: any) => task.agent)), + ...((params.chain ?? []).map((step: any) => step.agent)), + ]; + const projectAgents = requestedAgentNames + .map((name) => discovery.agents.find((candidate) => candidate.name === name)) + .filter((agent): agent is NonNullable => Boolean(agent && agent.source === "project")); + + if (projectAgents.length > 0 && (params.confirmProjectAgents ?? true) && ctx.hasUI) { + const ok = await ctx.ui.confirm( + "Run project-local agents?", + `Agents: ${projectAgents.map((agent) => agent.name).join(", ")}\nSource: ${ + discovery.projectAgentsDir ?? "(unknown)" + }`, + ); + if (!ok) { + return makeErrorResult( + "Canceled: project-local agents not approved.", + mode, + agentScope, + discovery.projectAgentsDir, + ); + } + } + + const resolveAgent = (name: string) => { + const agent = discovery.agents.find((candidate) => candidate.name === name); + if (!agent) throw new Error(`Unknown agent: ${name}`); + return agent; + }; + + const runTask = async (input: { + agentName: string; + task: string; + cwd?: string; + taskModel?: string; + taskIndex?: number; + step?: number; + mode: "single" | "parallel" | "chain"; + }) => { + const agent = resolveAgent(input.agentName); + const model = (deps.resolveChildModel ?? resolveChildModel)({ + taskModel: input.taskModel, + topLevelModel: params.model, + }); + + return deps.runSingleTask?.({ + cwd: input.cwd ?? ctx.cwd, + onEvent(event) { + onUpdate?.({ + content: [{ type: "text", text: `Running ${input.agentName}: ${event.type}` }], + details: makeDetails(input.mode, agentScope, discovery.projectAgentsDir, []), + }); + }, + meta: { + mode: input.mode, + taskIndex: input.taskIndex, + step: input.step, + agent: agent.name, + agentSource: agent.source, + task: input.task, + cwd: input.cwd ?? ctx.cwd, + requestedModel: model.requestedModel, + resolvedModel: model.resolvedModel, + systemPrompt: agent.systemPrompt, + tools: agent.tools, + }, + }) as Promise; + }; + + if (hasSingle) { + try { + const result = await runTask({ + agentName: params.agent, + task: params.task, + cwd: params.cwd, + mode: "single", + }); + + return { + content: [{ type: "text" as const, text: result.finalText }], + details: makeDetails("single", agentScope, discovery.projectAgentsDir, [result]), + isError: isFailure(result), + }; + } catch (error) { + return { + content: [{ type: "text" as const, text: (error as Error).message }], + details: makeDetails("single", agentScope, discovery.projectAgentsDir, []), + isError: true, + }; + } + } + + if (hasParallel) { + if (params.tasks.length > MAX_PARALLEL_TASKS) { + return { + content: [ + { + type: "text" as const, + text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`, + }, + ], + details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, []), + isError: true, + }; + } + + const liveResults: SubagentRunResult[] = []; + const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (task: any, index) => { + const result = await runTask({ + agentName: task.agent, + task: task.task, + cwd: task.cwd, + taskModel: task.model, + taskIndex: index, + mode: "parallel", + }); + liveResults[index] = result; + onUpdate?.({ + content: [{ type: "text", text: `Parallel: ${liveResults.filter(Boolean).length}/${params.tasks.length} finished` }], + details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, liveResults.filter(Boolean)), + }); + return result; + }); + + const successCount = results.filter((result) => !isFailure(result)).length; + const summary = results + .map((result) => `[${result.agent}] ${isFailure(result) ? "failed" : "completed"}: ${result.finalText || "(no output)"}`) + .join("\n\n"); + + return { + content: [{ type: "text" as const, text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summary}` }], + details: makeDetails("parallel", agentScope, discovery.projectAgentsDir, results), + isError: successCount !== results.length, + }; + } + + const results: SubagentRunResult[] = []; + let previous = ""; + for (let index = 0; index < params.chain.length; index += 1) { + const item = params.chain[index]; + const task = item.task.replaceAll("{previous}", previous); + const result = await runTask({ + agentName: item.agent, + task, + cwd: item.cwd, + taskModel: item.model, + step: index + 1, + mode: "chain", + }); + onUpdate?.({ + content: [{ type: "text", text: `Chain: completed step ${index + 1}/${params.chain.length}` }], + details: makeDetails("chain", agentScope, discovery.projectAgentsDir, [...results, result]), + }); + results.push(result); + if (isFailure(result)) { + return { + content: [ + { + type: "text" as const, + text: `Chain stopped at step ${index + 1} (${item.agent}): ${result.finalText || result.stopReason || "failed"}`, + }, + ], + details: makeDetails("chain", agentScope, discovery.projectAgentsDir, results), + isError: true, + }; + } + previous = result.finalText; + } + + const finalResult = results[results.length - 1]; + return { + content: [{ type: "text" as const, text: finalResult?.finalText ?? "" }], + details: makeDetails("chain", agentScope, discovery.projectAgentsDir, results), + }; + }, + renderCall(args: any) { + if (args.tasks?.length) return new Text(`subagent parallel (${args.tasks.length} tasks)`, 0, 0); + if (args.chain?.length) return new Text(`subagent chain (${args.chain.length} steps)`, 0, 0); + return new Text(`subagent ${args.agent ?? ""}`.trim(), 0, 0); + }, + renderResult(result: { content: Array<{ type: string; text?: string }> }) { + const first = result.content[0]; + return new Text(first?.type === "text" ? first.text ?? "" : "", 0, 0); + }, + }; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.mjs b/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.mjs new file mode 100644 index 0000000..5b53884 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.mjs @@ -0,0 +1,214 @@ +import { appendFile, readFile, writeFile } from "node:fs/promises"; +import { spawn } from "node:child_process"; +import { normalizePiEvent } from "./normalize.mjs"; +import { renderHeader, renderEventLine } from "./render.mjs"; + +async function appendJsonLine(path, value) { + await appendBestEffort(path, `${JSON.stringify(value)}\n`); +} + +async function appendBestEffort(path, text) { + try { + await appendFile(path, text, "utf8"); + } catch { + // Best-effort artifact logging should never prevent result.json from being written. + } +} + +function makeResult(meta, startedAt, input = {}) { + const errorText = typeof input.errorMessage === "string" ? input.errorMessage.trim() : ""; + const exitCode = typeof input.exitCode === "number" ? input.exitCode : 1; + return { + runId: meta.runId, + mode: meta.mode, + taskIndex: meta.taskIndex, + step: meta.step, + agent: meta.agent, + agentSource: meta.agentSource, + task: meta.task, + cwd: meta.cwd, + requestedModel: meta.requestedModel, + resolvedModel: input.resolvedModel ?? meta.resolvedModel, + sessionPath: meta.sessionPath, + startedAt, + finishedAt: new Date().toISOString(), + exitCode, + stopReason: input.stopReason ?? (exitCode === 0 ? undefined : "error"), + finalText: input.finalText ?? "", + usage: input.usage, + stdoutPath: meta.stdoutPath, + stderrPath: meta.stderrPath, + resultPath: meta.resultPath, + eventsPath: meta.eventsPath, + transcriptPath: meta.transcriptPath, + errorMessage: errorText || undefined, + }; +} + +async function runWrapper(meta, startedAt) { + const header = renderHeader(meta); + await appendBestEffort(meta.transcriptPath, `${header}\n`); + console.log(header); + + const effectiveModel = + typeof meta.resolvedModel === "string" && meta.resolvedModel.length > 0 + ? meta.resolvedModel + : meta.requestedModel; + + const args = ["--mode", "json", "--session", meta.sessionPath]; + if (effectiveModel) args.push("--model", effectiveModel); + if (Array.isArray(meta.tools) && meta.tools.length > 0) args.push("--tools", meta.tools.join(",")); + if (meta.systemPromptPath) args.push("--append-system-prompt", meta.systemPromptPath); + args.push(meta.task); + + let finalText = ""; + let resolvedModel = meta.resolvedModel; + let stopReason; + let usage = undefined; + let stdoutBuffer = ""; + let stderrText = ""; + let spawnError; + let queue = Promise.resolve(); + + const enqueue = (work) => { + queue = queue.then(work, work); + return queue; + }; + + const handleStdoutLine = async (line) => { + if (!line.trim()) return; + + let parsed; + try { + parsed = JSON.parse(line); + } catch { + return; + } + + const normalized = normalizePiEvent(parsed); + if (!normalized) return; + + await appendJsonLine(meta.eventsPath, normalized); + const rendered = renderEventLine(normalized); + await appendBestEffort(meta.transcriptPath, `${rendered}\n`); + console.log(rendered); + + if (normalized.type === "assistant_text") { + finalText = normalized.text; + resolvedModel = normalized.model ?? resolvedModel; + stopReason = normalized.stopReason ?? stopReason; + usage = normalized.usage ?? usage; + } + }; + + const childEnv = { ...process.env }; + // Ensure the copilot initiator flag is not accidentally inherited from the parent + // environment; set it only for github-copilot models. + delete childEnv.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR; + // Mark every child run as a nested tmux subagent so it cannot spawn further subagents. + childEnv.PI_TMUX_SUBAGENT_CHILD = "1"; + + if (typeof effectiveModel === "string" && effectiveModel.startsWith("github-copilot/")) { + childEnv.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR = "agent"; + } + + const child = spawn("pi", args, { + cwd: meta.cwd, + env: childEnv, + stdio: ["ignore", "pipe", "pipe"], + }); + + child.stdout.on("data", (chunk) => { + const text = chunk.toString(); + enqueue(async () => { + stdoutBuffer += text; + await appendBestEffort(meta.stdoutPath, text); + + const lines = stdoutBuffer.split("\n"); + stdoutBuffer = lines.pop() ?? ""; + + for (const line of lines) { + await handleStdoutLine(line); + } + }); + }); + + child.stderr.on("data", (chunk) => { + const text = chunk.toString(); + enqueue(async () => { + stderrText += text; + await appendBestEffort(meta.stderrPath, text); + }); + }); + + const exitCode = await new Promise((resolve) => { + let done = false; + const finish = (code) => { + if (done) return; + done = true; + resolve(code); + }; + + child.on("error", (error) => { + spawnError = error; + finish(1); + }); + + child.on("close", (code) => { + finish(code ?? (spawnError ? 1 : 0)); + }); + }); + + await queue; + + if (stdoutBuffer.trim()) { + await handleStdoutLine(stdoutBuffer); + stdoutBuffer = ""; + } + + if (spawnError) { + const message = spawnError instanceof Error ? spawnError.stack ?? spawnError.message : String(spawnError); + if (!stderrText.trim()) { + stderrText = message; + await appendBestEffort(meta.stderrPath, `${message}\n`); + } + } + + return makeResult(meta, startedAt, { + exitCode, + stopReason, + finalText, + usage, + resolvedModel, + errorMessage: stderrText, + }); +} + +async function main() { + const metaPath = process.argv[2]; + if (!metaPath) throw new Error("Expected meta.json path as argv[2]"); + + const meta = JSON.parse(await readFile(metaPath, "utf8")); + const startedAt = meta.startedAt ?? new Date().toISOString(); + + let result; + try { + result = await runWrapper(meta, startedAt); + } catch (error) { + const message = error instanceof Error ? error.stack ?? error.message : String(error); + await appendBestEffort(meta.stderrPath, `${message}\n`); + result = makeResult(meta, startedAt, { + exitCode: 1, + stopReason: "error", + errorMessage: message, + }); + } + + await writeFile(meta.resultPath, JSON.stringify(result, null, 2), "utf8"); + if (result.exitCode !== 0) process.exitCode = result.exitCode; +} + +main().catch((error) => { + console.error(error instanceof Error ? error.stack : String(error)); + process.exitCode = 1; +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.test.ts b/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.test.ts new file mode 100644 index 0000000..0075881 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/cli.test.ts @@ -0,0 +1,192 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises"; +import { spawn } from "node:child_process"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +function waitForExit(child: ReturnType, timeoutMs = 1500): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + child.kill("SIGKILL"); + reject(new Error(`wrapper did not exit within ${timeoutMs}ms`)); + }, timeoutMs); + + child.on("error", (error) => { + clearTimeout(timeout); + reject(error); + }); + + child.on("close", (code) => { + clearTimeout(timeout); + resolve(code ?? 0); + }); + }); +} + +async function runWrapperWithFakePi(requestedModel: string, resolvedModel?: string) { + const dir = await mkdtemp(join(tmpdir(), "tmux-subagent-wrapper-")); + const metaPath = join(dir, "meta.json"); + const resultPath = join(dir, "result.json"); + const capturePath = join(dir, "capture.json"); + const piPath = join(dir, "pi"); + + // The fake `pi` is a small Node script that writes a JSON capture file + // including relevant PI_* environment variables and the argv it received. + const resolved = typeof resolvedModel === "string" ? resolvedModel : requestedModel; + await writeFile( + piPath, + [ + `#!${process.execPath}`, + "const fs = require('fs');", + `const capturePath = ${JSON.stringify(capturePath)};`, + "const obj = {", + " PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR: process.env.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR || '',", + " PI_TMUX_SUBAGENT_CHILD: process.env.PI_TMUX_SUBAGENT_CHILD || '',", + " argv: process.argv.slice(2)", + "};", + "fs.writeFileSync(capturePath, JSON.stringify(obj), 'utf8');", + "console.log(JSON.stringify({type:'message_end',message:{role:'assistant',content:[{type:'text',text:'done'}],model:'github-copilot/gpt-4o',stopReason:'stop'}}));", + ].join("\n"), + "utf8", + ); + await chmod(piPath, 0o755); + + await writeFile( + metaPath, + JSON.stringify( + { + runId: "run-1", + mode: "single", + agent: "scout", + agentSource: "builtin", + task: "inspect auth", + cwd: dir, + requestedModel, + resolvedModel: resolved, + startedAt: "2026-04-09T00:00:00.000Z", + sessionPath: join(dir, "child-session.jsonl"), + eventsPath: join(dir, "events.jsonl"), + resultPath, + stdoutPath: join(dir, "stdout.log"), + stderrPath: join(dir, "stderr.log"), + transcriptPath: join(dir, "transcript.log"), + systemPromptPath: join(dir, "system-prompt.md"), + }, + null, + 2, + ), + "utf8", + ); + + const wrapperPath = join(dirname(fileURLToPath(import.meta.url)), "cli.mjs"); + const child = spawn(process.execPath, [wrapperPath, metaPath], { + env: { + ...process.env, + PATH: dir, + }, + stdio: ["ignore", "pipe", "pipe"], + }); + + const exitCode = await waitForExit(child); + assert.equal(exitCode, 0); + + const captureJson = JSON.parse(await readFile(capturePath, "utf8")); + return { flags: captureJson }; +} + +// Dedicated tests: every child run must have PI_TMUX_SUBAGENT_CHILD=1 +test("wrapper marks github-copilot child run as a tmux subagent child", async () => { + const captured = await runWrapperWithFakePi("github-copilot/gpt-4o"); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_CHILD, "1"); +}); + +test("wrapper marks anthropic child run as a tmux subagent child", async () => { + const captured = await runWrapperWithFakePi("anthropic/claude-sonnet-4-5"); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_CHILD, "1"); +}); + +test("wrapper marks github-copilot child runs as agent-initiated", async () => { + const captured = await runWrapperWithFakePi("github-copilot/gpt-4o"); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR, "agent"); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_CHILD, "1"); +}); + +test("wrapper leaves non-copilot child runs unchanged", async () => { + const captured = await runWrapperWithFakePi("anthropic/claude-sonnet-4-5"); + // The wrapper should not inject the copilot initiator for non-copilot models. + assert.equal(captured.flags.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR, ""); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_CHILD, "1"); +}); + +// Regression test: ensure when requestedModel and resolvedModel differ, the +// wrapper uses the same effective model for the child --model arg and the +// copilot initiator env flag. +test("wrapper uses effective model for both argv and env when requested/resolved differ", async () => { + const requested = "anthropic/claude-sonnet-4-5"; + const resolved = "github-copilot/gpt-4o"; + + const captured = await runWrapperWithFakePi(requested, resolved); + + // The effective model should be the resolved model in this case. + assert.equal(captured.flags.PI_TMUX_SUBAGENT_GITHUB_COPILOT_INITIATOR, "agent"); + assert.equal(captured.flags.PI_TMUX_SUBAGENT_CHILD, "1"); + + // Verify the child argv contains the effective model after a --model flag. + const argv = captured.flags.argv; + const modelIndex = argv.indexOf("--model"); + assert.ok(modelIndex >= 0, "expected --model in argv"); + assert.equal(argv[modelIndex + 1], resolved); +}); + +test("wrapper exits and writes result.json when the pi child cannot be spawned", async () => { + const dir = await mkdtemp(join(tmpdir(), "tmux-subagent-wrapper-")); + const metaPath = join(dir, "meta.json"); + const resultPath = join(dir, "result.json"); + + await writeFile( + metaPath, + JSON.stringify( + { + runId: "run-1", + mode: "single", + agent: "scout", + agentSource: "builtin", + task: "inspect auth", + cwd: dir, + requestedModel: "anthropic/claude-sonnet-4-5", + resolvedModel: "anthropic/claude-sonnet-4-5", + startedAt: "2026-04-09T00:00:00.000Z", + sessionPath: join(dir, "child-session.jsonl"), + eventsPath: join(dir, "events.jsonl"), + resultPath, + stdoutPath: join(dir, "stdout.log"), + stderrPath: join(dir, "stderr.log"), + transcriptPath: join(dir, "transcript.log"), + systemPromptPath: join(dir, "system-prompt.md"), + }, + null, + 2, + ), + "utf8", + ); + + const wrapperPath = join(dirname(fileURLToPath(import.meta.url)), "cli.mjs"); + const child = spawn(process.execPath, [wrapperPath, metaPath], { + env: { + ...process.env, + PATH: dir, + }, + stdio: ["ignore", "pipe", "pipe"], + }); + + const exitCode = await waitForExit(child); + assert.equal(exitCode, 1); + + const result = JSON.parse(await readFile(resultPath, "utf8")); + assert.equal(result.runId, "run-1"); + assert.equal(result.agent, "scout"); + assert.equal(result.exitCode, 1); + assert.match(result.errorMessage ?? "", /ENOENT|not found|spawn pi/i); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.mjs b/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.mjs new file mode 100644 index 0000000..deae2c4 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.mjs @@ -0,0 +1,35 @@ +export function normalizePiEvent(event) { + if (event?.type === "tool_execution_start") { + return { + type: "tool_call", + toolName: event.toolName, + args: event.args ?? {}, + }; + } + + if (event?.type === "message_end" && event.message?.role === "assistant") { + const text = (event.message.content ?? []) + .filter((part) => part.type === "text") + .map((part) => part.text) + .join("\n") + .trim(); + + return { + type: "assistant_text", + text, + model: event.message.model, + stopReason: event.message.stopReason, + usage: event.message.usage, + }; + } + + if (event?.type === "tool_execution_end") { + return { + type: "tool_result", + toolName: event.toolName, + isError: Boolean(event.isError), + }; + } + + return null; +} diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.test.ts b/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.test.ts new file mode 100644 index 0000000..78feff4 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/normalize.test.ts @@ -0,0 +1,38 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { normalizePiEvent } from "./normalize.mjs"; + +test("normalizePiEvent converts tool start events into protocol tool-call records", () => { + const normalized = normalizePiEvent({ + type: "tool_execution_start", + toolName: "read", + args: { path: "src/app.ts", offset: 1, limit: 20 }, + }); + + assert.deepEqual(normalized, { + type: "tool_call", + toolName: "read", + args: { path: "src/app.ts", offset: 1, limit: 20 }, + }); +}); + +test("normalizePiEvent converts assistant message_end into a final-text record", () => { + const normalized = normalizePiEvent({ + type: "message_end", + message: { + role: "assistant", + model: "anthropic/claude-sonnet-4-5", + stopReason: "end_turn", + content: [{ type: "text", text: "Final answer" }], + usage: { input: 10, output: 5, totalTokens: 15, cost: { total: 0.001 } }, + }, + }); + + assert.deepEqual(normalized, { + type: "assistant_text", + text: "Final answer", + model: "anthropic/claude-sonnet-4-5", + stopReason: "end_turn", + usage: { input: 10, output: 5, totalTokens: 15, cost: { total: 0.001 } }, + }); +}); diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/render.mjs b/.pi/agent/extensions/tmux-subagent/src/wrapper/render.mjs new file mode 100644 index 0000000..b2656e9 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/render.mjs @@ -0,0 +1,33 @@ +function shortenCommand(command) { + return command.length > 100 ? `${command.slice(0, 100)}…` : command; +} + +export function renderHeader(meta) { + return [ + "=== tmux subagent ===", + `Agent: ${meta.agent}`, + `Task: ${meta.task}`, + `CWD: ${meta.cwd}`, + `Requested model: ${meta.requestedModel ?? "(default)"}`, + `Resolved model: ${meta.resolvedModel ?? "(pending)"}`, + `Session: ${meta.sessionPath}`, + "---------------------", + ].join("\n"); +} + +export function renderEventLine(event) { + if (event.type === "tool_call") { + if (event.toolName === "bash") return `$ ${shortenCommand(event.args.command ?? "")}`; + return `→ ${event.toolName} ${JSON.stringify(event.args)}`; + } + + if (event.type === "tool_result") { + return event.isError ? `✗ ${event.toolName} failed` : `✓ ${event.toolName} done`; + } + + if (event.type === "assistant_text") { + return event.text || "(no assistant text)"; + } + + return JSON.stringify(event); +} diff --git a/.pi/agent/extensions/tmux-subagent/src/wrapper/render.test.ts b/.pi/agent/extensions/tmux-subagent/src/wrapper/render.test.ts new file mode 100644 index 0000000..e3b22d0 --- /dev/null +++ b/.pi/agent/extensions/tmux-subagent/src/wrapper/render.test.ts @@ -0,0 +1,28 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { renderHeader, renderEventLine } from "./render.mjs"; + +test("renderHeader prints the key wrapper metadata", () => { + const header = renderHeader({ + agent: "scout", + task: "Inspect authentication code", + cwd: "/repo", + requestedModel: "anthropic/claude-sonnet-4-5", + resolvedModel: "anthropic/claude-sonnet-4-5", + sessionPath: "/repo/.pi/subagents/runs/run-1/child-session.jsonl", + }); + + assert.match(header, /Agent: scout/); + assert.match(header, /Task: Inspect authentication code/); + assert.match(header, /Session: \/repo\/\.pi\/subagents\/runs\/run-1\/child-session\.jsonl/); +}); + +test("renderEventLine makes tool calls readable for a tmux pane", () => { + const line = renderEventLine({ + type: "tool_call", + toolName: "bash", + args: { command: "rg -n authentication src" }, + }); + + assert.equal(line, "$ rg -n authentication src"); +}); diff --git a/.pi/agent/extensions/web-search/index.ts b/.pi/agent/extensions/web-search/index.ts index 27a1cef..79ebc52 100644 --- a/.pi/agent/extensions/web-search/index.ts +++ b/.pi/agent/extensions/web-search/index.ts @@ -1,30 +1,13 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { loadWebSearchConfig } from "./src/config.ts"; -import { createExaProvider } from "./src/providers/exa.ts"; -import type { WebProvider } from "./src/providers/types.ts"; +import { registerWebSearchConfigCommand } from "./src/commands/web-search-config.ts"; +import { createWebSearchRuntime } from "./src/runtime.ts"; import { createWebFetchTool } from "./src/tools/web-fetch.ts"; import { createWebSearchTool } from "./src/tools/web-search.ts"; -async function resolveProvider(providerName?: string): Promise { - const config = await loadWebSearchConfig(); - const selectedName = providerName ?? config.defaultProviderName; - const providerConfig = config.providersByName.get(selectedName); - - if (!providerConfig) { - throw new Error( - `Unknown web-search provider \"${selectedName}\". Configured providers: ${[...config.providersByName.keys()].join(", ")}`, - ); - } - - switch (providerConfig.type) { - case "exa": - return createExaProvider(providerConfig); - default: - throw new Error(`Unsupported web-search provider type: ${(providerConfig as { type: string }).type}`); - } -} - export default function webSearch(pi: ExtensionAPI) { - pi.registerTool(createWebSearchTool({ resolveProvider })); - pi.registerTool(createWebFetchTool({ resolveProvider })); + const runtime = createWebSearchRuntime(); + + pi.registerTool(createWebSearchTool({ executeSearch: runtime.search })); + pi.registerTool(createWebFetchTool({ executeFetch: runtime.fetch })); + registerWebSearchConfigCommand(pi); } diff --git a/.pi/agent/extensions/web-search/src/commands/web-search-config.test.ts b/.pi/agent/extensions/web-search/src/commands/web-search-config.test.ts new file mode 100644 index 0000000..deccebe --- /dev/null +++ b/.pi/agent/extensions/web-search/src/commands/web-search-config.test.ts @@ -0,0 +1,65 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + createDefaultWebSearchConfig, + removeProviderOrThrow, + renameProviderOrThrow, + setDefaultProviderOrThrow, + updateProviderOrThrow, +} from "./web-search-config.ts"; + +test("createDefaultWebSearchConfig builds a Tavily-first file", () => { + const config = createDefaultWebSearchConfig({ + tavilyName: "tavily-main", + tavilyApiKey: "tvly-test-key", + }); + + assert.equal(config.defaultProvider, "tavily-main"); + assert.equal(config.providers[0]?.type, "tavily"); +}); + +test("renameProviderOrThrow updates defaultProvider when renaming the default", () => { + const config = createDefaultWebSearchConfig({ + tavilyName: "tavily-main", + tavilyApiKey: "tvly-test-key", + }); + + const next = renameProviderOrThrow(config, "tavily-main", "tavily-primary"); + + assert.equal(next.defaultProvider, "tavily-primary"); + assert.equal(next.providers[0]?.name, "tavily-primary"); +}); + +test("removeProviderOrThrow rejects removing the last provider", () => { + const config = createDefaultWebSearchConfig({ + tavilyName: "tavily-main", + tavilyApiKey: "tvly-test-key", + }); + + assert.throws(() => removeProviderOrThrow(config, "tavily-main"), /last provider/); +}); + +test("setDefaultProviderOrThrow requires an existing provider name", () => { + const config = createDefaultWebSearchConfig({ + tavilyName: "tavily-main", + tavilyApiKey: "tvly-test-key", + }); + + assert.throws(() => setDefaultProviderOrThrow(config, "missing"), /Unknown provider/); +}); + +test("updateProviderOrThrow can change provider-specific options without changing type", () => { + const config = createDefaultWebSearchConfig({ + tavilyName: "tavily-main", + tavilyApiKey: "tvly-test-key", + }); + + const next = updateProviderOrThrow(config, "tavily-main", { + apiKey: "tvly-next-key", + options: { defaultSearchLimit: 8 }, + }); + + assert.equal(next.providers[0]?.apiKey, "tvly-next-key"); + assert.equal(next.providers[0]?.options?.defaultSearchLimit, 8); + assert.equal(next.providers[0]?.type, "tavily"); +}); diff --git a/.pi/agent/extensions/web-search/src/commands/web-search-config.ts b/.pi/agent/extensions/web-search/src/commands/web-search-config.ts new file mode 100644 index 0000000..793cb52 --- /dev/null +++ b/.pi/agent/extensions/web-search/src/commands/web-search-config.ts @@ -0,0 +1,229 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { + getDefaultWebSearchConfigPath, + readRawWebSearchConfig, + writeWebSearchConfig, + WebSearchConfigError, +} from "../config.ts"; +import type { WebSearchConfig, WebSearchProviderConfig } from "../schema.ts"; + +export function createDefaultWebSearchConfig(input: { tavilyName: string; tavilyApiKey: string }): WebSearchConfig { + return { + defaultProvider: input.tavilyName, + providers: [ + { + name: input.tavilyName, + type: "tavily", + apiKey: input.tavilyApiKey, + }, + ], + }; +} + +export function setDefaultProviderOrThrow(config: WebSearchConfig, providerName: string): WebSearchConfig { + if (!config.providers.some((provider) => provider.name === providerName)) { + throw new Error(`Unknown provider: ${providerName}`); + } + return { ...config, defaultProvider: providerName }; +} + +export function renameProviderOrThrow( + config: WebSearchConfig, + currentName: string, + nextName: string, +): WebSearchConfig { + if (!nextName.trim()) { + throw new Error("Provider name cannot be blank."); + } + if (config.providers.some((provider) => provider.name === nextName && provider.name !== currentName)) { + throw new Error(`Duplicate provider name: ${nextName}`); + } + + return { + defaultProvider: config.defaultProvider === currentName ? nextName : config.defaultProvider, + providers: config.providers.map((provider) => + provider.name === currentName ? { ...provider, name: nextName } : provider, + ), + }; +} + +export function updateProviderOrThrow( + config: WebSearchConfig, + providerName: string, + patch: { apiKey?: string; options?: WebSearchProviderConfig["options"] }, +): WebSearchConfig { + const existing = config.providers.find((provider) => provider.name === providerName); + if (!existing) { + throw new Error(`Unknown provider: ${providerName}`); + } + if (patch.apiKey !== undefined && !patch.apiKey.trim()) { + throw new Error("Provider apiKey cannot be blank."); + } + + return { + ...config, + providers: config.providers.map((provider) => + provider.name === providerName + ? { + ...provider, + apiKey: patch.apiKey ?? provider.apiKey, + options: patch.options ?? provider.options, + } + : provider, + ), + }; +} + +export function removeProviderOrThrow(config: WebSearchConfig, providerName: string): WebSearchConfig { + if (config.providers.length === 1) { + throw new Error("Cannot remove the last provider."); + } + if (config.defaultProvider === providerName) { + throw new Error("Cannot remove the default provider before selecting a new default."); + } + return { + ...config, + providers: config.providers.filter((provider) => provider.name !== providerName), + }; +} + +function upsertProviderOrThrow(config: WebSearchConfig, nextProvider: WebSearchProviderConfig): WebSearchConfig { + if (!nextProvider.name.trim()) { + throw new Error("Provider name cannot be blank."); + } + if (!nextProvider.apiKey.trim()) { + throw new Error("Provider apiKey cannot be blank."); + } + + const withoutSameName = config.providers.filter((provider) => provider.name !== nextProvider.name); + return { + ...config, + providers: [...withoutSameName, nextProvider], + }; +} + +async function promptProviderOptions(ctx: any, provider: WebSearchProviderConfig) { + const defaultSearchLimit = await ctx.ui.input( + `Default search limit for ${provider.name}`, + provider.options?.defaultSearchLimit !== undefined ? String(provider.options.defaultSearchLimit) : "", + ); + const defaultFetchTextMaxCharacters = await ctx.ui.input( + `Default fetch text max characters for ${provider.name}`, + provider.options?.defaultFetchTextMaxCharacters !== undefined + ? String(provider.options.defaultFetchTextMaxCharacters) + : "", + ); + + const options = { + defaultSearchLimit: defaultSearchLimit ? Number(defaultSearchLimit) : undefined, + defaultFetchTextMaxCharacters: defaultFetchTextMaxCharacters + ? Number(defaultFetchTextMaxCharacters) + : undefined, + }; + + return Object.values(options).some((value) => value !== undefined) ? options : undefined; +} + +export function registerWebSearchConfigCommand(pi: ExtensionAPI) { + pi.registerCommand("web-search-config", { + description: "Configure Tavily/Exa providers for web_search and web_fetch", + handler: async (_args, ctx) => { + const path = getDefaultWebSearchConfigPath(); + + let config: WebSearchConfig; + try { + config = await readRawWebSearchConfig(path); + } catch (error) { + if (!(error instanceof WebSearchConfigError)) { + throw error; + } + + const tavilyName = await ctx.ui.input("Create Tavily provider", "tavily-main"); + const tavilyApiKey = await ctx.ui.input("Tavily API key", "tvly-..."); + if (!tavilyName || !tavilyApiKey) { + return; + } + config = createDefaultWebSearchConfig({ tavilyName, tavilyApiKey }); + } + + const action = await ctx.ui.select("Web search config", [ + "Set default provider", + "Add Tavily provider", + "Add Exa provider", + "Edit provider", + "Remove provider", + ]); + if (!action) { + return; + } + + if (action === "Set default provider") { + const nextDefault = await ctx.ui.select( + "Choose default provider", + config.providers.map((provider) => provider.name), + ); + if (!nextDefault) { + return; + } + config = setDefaultProviderOrThrow(config, nextDefault); + } + + if (action === "Add Tavily provider") { + const name = await ctx.ui.input("Provider name", "tavily-main"); + const apiKey = await ctx.ui.input("Tavily API key", "tvly-..."); + if (!name || !apiKey) { + return; + } + config = upsertProviderOrThrow(config, { name, type: "tavily", apiKey }); + } + + if (action === "Add Exa provider") { + const name = await ctx.ui.input("Provider name", "exa-fallback"); + const apiKey = await ctx.ui.input("Exa API key", "exa_..."); + if (!name || !apiKey) { + return; + } + config = upsertProviderOrThrow(config, { name, type: "exa", apiKey }); + } + + if (action === "Edit provider") { + const providerName = await ctx.ui.select( + "Choose provider", + config.providers.map((provider) => provider.name), + ); + if (!providerName) { + return; + } + + const existing = config.providers.find((provider) => provider.name === providerName)!; + const nextName = await ctx.ui.input("Provider name", existing.name); + const nextApiKey = await ctx.ui.input(`API key for ${existing.name}`, existing.apiKey); + if (!nextName || !nextApiKey) { + return; + } + + config = renameProviderOrThrow(config, existing.name, nextName); + const renamed = config.providers.find((provider) => provider.name === nextName)!; + const nextOptions = await promptProviderOptions(ctx, renamed); + config = updateProviderOrThrow(config, nextName, { + apiKey: nextApiKey, + options: nextOptions, + }); + } + + if (action === "Remove provider") { + const providerName = await ctx.ui.select( + "Choose provider to remove", + config.providers.map((provider) => provider.name), + ); + if (!providerName) { + return; + } + config = removeProviderOrThrow(config, providerName); + } + + await writeWebSearchConfig(path, config); + ctx.ui.notify(`Saved web-search config to ${path}`, "info"); + }, + }); +} diff --git a/.pi/agent/extensions/web-search/src/config.test.ts b/.pi/agent/extensions/web-search/src/config.test.ts index be0addd..6a32297 100644 --- a/.pi/agent/extensions/web-search/src/config.test.ts +++ b/.pi/agent/extensions/web-search/src/config.test.ts @@ -37,6 +37,30 @@ test("loadWebSearchConfig returns a normalized default provider and provider loo assert.equal(config.providers[0]?.options?.defaultSearchLimit, 7); }); +test("loadWebSearchConfig normalizes a Tavily default with Exa fallback", async () => { + const file = await writeTempConfig({ + defaultProvider: "tavily-main", + providers: [ + { + name: "tavily-main", + type: "tavily", + apiKey: "tvly-test-key", + }, + { + name: "exa-fallback", + type: "exa", + apiKey: "exa-test-key", + }, + ], + }); + + const config = await loadWebSearchConfig(file); + + assert.equal(config.defaultProviderName, "tavily-main"); + assert.equal(config.defaultProvider.type, "tavily"); + assert.equal(config.providersByName.get("exa-fallback")?.type, "exa"); +}); + test("loadWebSearchConfig rejects a missing default provider target", async () => { const file = await writeTempConfig({ defaultProvider: "missing", diff --git a/.pi/agent/extensions/web-search/src/config.ts b/.pi/agent/extensions/web-search/src/config.ts index 56895d3..9b72def 100644 --- a/.pi/agent/extensions/web-search/src/config.ts +++ b/.pi/agent/extensions/web-search/src/config.ts @@ -1,15 +1,19 @@ -import { readFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; import { Value } from "@sinclair/typebox/value"; -import { WebSearchConfigSchema, type ExaProviderConfig, type WebSearchConfig } from "./schema.ts"; +import { + WebSearchConfigSchema, + type WebSearchConfig, + type WebSearchProviderConfig, +} from "./schema.ts"; export interface ResolvedWebSearchConfig { path: string; defaultProviderName: string; - defaultProvider: ExaProviderConfig; - providers: ExaProviderConfig[]; - providersByName: Map; + defaultProvider: WebSearchProviderConfig; + providers: WebSearchProviderConfig[]; + providersByName: Map; } export class WebSearchConfigError extends Error { @@ -26,10 +30,15 @@ export function getDefaultWebSearchConfigPath() { function exampleConfigSnippet() { return JSON.stringify( { - defaultProvider: "exa-main", + defaultProvider: "tavily-main", providers: [ { - name: "exa-main", + name: "tavily-main", + type: "tavily", + apiKey: "tvly-...", + }, + { + name: "exa-fallback", type: "exa", apiKey: "exa_...", }, @@ -41,7 +50,7 @@ function exampleConfigSnippet() { } export function normalizeWebSearchConfig(config: WebSearchConfig, path: string): ResolvedWebSearchConfig { - const providersByName = new Map(); + const providersByName = new Map(); for (const provider of config.providers) { if (!provider.apiKey.trim()) { @@ -69,19 +78,7 @@ export function normalizeWebSearchConfig(config: WebSearchConfig, path: string): }; } -export async function loadWebSearchConfig(path = getDefaultWebSearchConfigPath()) { - let raw: string; - try { - raw = await readFile(path, "utf8"); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") { - throw new WebSearchConfigError( - `Missing web-search config at ${path}.\nCreate ${path} with contents like:\n${exampleConfigSnippet()}`, - ); - } - throw error; - } - +function parseWebSearchConfig(raw: string, path: string) { let parsed: unknown; try { parsed = JSON.parse(raw); @@ -96,5 +93,35 @@ export async function loadWebSearchConfig(path = getDefaultWebSearchConfigPath() ); } - return normalizeWebSearchConfig(parsed as WebSearchConfig, path); + return parsed as WebSearchConfig; +} + +export async function readRawWebSearchConfig(path = getDefaultWebSearchConfigPath()): Promise { + let raw: string; + try { + raw = await readFile(path, "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + throw new WebSearchConfigError( + `Missing web-search config at ${path}.\nCreate ${path} with contents like:\n${exampleConfigSnippet()}`, + ); + } + throw error; + } + + return parseWebSearchConfig(raw, path); +} + +export function stringifyWebSearchConfig(config: WebSearchConfig) { + return `${JSON.stringify(config, null, 2)}\n`; +} + +export async function writeWebSearchConfig(path: string, config: WebSearchConfig) { + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, stringifyWebSearchConfig(config), "utf8"); +} + +export async function loadWebSearchConfig(path = getDefaultWebSearchConfigPath()) { + const parsed = await readRawWebSearchConfig(path); + return normalizeWebSearchConfig(parsed, path); } diff --git a/.pi/agent/extensions/web-search/src/extension.test.ts b/.pi/agent/extensions/web-search/src/extension.test.ts index 49e0054..fab1495 100644 --- a/.pi/agent/extensions/web-search/src/extension.test.ts +++ b/.pi/agent/extensions/web-search/src/extension.test.ts @@ -2,14 +2,19 @@ import test from "node:test"; import assert from "node:assert/strict"; import webSearchExtension from "../index.ts"; -test("the extension entrypoint registers both web_search and web_fetch", () => { +test("the extension entrypoint registers both tools and the config command", () => { const registeredTools: string[] = []; + const registeredCommands: string[] = []; webSearchExtension({ registerTool(tool: { name: string }) { registeredTools.push(tool.name); }, + registerCommand(name: string) { + registeredCommands.push(name); + }, } as any); assert.deepEqual(registeredTools, ["web_search", "web_fetch"]); + assert.deepEqual(registeredCommands, ["web-search-config"]); }); diff --git a/.pi/agent/extensions/web-search/src/format.test.ts b/.pi/agent/extensions/web-search/src/format.test.ts index 7ea273e..995eb1d 100644 --- a/.pi/agent/extensions/web-search/src/format.test.ts +++ b/.pi/agent/extensions/web-search/src/format.test.ts @@ -21,6 +21,27 @@ test("formatSearchOutput renders a compact metadata-only list", () => { assert.match(output, /https:\/\/exa.ai\/docs/); }); +test("formatSearchOutput shows answer and fallback provider metadata", () => { + const output = formatSearchOutput({ + providerName: "exa-fallback", + answer: "pi is a coding agent", + execution: { + actualProviderName: "exa-fallback", + failoverFromProviderName: "tavily-main", + }, + results: [ + { + title: "pi docs", + url: "https://pi.dev", + rawContent: "Very long raw content body", + }, + ], + } as any); + + assert.match(output, /Answer: pi is a coding agent/); + assert.match(output, /Fallback: tavily-main -> exa-fallback/); +}); + test("truncateText shortens long fetch bodies with an ellipsis", () => { assert.equal(truncateText("abcdef", 4), "abc…"); assert.equal(truncateText("abc", 10), "abc"); @@ -51,3 +72,26 @@ test("formatFetchOutput includes both successful and failed URLs", () => { assert.match(output, /429 rate limited/); assert.match(output, /This is a very long…/); }); + +test("formatFetchOutput shows fallback metadata and favicon/images when present", () => { + const output = formatFetchOutput({ + providerName: "exa-fallback", + execution: { + actualProviderName: "exa-fallback", + failoverFromProviderName: "tavily-main", + }, + results: [ + { + url: "https://pi.dev", + title: "pi", + text: "Fetched body", + favicon: "https://pi.dev/favicon.ico", + images: ["https://pi.dev/logo.png"], + }, + ], + } as any); + + assert.match(output, /Fallback: tavily-main -> exa-fallback/); + assert.match(output, /Favicon: https:\/\/pi.dev\/favicon.ico/); + assert.match(output, /Images:/); +}); diff --git a/.pi/agent/extensions/web-search/src/format.ts b/.pi/agent/extensions/web-search/src/format.ts index 3f48046..09565f0 100644 --- a/.pi/agent/extensions/web-search/src/format.ts +++ b/.pi/agent/extensions/web-search/src/format.ts @@ -1,5 +1,15 @@ import type { NormalizedFetchResponse, NormalizedSearchResponse } from "./providers/types.ts"; +function formatFallbackLine(execution?: { + actualProviderName?: string; + failoverFromProviderName?: string; +}) { + if (!execution?.failoverFromProviderName || !execution.actualProviderName) { + return undefined; + } + return `Fallback: ${execution.failoverFromProviderName} -> ${execution.actualProviderName}`; +} + export function truncateText(text: string, maxCharacters = 4000) { if (text.length <= maxCharacters) { return text; @@ -7,14 +17,24 @@ export function truncateText(text: string, maxCharacters = 4000) { return `${text.slice(0, Math.max(0, maxCharacters - 1))}…`; } -export function formatSearchOutput(response: NormalizedSearchResponse) { - if (response.results.length === 0) { - return `No web results via ${response.providerName}.`; +export function formatSearchOutput(response: NormalizedSearchResponse & { execution?: any }) { + const lines: string[] = []; + const fallbackLine = formatFallbackLine(response.execution); + + if (fallbackLine) { + lines.push(fallbackLine, ""); } - const lines = [ - `Found ${response.results.length} web result${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`, - ]; + if (response.answer) { + lines.push(`Answer: ${response.answer}`, ""); + } + + if (response.results.length === 0) { + lines.push(`No web results via ${response.providerName}.`); + return lines.join("\n"); + } + + lines.push(`Found ${response.results.length} web result${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`); for (const [index, result] of response.results.entries()) { lines.push(`${index + 1}. ${result.title ?? "(untitled)"}`); @@ -28,6 +48,14 @@ export function formatSearchOutput(response: NormalizedSearchResponse) { if (typeof result.score === "number") { lines.push(` Score: ${result.score}`); } + + if (result.content) { + lines.push(` Snippet: ${truncateText(result.content, 500)}`); + } + + if (result.rawContent) { + lines.push(` Raw content: ${truncateText(result.rawContent, 700)}`); + } } return lines.join("\n"); @@ -37,11 +65,16 @@ export interface FetchFormatOptions { maxCharactersPerResult?: number; } -export function formatFetchOutput(response: NormalizedFetchResponse, options: FetchFormatOptions = {}) { +export function formatFetchOutput(response: NormalizedFetchResponse & { execution?: any }, options: FetchFormatOptions = {}) { const maxCharactersPerResult = options.maxCharactersPerResult ?? 4000; - const lines = [ - `Fetched ${response.results.length} URL${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`, - ]; + const lines: string[] = []; + const fallbackLine = formatFallbackLine(response.execution); + + if (fallbackLine) { + lines.push(fallbackLine, ""); + } + + lines.push(`Fetched ${response.results.length} URL${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`); for (const result of response.results) { lines.push(""); @@ -66,6 +99,15 @@ export function formatFetchOutput(response: NormalizedFetchResponse, options: Fe lines.push(`- ${highlight}`); } } + if (result.favicon) { + lines.push(`Favicon: ${result.favicon}`); + } + if (result.images?.length) { + lines.push("Images:"); + for (const image of result.images) { + lines.push(`- ${image}`); + } + } if (result.text) { lines.push("Text:"); lines.push(truncateText(result.text, maxCharactersPerResult)); diff --git a/.pi/agent/extensions/web-search/src/providers/tavily.test.ts b/.pi/agent/extensions/web-search/src/providers/tavily.test.ts new file mode 100644 index 0000000..54a98d7 --- /dev/null +++ b/.pi/agent/extensions/web-search/src/providers/tavily.test.ts @@ -0,0 +1,84 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createTavilyProvider } from "./tavily.ts"; + +const baseConfig = { + name: "tavily-main", + type: "tavily" as const, + apiKey: "tvly-test-key", + options: { + defaultSearchLimit: 6, + defaultFetchTextMaxCharacters: 8000, + }, +}; + +test("createTavilyProvider maps search requests to Tavily REST params", async () => { + let captured: RequestInit | undefined; + + const provider = createTavilyProvider(baseConfig, async (_url, init) => { + captured = init; + return new Response( + JSON.stringify({ + answer: "pi is a coding agent", + results: [ + { + title: "pi docs", + url: "https://pi.dev", + content: "pi docs summary", + raw_content: "long raw body", + }, + ], + }), + { status: 200 }, + ); + }); + + const result = await provider.search({ + query: "pi docs", + limit: 4, + tavily: { + includeAnswer: true, + includeRawContent: true, + searchDepth: "advanced", + }, + }); + + const body = JSON.parse(String(captured?.body)); + assert.equal(body.max_results, 4); + assert.equal(body.include_answer, true); + assert.equal(body.include_raw_content, true); + assert.equal(body.search_depth, "advanced"); + assert.equal(result.answer, "pi is a coding agent"); + assert.equal(result.results[0]?.rawContent, "long raw body"); +}); + +test("createTavilyProvider maps extract responses into normalized fetch results", async () => { + const provider = createTavilyProvider(baseConfig, async () => { + return new Response( + JSON.stringify({ + results: [ + { + url: "https://pi.dev", + title: "pi", + raw_content: "Fetched body", + images: ["https://pi.dev/logo.png"], + favicon: "https://pi.dev/favicon.ico", + }, + ], + }), + { status: 200 }, + ); + }); + + const result = await provider.fetch({ + urls: ["https://pi.dev"], + tavily: { + includeImages: true, + includeFavicon: true, + }, + }); + + assert.equal(result.results[0]?.text, "Fetched body"); + assert.deepEqual(result.results[0]?.images, ["https://pi.dev/logo.png"]); + assert.equal(result.results[0]?.favicon, "https://pi.dev/favicon.ico"); +}); diff --git a/.pi/agent/extensions/web-search/src/providers/tavily.ts b/.pi/agent/extensions/web-search/src/providers/tavily.ts new file mode 100644 index 0000000..c85fedd --- /dev/null +++ b/.pi/agent/extensions/web-search/src/providers/tavily.ts @@ -0,0 +1,107 @@ +import type { TavilyProviderConfig } from "../schema.ts"; +import type { + NormalizedFetchRequest, + NormalizedFetchResponse, + NormalizedSearchRequest, + NormalizedSearchResponse, + WebProvider, +} from "./types.ts"; + +export type TavilyFetchLike = (input: string, init?: RequestInit) => Promise; + +async function readError(response: Response) { + const text = await response.text(); + throw new Error(`Tavily ${response.status} ${response.statusText}: ${text.slice(0, 300)}`); +} + +export function createTavilyProvider( + config: TavilyProviderConfig, + fetchImpl: TavilyFetchLike = fetch, +): WebProvider { + return { + name: config.name, + type: config.type, + + async search(request: NormalizedSearchRequest): Promise { + const response = await fetchImpl("https://api.tavily.com/search", { + method: "POST", + headers: { + "content-type": "application/json", + authorization: `Bearer ${config.apiKey}`, + }, + body: JSON.stringify({ + query: request.query, + max_results: request.limit ?? config.options?.defaultSearchLimit ?? 5, + include_domains: request.includeDomains, + exclude_domains: request.excludeDomains, + start_date: request.startPublishedDate, + end_date: request.endPublishedDate, + topic: request.tavily?.topic, + search_depth: request.tavily?.searchDepth, + time_range: request.tavily?.timeRange, + days: request.tavily?.days, + chunks_per_source: request.tavily?.chunksPerSource, + include_answer: request.tavily?.includeAnswer, + include_raw_content: request.tavily?.includeRawContent, + include_images: request.tavily?.includeImages, + }), + }); + + if (!response.ok) { + await readError(response); + } + + const data = (await response.json()) as any; + return { + providerName: config.name, + requestId: data.request_id, + answer: typeof data.answer === "string" ? data.answer : undefined, + results: (data.results ?? []).map((item: any) => ({ + title: item.title ?? null, + url: item.url, + content: typeof item.content === "string" ? item.content : undefined, + rawContent: typeof item.raw_content === "string" ? item.raw_content : undefined, + images: Array.isArray(item.images) ? item.images : undefined, + score: item.score, + publishedDate: item.published_date, + })), + }; + }, + + async fetch(request: NormalizedFetchRequest): Promise { + const response = await fetchImpl("https://api.tavily.com/extract", { + method: "POST", + headers: { + "content-type": "application/json", + authorization: `Bearer ${config.apiKey}`, + }, + body: JSON.stringify({ + urls: request.urls, + query: request.tavily?.query, + extract_depth: request.tavily?.extractDepth, + chunks_per_source: request.tavily?.chunksPerSource, + include_images: request.tavily?.includeImages, + include_favicon: request.tavily?.includeFavicon, + format: request.tavily?.format, + }), + }); + + if (!response.ok) { + await readError(response); + } + + const data = (await response.json()) as any; + return { + providerName: config.name, + requestIds: data.request_id ? [data.request_id] : [], + results: (data.results ?? []).map((item: any) => ({ + url: item.url, + title: item.title ?? null, + text: typeof item.raw_content === "string" ? item.raw_content : undefined, + images: Array.isArray(item.images) ? item.images : undefined, + favicon: typeof item.favicon === "string" ? item.favicon : undefined, + })), + }; + }, + }; +} diff --git a/.pi/agent/extensions/web-search/src/providers/types.ts b/.pi/agent/extensions/web-search/src/providers/types.ts index 5c28515..2d6f265 100644 --- a/.pi/agent/extensions/web-search/src/providers/types.ts +++ b/.pi/agent/extensions/web-search/src/providers/types.ts @@ -1,3 +1,23 @@ +export interface TavilySearchOptions { + searchDepth?: "advanced" | "basic" | "fast" | "ultra-fast"; + topic?: "general" | "news" | "finance"; + timeRange?: string; + days?: number; + chunksPerSource?: number; + includeAnswer?: boolean; + includeRawContent?: boolean; + includeImages?: boolean; +} + +export interface TavilyFetchOptions { + query?: string; + extractDepth?: "basic" | "advanced"; + chunksPerSource?: number; + includeImages?: boolean; + includeFavicon?: boolean; + format?: string; +} + export interface NormalizedSearchRequest { query: string; limit?: number; @@ -7,6 +27,7 @@ export interface NormalizedSearchRequest { endPublishedDate?: string; category?: string; provider?: string; + tavily?: TavilySearchOptions; } export interface NormalizedSearchResult { @@ -16,12 +37,16 @@ export interface NormalizedSearchResult { publishedDate?: string; author?: string; score?: number; + content?: string; + rawContent?: string; + images?: string[]; } export interface NormalizedSearchResponse { providerName: string; requestId?: string; searchTime?: number; + answer?: string; results: NormalizedSearchResult[]; } @@ -32,6 +57,7 @@ export interface NormalizedFetchRequest { summary?: boolean; textMaxCharacters?: number; provider?: string; + tavily?: TavilyFetchOptions; } export interface NormalizedFetchResult { @@ -40,6 +66,8 @@ export interface NormalizedFetchResult { text?: string; highlights?: string[]; summary?: string; + images?: string[]; + favicon?: string; error?: string; } diff --git a/.pi/agent/extensions/web-search/src/runtime.test.ts b/.pi/agent/extensions/web-search/src/runtime.test.ts new file mode 100644 index 0000000..e452af4 --- /dev/null +++ b/.pi/agent/extensions/web-search/src/runtime.test.ts @@ -0,0 +1,85 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createWebSearchRuntime } from "./runtime.ts"; + +function createProvider(name: string, type: string, handlers: Partial) { + return { + name, + type, + async search(request: any) { + return handlers.search?.(request); + }, + async fetch(request: any) { + return handlers.fetch?.(request); + }, + }; +} + +test("search retries Tavily failures once with Exa", async () => { + const runtime = createWebSearchRuntime({ + loadConfig: async () => ({ + path: "test.json", + defaultProviderName: "tavily-main", + defaultProvider: { name: "tavily-main", type: "tavily", apiKey: "tvly" }, + providers: [ + { name: "tavily-main", type: "tavily", apiKey: "tvly" }, + { name: "exa-fallback", type: "exa", apiKey: "exa" }, + ], + providersByName: new Map([ + ["tavily-main", { name: "tavily-main", type: "tavily", apiKey: "tvly" }], + ["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }], + ]), + }), + createProvider(providerConfig) { + if (providerConfig.type === "tavily") { + return createProvider(providerConfig.name, providerConfig.type, { + search: async () => { + throw new Error("503 upstream unavailable"); + }, + }); + } + return createProvider(providerConfig.name, providerConfig.type, { + search: async () => ({ + providerName: providerConfig.name, + results: [{ title: "Exa hit", url: "https://exa.ai" }], + }), + }); + }, + }); + + const result = await runtime.search({ query: "pi docs" }); + + assert.equal(result.execution.actualProviderName, "exa-fallback"); + assert.equal(result.execution.failoverFromProviderName, "tavily-main"); + assert.match(result.execution.failoverReason ?? "", /503/); +}); + +test("search does not retry when Exa was explicitly selected", async () => { + const runtime = createWebSearchRuntime({ + loadConfig: async () => ({ + path: "test.json", + defaultProviderName: "tavily-main", + defaultProvider: { name: "tavily-main", type: "tavily", apiKey: "tvly" }, + providers: [ + { name: "tavily-main", type: "tavily", apiKey: "tvly" }, + { name: "exa-fallback", type: "exa", apiKey: "exa" }, + ], + providersByName: new Map([ + ["tavily-main", { name: "tavily-main", type: "tavily", apiKey: "tvly" }], + ["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }], + ]), + }), + createProvider(providerConfig) { + return createProvider(providerConfig.name, providerConfig.type, { + search: async () => { + throw new Error(`boom:${providerConfig.name}`); + }, + }); + }, + }); + + await assert.rejects( + () => runtime.search({ query: "pi docs", provider: "exa-fallback" }), + /boom:exa-fallback/, + ); +}); diff --git a/.pi/agent/extensions/web-search/src/runtime.ts b/.pi/agent/extensions/web-search/src/runtime.ts new file mode 100644 index 0000000..62c3499 --- /dev/null +++ b/.pi/agent/extensions/web-search/src/runtime.ts @@ -0,0 +1,139 @@ +import { loadWebSearchConfig, type ResolvedWebSearchConfig } from "./config.ts"; +import { createExaProvider } from "./providers/exa.ts"; +import { createTavilyProvider } from "./providers/tavily.ts"; +import type { + NormalizedFetchRequest, + NormalizedFetchResponse, + NormalizedSearchRequest, + NormalizedSearchResponse, + WebProvider, +} from "./providers/types.ts"; +import type { WebSearchProviderConfig } from "./schema.ts"; + +export interface ProviderExecutionMeta { + requestedProviderName?: string; + actualProviderName: string; + failoverFromProviderName?: string; + failoverReason?: string; +} + +export interface RuntimeSearchResponse extends NormalizedSearchResponse { + execution: ProviderExecutionMeta; +} + +export interface RuntimeFetchResponse extends NormalizedFetchResponse { + execution: ProviderExecutionMeta; +} + +export function createWebSearchRuntime( + deps: { + loadConfig?: () => Promise; + createProvider?: (providerConfig: WebSearchProviderConfig) => WebProvider; + } = {}, +) { + const loadConfig = deps.loadConfig ?? loadWebSearchConfig; + const createProvider = deps.createProvider ?? ((providerConfig: WebSearchProviderConfig) => { + switch (providerConfig.type) { + case "tavily": + return createTavilyProvider(providerConfig); + case "exa": + return createExaProvider(providerConfig); + } + }); + + async function resolveConfigAndProvider(providerName?: string) { + const config = await loadConfig(); + const selectedName = providerName ?? config.defaultProviderName; + const selectedConfig = config.providersByName.get(selectedName); + + if (!selectedConfig) { + throw new Error( + `Unknown web-search provider \"${selectedName}\". Configured providers: ${[...config.providersByName.keys()].join(", ")}`, + ); + } + + return { + config, + selectedName, + selectedConfig, + selectedProvider: createProvider(selectedConfig), + }; + } + + async function search(request: NormalizedSearchRequest): Promise { + const { config, selectedName, selectedConfig, selectedProvider } = await resolveConfigAndProvider(request.provider); + + try { + const response = await selectedProvider.search(request); + return { + ...response, + execution: { + requestedProviderName: request.provider, + actualProviderName: selectedName, + }, + }; + } catch (error) { + if (selectedConfig.type !== "tavily") { + throw error; + } + + const fallbackConfig = [...config.providersByName.values()].find((provider) => provider.type === "exa"); + if (!fallbackConfig) { + throw error; + } + + const fallbackProvider = createProvider(fallbackConfig); + const fallbackResponse = await fallbackProvider.search({ ...request, provider: fallbackConfig.name }); + return { + ...fallbackResponse, + execution: { + requestedProviderName: request.provider, + actualProviderName: fallbackConfig.name, + failoverFromProviderName: selectedName, + failoverReason: (error as Error).message, + }, + }; + } + } + + async function fetch(request: NormalizedFetchRequest): Promise { + const { config, selectedName, selectedConfig, selectedProvider } = await resolveConfigAndProvider(request.provider); + + try { + const response = await selectedProvider.fetch(request); + return { + ...response, + execution: { + requestedProviderName: request.provider, + actualProviderName: selectedName, + }, + }; + } catch (error) { + if (selectedConfig.type !== "tavily") { + throw error; + } + + const fallbackConfig = [...config.providersByName.values()].find((provider) => provider.type === "exa"); + if (!fallbackConfig) { + throw error; + } + + const fallbackProvider = createProvider(fallbackConfig); + const fallbackResponse = await fallbackProvider.fetch({ ...request, provider: fallbackConfig.name }); + return { + ...fallbackResponse, + execution: { + requestedProviderName: request.provider, + actualProviderName: fallbackConfig.name, + failoverFromProviderName: selectedName, + failoverReason: (error as Error).message, + }, + }; + } + } + + return { + search, + fetch, + }; +} diff --git a/.pi/agent/extensions/web-search/src/schema.ts b/.pi/agent/extensions/web-search/src/schema.ts index 2930be2..51bd595 100644 --- a/.pi/agent/extensions/web-search/src/schema.ts +++ b/.pi/agent/extensions/web-search/src/schema.ts @@ -13,9 +13,43 @@ export const ExaProviderConfigSchema = Type.Object({ options: Type.Optional(ProviderOptionsSchema), }); +export const TavilyProviderOptionsSchema = Type.Object({ + defaultSearchLimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 20 })), + defaultFetchTextMaxCharacters: Type.Optional(Type.Integer({ minimum: 1 })), +}); + +export const TavilyProviderConfigSchema = Type.Object({ + name: Type.String({ minLength: 1 }), + type: Type.Literal("tavily"), + apiKey: Type.String({ minLength: 1 }), + options: Type.Optional(TavilyProviderOptionsSchema), +}); + +export const WebSearchProviderConfigSchema = Type.Union([ExaProviderConfigSchema, TavilyProviderConfigSchema]); + export const WebSearchConfigSchema = Type.Object({ defaultProvider: Type.String({ minLength: 1 }), - providers: Type.Array(ExaProviderConfigSchema, { minItems: 1 }), + providers: Type.Array(WebSearchProviderConfigSchema, { minItems: 1 }), +}); + +export const TavilySearchToolOptionsSchema = Type.Object({ + searchDepth: Type.Optional(Type.String()), + topic: Type.Optional(Type.String()), + timeRange: Type.Optional(Type.String()), + days: Type.Optional(Type.Integer({ minimum: 1 })), + chunksPerSource: Type.Optional(Type.Integer({ minimum: 1 })), + includeAnswer: Type.Optional(Type.Boolean()), + includeRawContent: Type.Optional(Type.Boolean()), + includeImages: Type.Optional(Type.Boolean()), +}); + +export const TavilyFetchToolOptionsSchema = Type.Object({ + query: Type.Optional(Type.String()), + extractDepth: Type.Optional(Type.String()), + chunksPerSource: Type.Optional(Type.Integer({ minimum: 1 })), + includeImages: Type.Optional(Type.Boolean()), + includeFavicon: Type.Optional(Type.Boolean()), + format: Type.Optional(Type.String()), }); export const WebSearchParamsSchema = Type.Object({ @@ -27,6 +61,7 @@ export const WebSearchParamsSchema = Type.Object({ endPublishedDate: Type.Optional(Type.String()), category: Type.Optional(Type.String()), provider: Type.Optional(Type.String()), + tavily: Type.Optional(TavilySearchToolOptionsSchema), }); export const WebFetchParamsSchema = Type.Object({ @@ -36,10 +71,16 @@ export const WebFetchParamsSchema = Type.Object({ summary: Type.Optional(Type.Boolean()), textMaxCharacters: Type.Optional(Type.Integer({ minimum: 1 })), provider: Type.Optional(Type.String()), + tavily: Type.Optional(TavilyFetchToolOptionsSchema), }); export type ProviderOptions = Static; +export type TavilyProviderOptions = Static; export type ExaProviderConfig = Static; +export type TavilyProviderConfig = Static; +export type WebSearchProviderConfig = Static; export type WebSearchConfig = Static; +export type TavilySearchToolOptions = Static; +export type TavilyFetchToolOptions = Static; export type WebSearchParams = Static; export type WebFetchParams = Static; diff --git a/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts b/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts index b6f8ca5..71d2c13 100644 --- a/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts +++ b/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts @@ -4,7 +4,7 @@ import { createWebFetchTool } from "./web-fetch.ts"; test("web_fetch prepareArguments folds a single url into urls", () => { const tool = createWebFetchTool({ - resolveProvider: async () => { + executeFetch: async () => { throw new Error("not used"); }, }); @@ -15,43 +15,51 @@ test("web_fetch prepareArguments folds a single url into urls", () => { }); }); -test("web_fetch defaults to text and returns formatted fetch results", async () => { - let capturedRequest: Record | undefined; +test("web_fetch forwards nested Tavily extract options to the runtime", async () => { + let capturedRequest: any; const tool = createWebFetchTool({ - resolveProvider: async () => ({ - name: "exa-main", - type: "exa", - async search() { - throw new Error("not used"); - }, - async fetch(request) { - capturedRequest = request as unknown as Record; - return { - providerName: "exa-main", - results: [ - { - url: "https://exa.ai/docs", - title: "Docs", - text: "Body", - }, - ], - }; - }, - }), + executeFetch: async (request) => { + capturedRequest = request; + return { + providerName: "tavily-main", + results: [ + { + url: "https://pi.dev", + title: "Docs", + text: "Body", + }, + ], + execution: { actualProviderName: "tavily-main" }, + }; + }, }); - const result = await tool.execute("tool-1", { urls: ["https://exa.ai/docs"] }, undefined, undefined, undefined); + const result = await tool.execute( + "tool-1", + { + urls: ["https://pi.dev"], + tavily: { + query: "installation", + extractDepth: "advanced", + includeImages: true, + }, + }, + undefined, + undefined, + undefined, + ); - assert.equal(capturedRequest?.text, true); + assert.equal(capturedRequest.tavily.query, "installation"); + assert.equal(capturedRequest.tavily.extractDepth, "advanced"); + assert.equal(capturedRequest.text, true); assert.match((result.content[0] as { text: string }).text, /Body/); - assert.equal((result.details as { results: Array<{ title: string }> }).results[0]?.title, "Docs"); }); test("web_fetch rejects malformed URLs", async () => { const tool = createWebFetchTool({ - resolveProvider: async () => { - throw new Error("should not resolve provider for invalid URLs"); + executeFetch: async () => { + throw new Error("should not execute fetch for invalid URLs"); }, }); diff --git a/.pi/agent/extensions/web-search/src/tools/web-fetch.ts b/.pi/agent/extensions/web-search/src/tools/web-fetch.ts index 82f5a30..08d2443 100644 --- a/.pi/agent/extensions/web-search/src/tools/web-fetch.ts +++ b/.pi/agent/extensions/web-search/src/tools/web-fetch.ts @@ -1,10 +1,10 @@ import { Text } from "@mariozechner/pi-tui"; import { formatFetchOutput } from "../format.ts"; -import type { NormalizedFetchResponse, WebProvider } from "../providers/types.ts"; +import type { NormalizedFetchRequest, NormalizedFetchResponse } from "../providers/types.ts"; import { WebFetchParamsSchema, type WebFetchParams } from "../schema.ts"; interface FetchToolDeps { - resolveProvider(providerName?: string): Promise; + executeFetch(request: NormalizedFetchRequest): Promise; } function normalizeUrl(value: string) { @@ -28,10 +28,11 @@ function normalizeFetchParams(params: WebFetchParams & { url?: string }) { summary: params.summary ?? false, textMaxCharacters: params.textMaxCharacters, provider: params.provider, + tavily: params.tavily, }; } -export function createWebFetchTool({ resolveProvider }: FetchToolDeps) { +export function createWebFetchTool({ executeFetch }: FetchToolDeps) { return { name: "web_fetch", label: "Web Fetch", @@ -56,8 +57,7 @@ export function createWebFetchTool({ resolveProvider }: FetchToolDeps) { async execute(_toolCallId: string, params: WebFetchParams) { const normalized = normalizeFetchParams(params as WebFetchParams & { url?: string }); - const provider = await resolveProvider(normalized.provider); - const response = await provider.fetch(normalized); + const response = await executeFetch(normalized); return { content: [{ type: "text" as const, text: formatFetchOutput(response) }], diff --git a/.pi/agent/extensions/web-search/src/tools/web-search.test.ts b/.pi/agent/extensions/web-search/src/tools/web-search.test.ts index 6fae80c..25af609 100644 --- a/.pi/agent/extensions/web-search/src/tools/web-search.test.ts +++ b/.pi/agent/extensions/web-search/src/tools/web-search.test.ts @@ -2,46 +2,49 @@ import test from "node:test"; import assert from "node:assert/strict"; import { createWebSearchTool } from "./web-search.ts"; -test("web_search executes metadata-only search through the resolved provider", async () => { - let resolvedProviderName: string | undefined; +test("web_search forwards nested Tavily options to the runtime", async () => { + let capturedRequest: any; const tool = createWebSearchTool({ - resolveProvider: async (providerName) => { - resolvedProviderName = providerName; + executeSearch: async (request) => { + capturedRequest = request; return { - name: "exa-main", - type: "exa", - async search(request) { - assert.equal(request.query, "exa docs"); - return { - providerName: "exa-main", - results: [ - { - title: "Exa Docs", - url: "https://exa.ai/docs", - score: 0.98, - }, - ], - }; - }, - async fetch() { - throw new Error("not used"); - }, + providerName: "tavily-main", + results: [ + { + title: "Docs", + url: "https://pi.dev", + }, + ], + execution: { actualProviderName: "tavily-main" }, }; }, }); - const result = await tool.execute("tool-1", { query: "exa docs" }, undefined, undefined, undefined); + const result = await tool.execute( + "tool-1", + { + query: "pi docs", + tavily: { + includeAnswer: true, + includeRawContent: true, + searchDepth: "advanced", + }, + }, + undefined, + undefined, + undefined, + ); - assert.equal(resolvedProviderName, undefined); - assert.match((result.content[0] as { text: string }).text, /Exa Docs/); - assert.equal((result.details as { results: Array<{ url: string }> }).results[0]?.url, "https://exa.ai/docs"); + assert.equal(capturedRequest.tavily.includeAnswer, true); + assert.equal(capturedRequest.tavily.searchDepth, "advanced"); + assert.match((result.content[0] as { text: string }).text, /Docs/); }); test("web_search rejects a blank query before resolving a provider", async () => { const tool = createWebSearchTool({ - resolveProvider: async () => { - throw new Error("should not resolve provider for a blank query"); + executeSearch: async () => { + throw new Error("should not execute search for a blank query"); }, }); diff --git a/.pi/agent/extensions/web-search/src/tools/web-search.ts b/.pi/agent/extensions/web-search/src/tools/web-search.ts index 8a2f133..0683394 100644 --- a/.pi/agent/extensions/web-search/src/tools/web-search.ts +++ b/.pi/agent/extensions/web-search/src/tools/web-search.ts @@ -1,10 +1,10 @@ import { Text } from "@mariozechner/pi-tui"; import { formatSearchOutput } from "../format.ts"; -import type { NormalizedSearchResponse, WebProvider } from "../providers/types.ts"; +import type { NormalizedSearchRequest, NormalizedSearchResponse } from "../providers/types.ts"; import { WebSearchParamsSchema, type WebSearchParams } from "../schema.ts"; interface SearchToolDeps { - resolveProvider(providerName?: string): Promise; + executeSearch(request: NormalizedSearchRequest): Promise; } function normalizeSearchQuery(query: string) { @@ -15,7 +15,7 @@ function normalizeSearchQuery(query: string) { return trimmed; } -export function createWebSearchTool({ resolveProvider }: SearchToolDeps) { +export function createWebSearchTool({ executeSearch }: SearchToolDeps) { return { name: "web_search", label: "Web Search", @@ -24,8 +24,7 @@ export function createWebSearchTool({ resolveProvider }: SearchToolDeps) { async execute(_toolCallId: string, params: WebSearchParams) { const query = normalizeSearchQuery(params.query); - const provider = await resolveProvider(params.provider); - const response = await provider.search({ + const response = await executeSearch({ query, limit: params.limit, includeDomains: params.includeDomains, @@ -34,6 +33,7 @@ export function createWebSearchTool({ resolveProvider }: SearchToolDeps) { endPublishedDate: params.endPublishedDate, category: params.category, provider: params.provider, + tavily: params.tavily, }); return { diff --git a/.pi/agent/skills/find-docs/SKILL.md b/.pi/agent/skills/find-docs/SKILL.md new file mode 100644 index 0000000..8e3c2b3 --- /dev/null +++ b/.pi/agent/skills/find-docs/SKILL.md @@ -0,0 +1,154 @@ +--- +name: find-docs +description: >- + Retrieves up-to-date documentation, API references, and code examples for any + developer technology. Use this skill whenever the user asks about a specific + library, framework, SDK, CLI tool, or cloud service -- even for well-known ones + like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. Your + training data may not reflect recent API changes or version updates. + + Always use for: API syntax questions, configuration options, version migration + issues, "how do I" questions mentioning a library name, debugging that involves + library-specific behavior, setup instructions, and CLI tool usage. + + Use even when you think you know the answer -- do not rely on training data + for API details, signatures, or configuration options as they are frequently + outdated. Always verify against current docs. Prefer this over web search for + library documentation and API details. +--- + +# Documentation Lookup + +Retrieve current documentation and code examples for any library using the Context7 CLI. + +Make sure the CLI is up to date before running commands: + +```bash +bun install -g ctx7@latest +``` + +Or run directly without installing: + +```bash +bunx ctx7@latest +``` + +## Workflow + +Two-step process: resolve the library name to an ID, then query docs with that ID. + +```bash +# Step 1: Resolve library ID +ctx7 library + +# Step 2: Query documentation +ctx7 docs +``` + +You MUST call `ctx7 library` first to obtain a valid library ID UNLESS the user explicitly provides a library ID in the format `/org/project` or `/org/project/version`. + +IMPORTANT: Do not run these commands more than 3 times per question. If you cannot find what you need after 3 attempts, use the best result you have. + +## Step 1: Resolve a Library + +Resolves a package/product name to a Context7-compatible library ID and returns matching libraries. + +```bash +ctx7 library react "How to clean up useEffect with async operations" +ctx7 library nextjs "How to set up app router with middleware" +ctx7 library prisma "How to define one-to-many relations with cascade delete" +``` + +Always pass a `query` argument — it is required and directly affects result ranking. Use the user's intent to form the query, which helps disambiguate when multiple libraries share a similar name. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query. + +### Result fields + +Each result includes: + +- **Library ID** — Context7-compatible identifier (format: `/org/project`) +- **Name** — Library or package name +- **Description** — Short summary +- **Code Snippets** — Number of available code examples +- **Source Reputation** — Authority indicator (High, Medium, Low, or Unknown) +- **Benchmark Score** — Quality indicator (100 is the highest score) +- **Versions** — List of versions if available. Use one of those versions if the user provides a version in their query. The format is `/org/project/version`. + +### Selection process + +1. Analyze the query to understand what library/package the user is looking for +2. Select the most relevant match based on: + - Name similarity to the query (exact matches prioritized) + - Description relevance to the query's intent + - Documentation coverage (prioritize libraries with higher Code Snippet counts) + - Source reputation (consider libraries with High or Medium reputation more authoritative) + - Benchmark score (higher is better, 100 is the maximum) +3. If multiple good matches exist, acknowledge this but proceed with the most relevant one +4. If no good matches exist, clearly state this and suggest query refinements +5. For ambiguous queries, request clarification before proceeding with a best-guess match + +### Version-specific IDs + +If the user mentions a specific version, use a version-specific library ID: + +```bash +# General (latest indexed) +ctx7 docs /vercel/next.js "How to set up app router" + +# Version-specific +ctx7 docs /vercel/next.js/v14.3.0-canary.87 "How to set up app router" +``` + +The available versions are listed in the `ctx7 library` output. Use the closest match to what the user specified. + +## Step 2: Query Documentation + +Retrieves up-to-date documentation and code examples for the resolved library. + +```bash +ctx7 docs /facebook/react "How to clean up useEffect with async operations" +ctx7 docs /vercel/next.js "How to add authentication middleware to app router" +ctx7 docs /prisma/prisma "How to define one-to-many relations with cascade delete" +``` + +### Writing good queries + +The query directly affects the quality of results. Be specific and include relevant details. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query. + +| Quality | Example | +|---------|---------| +| Good | `"How to set up authentication with JWT in Express.js"` | +| Good | `"React useEffect cleanup function with async operations"` | +| Bad | `"auth"` | +| Bad | `"hooks"` | + +Use the user's full question as the query when possible, vague one-word queries return generic results. + +The output contains two types of content: **code snippets** (titled, with language-tagged blocks) and **info snippets** (prose explanations with breadcrumb context). + +## Authentication + +Works without authentication. For higher rate limits: + +```bash +# Option A: environment variable +export CONTEXT7_API_KEY=your_key + +# Option B: OAuth login +ctx7 login +``` + +## Error Handling + +If a command fails with a quota error ("Monthly quota reached" or "quota exceeded"): +1. Inform the user their Context7 quota is exhausted +2. Suggest they authenticate for higher limits: `ctx7 login` +3. If they cannot or choose not to authenticate, answer from training knowledge and clearly note it may be outdated + +Do not silently fall back to training data — always tell the user why Context7 was not used. + +## Common Mistakes + +- Library IDs require a `/` prefix — `/facebook/react` not `facebook/react` +- Always run `ctx7 library` first — `ctx7 docs react "hooks"` will fail without a valid ID +- Use descriptive queries, not single words — `"React useEffect cleanup function"` not `"hooks"` +- Do not include sensitive information (API keys, passwords, credentials) in queries diff --git a/.pi/agent/skills/research/deep-research/SKILL.md b/.pi/agent/skills/research/deep-research/SKILL.md new file mode 100644 index 0000000..ab1670b --- /dev/null +++ b/.pi/agent/skills/research/deep-research/SKILL.md @@ -0,0 +1,192 @@ +--- +name: deep-research +description: | + Comprehensive research assistant that synthesizes information from multiple sources with citations. + Use when: conducting in-depth research, gathering sources, writing research summaries, analyzing topics + from multiple perspectives, or when user mentions research, investigation, or needs synthesized analysis + with citations. +license: MIT +metadata: + author: awesome-llm-apps + version: "1.0.0" +--- + +# Deep Research + +You are an expert researcher who provides thorough, well-cited analysis by synthesizing information from multiple perspectives. + +## When to Apply + +Use this skill when: +- Conducting in-depth research on a topic +- Synthesizing information from multiple sources +- Creating research summaries with proper citations +- Analyzing different viewpoints and perspectives +- Identifying key findings and trends +- Evaluating the quality and credibility of sources + +## Research Process + +Follow this systematic approach: + +### 1. **Clarify the Research Question** +- What exactly needs to be researched? +- What level of detail is required? +- Are there specific angles to prioritize? +- What is the purpose of the research? + +### 2. **Identify Key Aspects** +- Break the topic into subtopics or dimensions +- List main questions to answer +- Note important context or background needed + +### 3. **Gather Information** +- Consider multiple perspectives +- Look for primary and secondary sources +- Check publication dates and currency +- Evaluate source credibility + +### 4. **Synthesize Findings** +- Identify patterns and themes +- Note areas of consensus and disagreement +- Highlight key insights +- Connect related information + +### 5. **Document Sources** +- Use numbered citations [1], [2], etc. +- List full sources at the end +- Note if information is uncertain or contested +- Indicate confidence levels where appropriate + +## Output Format + +Structure your research as: + +```markdown +## Executive Summary +[2-3 sentence overview of key findings] + +## Key Findings +- **[Finding 1]**: [Brief explanation] [1] +- **[Finding 2]**: [Brief explanation] [2] +- **[Finding 3]**: [Brief explanation] [3] + +## Detailed Analysis + +### [Subtopic 1] +[In-depth analysis with citations] + +### [Subtopic 2] +[In-depth analysis with citations] + +## Areas of Consensus +[What sources agree on] + +## Areas of Debate +[Where sources disagree or uncertainty exists] + +## Sources +[1] [Full citation with credibility note] +[2] [Full citation with credibility note] + +## Gaps and Further Research +[What's still unknown or needs investigation] +``` + +## Source Evaluation Criteria + +When citing sources, note: + +- **Peer-reviewed journals** - Highest credibility +- **Official reports/statistics** - Authoritative data +- **News from reputable outlets** - Timely, fact-checked +- **Expert commentary** - Qualified opinions +- **General websites** - verify independently + +## Example + +**User Request:** "Research the benefits and risks of intermittent fasting" + +**Response:** + +## Executive Summary + +Intermittent fasting (IF) shows promising benefits for weight loss and metabolic health based on current research, though long-term effects remain under study. Evidence supports its safety for most healthy adults, with certain populations requiring medical supervision [1][2]. + +## Key Findings + +- **Weight Loss**: IF produces similar weight loss to calorie restriction (5-8% body weight over 12 weeks), with potentially better adherence [1] +- **Metabolic Health**: May improve insulin sensitivity by 20-31% and reduce inflammation markers [2] +- **Longevity**: Animal studies show promise; human long-term data is limited [3] +- **Safety**: Not recommended for pregnant women, diabetics without supervision, or those with eating disorder history [4] + +## Detailed Analysis + +### Weight Management +Studies comparing IF to traditional calorie restriction show similar weight loss outcomes. The main advantage appears to be adherence - many people find time-restricted eating easier to maintain than calorie counting [1]. Typical results show 5-8% body weight loss over 12-16 weeks. + +Common IF protocols: +- **16:8** - 16 hours fasting, 8-hour eating window +- **5:2** - Normal eating 5 days, restricted calories 2 days +- **Alternate day** - Alternating between fasting and eating days + +### Metabolic Effects +Research indicates improvements in several biomarkers [2]: +- Fasting insulin levels decreased 20-31% +- Blood pressure reductions (3-8 mmHg systolic) +- Inflammatory marker (CRP) reductions +- Improved lipid profiles in some studies + +However, individual responses vary significantly. Some people show dramatic improvements while others see minimal changes. + +### Potential Mechanisms +Several explanations for observed benefits: +1. **Caloric restriction** - IF naturally reduces total calories +2. **Circadian alignment** - Eating during daytime hours aligns with metabolism +3. **Autophagy** - Cellular cleanup processes activated during fasting +4. **Hormetic stress** - Mild stress that improves adaptation + +### Safety Considerations +While generally safe for healthy adults, certain groups should avoid IF or proceed with medical supervision [4]: +- Pregnant or breastfeeding women +- People with diabetes (risk of hypoglycemia) +- History of eating disorders +- Children and adolescents +- Those taking medications requiring food + +Common side effects during adaptation (1-2 weeks): +- Hunger and irritability +- Headaches +- Fatigue +- Difficulty concentrating + +## Areas of Consensus + +- IF is as effective as traditional diets for weight loss +- Short-term safety is established for healthy adults +- Individual results vary considerably +- Not a magic solution - overall diet quality matters + +## Areas of Debate + +- **Optimal fasting window**: Research shows benefits across different protocols +- **Long-term sustainability**: Most studies are 8-24 weeks; need multi-year data +- **Superiority to other diets**: Unclear if benefits exceed other healthy eating patterns +- **Muscle preservation**: Some concern about muscle loss, but studies show mixed results + +## Sources + +[1] Varady KA, et al. "Clinical application of intermittent fasting for weight loss." *Nature Reviews Endocrinology*, 2022. (Systematic review, high credibility) + +[2] de Cabo R, Mattson MP. "Effects of Intermittent Fasting on Health, Aging, and Disease." *New England Journal of Medicine*, 2019. (Peer-reviewed, authoritative review) + +[3] Longo VD, Panda S. "Fasting, Circadian Rhythms, and Time-Restricted Feeding in Healthy Lifespan." *Cell Metabolism*, 2016. (Mechanistic research, preliminary human data) + +[4] Academy of Nutrition and Dietetics. "Position on Intermittent Fasting." 2022. (Professional organization guidelines) + +## Gaps and Further Research + +- **Long-term studies** (5+ years) needed for sustained effects +- **Different populations** - effects across ages, sexes, ethnicities +- **Optimization** - best fasting windows, meal timing, macronutrient composition +- **Clinical applications** - specific diseases or conditions that benefit most diff --git a/.pi/agent/web-search.json b/.pi/agent/web-search.json deleted file mode 100644 index 8f38269..0000000 --- a/.pi/agent/web-search.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "defaultProvider": "exa-main", - "providers": [ - { - "name": "exa-main", - "type": "exa", - "apiKey": "ea8ea022-339e-4061-967d-5fbdf1b93b40", - "options": { - "defaultSearchLimit": 5, - "defaultFetchTextMaxCharacters": 12000 - } - } - ] -} diff --git a/.pi/reviews/context-manager-task2-fix-report.md b/.pi/reviews/context-manager-task2-fix-report.md new file mode 100644 index 0000000..3d77b28 --- /dev/null +++ b/.pi/reviews/context-manager-task2-fix-report.md @@ -0,0 +1,31 @@ +# Context Manager Task 2 Fix Report + +## Status +Completed. + +## What changed +- Updated `.pi/agent/extensions/context-manager/src/ledger.ts` so `mergeCandidates()` only supersedes the current active item when the incoming candidate is at least as new by `timestamp`. +- Stale same-slot candidates now remain archived instead of reactivating older state when merges arrive out of order. +- Kept `createId(candidate)` intact and added a small internal uniqueness guard so same-slot same-timestamp candidates receive distinct item IDs in practice. +- Added regression coverage in `.pi/agent/extensions/context-manager/src/ledger.test.ts` for: + - out-of-order same-slot merges, and + - same-slot same-timestamp ID collisions. + +## Test results +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/ledger.test.ts` + - Result: 5/5 tests passed. +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 10/10 tests passed. + +## Files changed +- `.pi/agent/extensions/context-manager/src/ledger.ts` +- `.pi/agent/extensions/context-manager/src/ledger.test.ts` +- `.pi/reviews/context-manager-task2-fix-report.md` + +## Self-review findings +- Scope stayed limited to the two reviewed correctness issues and their regression tests. +- Public API and overall Task 2 structure were preserved. +- `createId(candidate)` remains the base ID format from the original Task 2 implementation. + +## Any concerns +- None. diff --git a/.pi/reviews/context-manager-task2-implementer-report.md b/.pi/reviews/context-manager-task2-implementer-report.md new file mode 100644 index 0000000..67835ce --- /dev/null +++ b/.pi/reviews/context-manager-task2-implementer-report.md @@ -0,0 +1,38 @@ +# Context Manager Task 2 Implementer Report + +## Status +Completed. + +## What you implemented +- Added `src/ledger.ts` with the branch-aware memory ledger types and helpers: + - `createEmptyLedger()` + - `mergeCandidates()` + - `getActiveItems()` +- Implemented supersession behavior so a new active item archives the previous active item in the same `kind` + `scope` + `subject` slot. +- Preserved coexistence between branch-scoped and session-scoped memory items. +- Added `src/ledger.test.ts` with the required ledger coverage. + +## What you tested and results +1. Ran the ledger test command before the files existed. + - Result: failed because `src/ledger.test.ts` did not exist. +2. Added `src/ledger.test.ts` and ran the ledger test command again before implementing the module. + - Result: failed with `ERR_MODULE_NOT_FOUND` because `src/ledger.ts` did not exist. +3. Ran the ledger test file after implementing the module. + - Command: `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/ledger.test.ts` + - Result: 3/3 tests passed. +4. Ran the full package test suite. + - Command: `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 8/8 tests passed. + +## Files changed +- `.pi/agent/extensions/context-manager/src/ledger.ts` +- `.pi/agent/extensions/context-manager/src/ledger.test.ts` +- `.pi/reviews/context-manager-task2-implementer-report.md` + +## Self-review findings +- Implementation matches the required API and logic exactly. +- Test style matches the existing `src/config.test.ts` style. +- Scope stayed limited to the ledger work only. + +## Any concerns +- The task text referenced `/home/alex/dotfiles/.pi/agent/extensions/context-manager`, but that path does not exist in this worktree. I ran the equivalent commands in the requested worktree path instead. diff --git a/.pi/reviews/context-manager-task2-minor-fix-report.md b/.pi/reviews/context-manager-task2-minor-fix-report.md new file mode 100644 index 0000000..48969b9 --- /dev/null +++ b/.pi/reviews/context-manager-task2-minor-fix-report.md @@ -0,0 +1,30 @@ +# Context Manager Task 2 Minor Fix Report + +## Status +- Completed. +- Added a deterministic exact-timestamp tie-break for same-slot candidates in `mergeCandidates()`. +- Added regression coverage for reversed input order. + +## Changes +- Updated `.pi/agent/extensions/context-manager/src/ledger.ts` so same-slot candidates with identical timestamps no longer fall back to caller order. +- Added a narrow tie-break helper that compares candidate data (`text`, `sourceType`, `sourceEntryId`, then `confidence`) when timestamps match. +- Kept the public API unchanged and limited the behavior change to exact-timestamp same-slot comparisons. +- Added a regression test in `.pi/agent/extensions/context-manager/src/ledger.test.ts` that verifies the same candidate stays active regardless of input order. + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/ledger.test.ts` ✅ +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` ✅ + +## Files Changed +- `.pi/agent/extensions/context-manager/src/ledger.ts` +- `.pi/agent/extensions/context-manager/src/ledger.test.ts` +- `.pi/reviews/context-manager-task2-minor-fix-report.md` + +## Self-Review Findings +- The fix is narrowly scoped to same-slot exact-timestamp winner selection. +- Existing out-of-order merge coverage and same-timestamp ID-collision coverage still pass. +- The new regression test fails on the pre-fix code path and passes with the tie-break in place. + +## Concerns +- None blocking. +- Same-slot collision IDs remain unique, but sequential `:2` / `:3` suffix assignment is still derived from insertion order across separate incremental merges; I left that behavior unchanged to keep this fix narrowly scoped to the requested active-item determinism issue. diff --git a/.pi/reviews/context-manager-task2-snapshot-fix-report.md b/.pi/reviews/context-manager-task2-snapshot-fix-report.md new file mode 100644 index 0000000..97cb4aa --- /dev/null +++ b/.pi/reviews/context-manager-task2-snapshot-fix-report.md @@ -0,0 +1,35 @@ +# Context Manager Task 2 Snapshot Fix Report + +## Status +Completed. + +## Changes +- Updated `.pi/agent/extensions/context-manager/src/ledger.ts` so affected ledger slots are normalized after merges instead of preserving same-slot insertion order. +- Added deterministic slot ordering by: + - newer `timestamp` first, + - existing exact-timestamp tie-break fields (`text`, `sourceType`, `sourceEntryId`, then `confidence`) for same-timestamp candidates. +- Reassigned same-slot same-timestamp IDs from that canonical order, so `:2` / `:3` suffixes no longer depend on input order. +- Recomputed slot-level `active`, `freshness`, and `supersedesId` from the canonical slot order so forward vs reversed tie merges produce the same stored snapshot and lineage. +- Strengthened `.pi/agent/extensions/context-manager/src/ledger.test.ts` to compare the resulting ledger snapshot, not only the active winner text. +- Updated the existing same-timestamp ID test to assert lineage against the archived tied item rather than insertion position. + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/ledger.test.ts` + - Result: 6/6 tests passed. +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 11/11 tests passed. + +## Files Changed +- `.pi/agent/extensions/context-manager/src/ledger.ts` +- `.pi/agent/extensions/context-manager/src/ledger.test.ts` +- `.pi/reviews/context-manager-task2-snapshot-fix-report.md` + +## Self-Review Findings +- Scope stayed inside the requested ledger merge path and regression tests. +- Public function signatures and exported types remain unchanged. +- The deterministic normalization preserves the earlier out-of-order timestamp protection while also making stored tie snapshots replay-stable. +- Regression coverage now locks the exact stored state for the tied slot, including IDs and `supersedesId` values. + +## Concerns +- None blocking. +- I attempted to invoke the requested code-review subagent workflow, but no `code-reviewer` agent is available in this harness, so this pass relied on direct verification plus self-review. diff --git a/.pi/reviews/context-manager-task3-durable-fix-report.md b/.pi/reviews/context-manager-task3-durable-fix-report.md new file mode 100644 index 0000000..8abfe88 --- /dev/null +++ b/.pi/reviews/context-manager-task3-durable-fix-report.md @@ -0,0 +1,44 @@ +# Context Manager Task 3 Durable Constraint Fix Report + +## Status +- Done +- Fixed the remaining durable-constraint extraction gaps in the context-manager extractor. +- Kept the change narrowly scoped and preserved the earlier source-stable subject behavior plus the narrowed ambiguous-scope behavior. + +## Changes +1. **Allowed explicit durable file-specific constraints to remain session-scoped in `.pi/agent/extensions/context-manager/src/extract.ts`** + - Updated `inferConstraintScope()` so branch-local cues still win first. + - Checked durable session-wide phrasing before falling back to file-path-based branch scoping. + - This keeps ambiguous file-specific lines branch-scoped while allowing explicit durable lines such as `Avoid touching docs/extensions.md across the whole session.` to resolve to `session`. + +2. **Expanded the constraint trigger in `.pi/agent/extensions/context-manager/src/extract.ts`** + - Introduced a shared `CONSTRAINT_RE`. + - Added support for the spelled-out `do not` form alongside the existing `must`, `should`, `don't`, `avoid`, and `prefer` triggers. + +3. **Added regression coverage in `.pi/agent/extensions/context-manager/src/extract.test.ts`** + - Extended the user-constraint scope test with both: + - `Avoid touching docs/extensions.md.` → `branch` + - `Avoid touching docs/extensions.md across the whole session.` → `session` + - Added a dedicated regression test for: + - `Do not add new LLM-facing tools across the whole session.` → extracted `constraint` + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extract.test.ts` + - Result: 5 tests passed, 0 failed +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 16 tests passed, 0 failed + +## Files Changed +- Modified: `.pi/agent/extensions/context-manager/src/extract.ts` +- Modified: `.pi/agent/extensions/context-manager/src/extract.test.ts` +- Created: `.pi/reviews/context-manager-task3-durable-fix-report.md` + +## Self-Review Findings +- The fix is limited to constraint detection and scope inference; no public APIs or ledger merge behavior changed. +- Source-stable `subject` generation remains untouched. +- Ambiguous file-specific instructions without explicit durable phrasing still stay `branch` scoped. +- Explicit durable session-wide wording now correctly overrides file-path presence for constraint scope. + +## Concerns +- Constraint extraction remains heuristic-based by design, so contradictory phrasing that mixes branch-local and session-wide cues will still resolve conservatively to `branch`. +- No additional blocking concerns found after the targeted and full test runs. diff --git a/.pi/reviews/context-manager-task3-fix-report.md b/.pi/reviews/context-manager-task3-fix-report.md new file mode 100644 index 0000000..80b5ab3 --- /dev/null +++ b/.pi/reviews/context-manager-task3-fix-report.md @@ -0,0 +1,41 @@ +# Context Manager Task 3 Fix Report + +## Status +- Done +- Fixed the Task 3 review findings in the extractor and strengthened coverage around extractor/ledger interaction. + +## Changes +1. **Stopped cross-slice subject collisions in extracted memory** + - Updated `.pi/agent/extensions/context-manager/src/extract.ts` so synthetic subjects for decisions, constraints, and active tasks include the source `entryId` (for example `decision-u1-0`). + - This keeps independently extracted facts from different transcript slices in separate ledger slots without changing public APIs or the ledger merge contract. + +2. **Refined user-constraint scoping** + - Added a small `inferConstraintScope()` heuristic. + - User constraints now default to `branch` scope unless they include durable session-wide signals. + - Branch-local cues such as `this branch`, `for now`, `in this file`, or explicit file references stay branch-scoped. + - Durable cues such as `whole session`, `across branches`, `MVP`, or `context window` stay session-scoped. + +3. **Strengthened regression coverage** + - Expanded `.pi/agent/extensions/context-manager/src/extract.test.ts` to verify source metadata, stable per-slice subject generation, extractor/ledger interaction, and the refined scope heuristic. + - Added regression coverage proving independently extracted constraints, decisions, and active tasks all remain active after `mergeCandidates()`. + +## Tests +- Focused extractor tests: + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extract.test.ts` + - Result: 4 tests passed +- Full extension suite: + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 15 tests passed + +## Files Changed +- Modified: `.pi/agent/extensions/context-manager/src/extract.ts` +- Modified: `.pi/agent/extensions/context-manager/src/extract.test.ts` +- Created: `.pi/reviews/context-manager-task3-fix-report.md` + +## Self-Review Findings +- The fix stays focused on Task 3 behavior and keeps all public function signatures unchanged. +- No ledger changes were needed; Task 2 deterministic merge behavior remains covered by the existing ledger tests and the full suite still passes. +- The new interaction test exercises the real extractor-to-ledger path that the review flagged as missing. + +## Concerns +- The extractor still uses heuristic subject generation for independently extracted items, so semantically related decisions restated in different messages will coexist unless a future task adds stronger semantic matching. That is intentional for this focused fix and avoids the reported overwrite bug. diff --git a/.pi/reviews/context-manager-task3-implementer-report.md b/.pi/reviews/context-manager-task3-implementer-report.md new file mode 100644 index 0000000..d7799f8 --- /dev/null +++ b/.pi/reviews/context-manager-task3-implementer-report.md @@ -0,0 +1,40 @@ +# Context Manager Task 3 Implementer Report + +## Status +- Done +- Implemented heuristic transcript-slice extraction in `.pi/agent/extensions/context-manager/src/extract.ts` +- Added focused tests in `.pi/agent/extensions/context-manager/src/extract.test.ts` + +## Changes +- Added `TranscriptSlice` and `extractCandidates()`. +- Reused `MemoryCandidate`, `MemoryScope`, and `MemorySourceType` from `src/ledger.ts`. +- Added heuristics for: + - user goal extraction + - role-aware constraint extraction + - decision extraction + - next-step / task extraction + - file reference extraction +- Kept constraint extraction from duplicating structured `Goal:`, `Decision:`, and `Next:` lines so the output matches the requested candidate set. + +## Tests +- Verified red state first: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/extract.test.ts` + - Result: failed with `ERR_MODULE_NOT_FOUND` because `src/extract.ts` did not exist +- Verified green state after implementation: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/extract.test.ts` +- Ran full extension test suite: + - `cd .pi/agent/extensions/context-manager && npm test` + +## Files Changed +- Created `.pi/agent/extensions/context-manager/src/extract.ts` +- Created `.pi/agent/extensions/context-manager/src/extract.test.ts` +- Created `.pi/reviews/context-manager-task3-implementer-report.md` + +## Self-Review Findings +- The implementation is tightly scoped to extraction only. +- Test coverage matches the task requirements and verifies both user and non-user sources. +- File reference extraction is intentionally heuristic and limited to the requested extensions. + +## Concerns +- The provided implementation snippet would also classify `Decision: ... avoid ...` as a constraint; I prevented that duplicate extraction so the required tests pass. +- No other concerns. diff --git a/.pi/reviews/context-manager-task3-scope-fix-report.md b/.pi/reviews/context-manager-task3-scope-fix-report.md new file mode 100644 index 0000000..b69f313 --- /dev/null +++ b/.pi/reviews/context-manager-task3-scope-fix-report.md @@ -0,0 +1,42 @@ +# Context Manager Task 3 Scope Fix Report + +## Status +- Done +- Tightened the remaining user-constraint scope heuristic so ambiguous constraint lines stay `branch` scoped. +- Preserved the source-stable subject behavior introduced in the prior Task 3 fix. + +## Changes +1. **Narrowed durable session promotion in `.pi/agent/extensions/context-manager/src/extract.ts`** + - Removed the bare-keyword session promotion behavior for terms such as `MVP` and `context window`. + - Kept `session` scope promotion limited to clearer durability signals such as `whole session`, `across branches`, `session-wide`, `project-wide`, and similar explicit phrasing. + +2. **Strengthened local-scope detection** + - Treated `this module` / `in this module` as branch-local cues so module-local rename instructions do not leak across branch switches. + +3. **Added regression coverage in `.pi/agent/extensions/context-manager/src/extract.test.ts`** + - Updated the existing extraction expectation so `We must adapt to the active model context window.` remains `branch` scoped. + - Added explicit regression assertions for the reviewer’s false-positive examples: + - `We should keep the MVP branch experimental.` → `branch` + - `We should rename the context window helper in this module.` → `branch` + - Kept a durable-session control case to verify explicit session phrasing still promotes correctly. + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extract.test.ts` + - Result: 4 tests passed, 0 failed +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: 15 tests passed, 0 failed + +## Files Changed +- Modified: `.pi/agent/extensions/context-manager/src/extract.ts` +- Modified: `.pi/agent/extensions/context-manager/src/extract.test.ts` +- Created: `.pi/reviews/context-manager-task3-scope-fix-report.md` + +## Self-Review Findings +- The fix stays narrowly scoped to `inferConstraintScope()` heuristics and targeted regression coverage. +- Public APIs and exported function signatures are unchanged. +- The previously fixed source-stable `subject` generation remains untouched. +- The updated tests cover both the reviewer’s concrete false positives and an existing ambiguous `context window` example. + +## Concerns +- Constraint scoping remains heuristic-based by design, so the extractor is intentionally conservative: unclear user constraints now stay `branch` scoped unless the text explicitly signals broader durability. +- No additional blocking concerns found after the targeted and full test runs. diff --git a/.pi/reviews/context-manager-task4-boundary-fix-report.md b/.pi/reviews/context-manager-task4-boundary-fix-report.md new file mode 100644 index 0000000..d69c4c9 --- /dev/null +++ b/.pi/reviews/context-manager-task4-boundary-fix-report.md @@ -0,0 +1,13 @@ +# Task 4 Boundary Fix Report + +- **Status:** DONE +- **Changes:** Updated `.pi/agent/extensions/context-manager/src/prune.ts` so `isBulky()` does not count the final empty split segment when content ends with a trailing newline. This keeps exactly-150-line tool output at the non-bulky boundary while preserving the existing byte and over-threshold line behavior. Added a regression test in `.pi/agent/extensions/context-manager/src/prune.test.ts` covering an old tool result with exactly 150 real lines plus a trailing newline. +- **Tests:** + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/packet.test.ts src/prune.test.ts` → 8 passed, 0 failed + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` → 24 passed, 0 failed +- **Files changed:** + - `.pi/agent/extensions/context-manager/src/prune.ts` + - `.pi/agent/extensions/context-manager/src/prune.test.ts` + - `.pi/reviews/context-manager-task4-boundary-fix-report.md` +- **Self-review findings:** The fix is narrowly scoped to line counting inside `isBulky()`, keeps the public API unchanged, and preserves the existing prune-window behavior for bulky vs. non-bulky tool results outside this exact trailing-newline boundary case. +- **Concerns:** None. diff --git a/.pi/reviews/context-manager-task4-fix-report.md b/.pi/reviews/context-manager-task4-fix-report.md new file mode 100644 index 0000000..48d1a60 --- /dev/null +++ b/.pi/reviews/context-manager-task4-fix-report.md @@ -0,0 +1,46 @@ +# Context Manager Task 4 Fix Report + +## Status +- Fixed +- Packet trimming now selects facts under the cap instead of skipping whole sections. +- Targeted packet/prune tests and the full package test suite pass. + +## Changes +- Updated `.pi/agent/extensions/context-manager/src/packet.ts` to: + - score active facts globally with the existing kind weights + - select facts one at a time under `packetTokenCap` + - render the final packet in the existing stable section order + - keep per-section bullet ordering priority-based +- Replaced the weak packet test in `.pi/agent/extensions/context-manager/src/packet.test.ts` with cap-edge assertions that check exact output and prove: + - a tight cap keeps the highest-priority fact from a partially fitting section + - cross-kind weights prefer `activeTask` over `decision` when only one can fit +- Expanded `.pi/agent/extensions/context-manager/src/prune.test.ts` to cover: + - old bulky tool results being pruned + - recent bulky tool results being preserved + - old non-bulky tool results being preserved + - the `recentUserTurns` boundary behavior + +## Tests +- Red check before the packet fix: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/packet.test.ts` + - Result: 2 failing packet tests, showing whole-section trimming and cross-kind selection problems +- Focused verification: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/packet.test.ts src/prune.test.ts` + - Result: 6/6 passing +- Full package verification: + - `cd .pi/agent/extensions/context-manager && npm test` + - Result: 22/22 passing + +## Files Changed +- `.pi/agent/extensions/context-manager/src/packet.ts` +- `.pi/agent/extensions/context-manager/src/packet.test.ts` +- `.pi/agent/extensions/context-manager/src/prune.test.ts` +- `.pi/reviews/context-manager-task4-fix-report.md` + +## Self-Review Findings +- The packet builder now applies weights meaningfully across kinds because selection happens from a global priority list before rendering. +- Rendered output still uses the original section headings and section order. +- `prune.ts` behavior did not need logic changes; the missing boundary coverage is now explicit in tests. + +## Concerns +- None. diff --git a/.pi/reviews/context-manager-task4-implementer-report.md b/.pi/reviews/context-manager-task4-implementer-report.md new file mode 100644 index 0000000..e44b721 --- /dev/null +++ b/.pi/reviews/context-manager-task4-implementer-report.md @@ -0,0 +1,37 @@ +# Context Manager Task 4 Implementer Report + +## Status +- Done +- Implemented packet building in `.pi/agent/extensions/context-manager/src/packet.ts` +- Implemented context pruning in `.pi/agent/extensions/context-manager/src/prune.ts` +- Added focused tests for both modules + +## Changes +- Added `buildContextPacket()` with ordered section emission, priority sorting, and packet token-cap enforcement. +- Added `pruneContextMessages()` with recent-turn preservation and bulky old tool-result pruning. +- Reused existing policy and ledger types from `src/config.ts` and `src/ledger.ts`. +- Kept the implementation scoped to Task 4 only. + +## Tests +- Verified red state first: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/packet.test.ts src/prune.test.ts` + - Result: failed with `ERR_MODULE_NOT_FOUND` because `src/packet.ts` and `src/prune.ts` did not exist yet +- Verified green state after implementation: + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/packet.test.ts src/prune.test.ts` +- Ran full extension test suite: + - `cd .pi/agent/extensions/context-manager && npm test` + +## Files Changed +- Created `.pi/agent/extensions/context-manager/src/packet.ts` +- Created `.pi/agent/extensions/context-manager/src/prune.ts` +- Created `.pi/agent/extensions/context-manager/src/packet.test.ts` +- Created `.pi/agent/extensions/context-manager/src/prune.test.ts` +- Created `.pi/reviews/context-manager-task4-implementer-report.md` + +## Self-Review Findings +- Section ordering and token-cap behavior are covered by the packet test. +- Pruning behavior is covered by the prune test and preserves the latest user turn. +- No unrelated files in the extension package were modified. + +## Concerns +- No known concerns. diff --git a/.pi/reviews/context-manager-task5-fix-report.md b/.pi/reviews/context-manager-task5-fix-report.md new file mode 100644 index 0000000..87b3b12 --- /dev/null +++ b/.pi/reviews/context-manager-task5-fix-report.md @@ -0,0 +1,21 @@ +# Task 5 Fix Report + +- **Status:** DONE +- **Changes:** + - Updated `.pi/agent/extensions/context-manager/src/runtime.ts` to store the last observed token count, recompute `lastZone` whenever policy changes, and return a cloned policy from `getPolicy()`. + - Updated `.pi/agent/extensions/context-manager/src/persist.ts` to validate and sanitize snapshot data before restoring it, skip malformed newer snapshot entries, and preserve backward compatibility by keeping `lastObservedTokens` optional. + - Expanded `.pi/agent/extensions/context-manager/src/runtime.test.ts` with zone recomputation, restore-policy, and no-alias policy/snapshot coverage. + - Expanded `.pi/agent/extensions/context-manager/src/persist.test.ts` with malformed snapshot skipping and deep clone coverage. + - Expanded `.pi/agent/extensions/context-manager/src/summaries.test.ts` with exact summary content and ordering assertions. +- **Tests:** + - `cd .pi/agent/extensions/context-manager && npx tsx --test src/runtime.test.ts src/persist.test.ts src/summaries.test.ts` → 9 passed, 0 failed + - `cd .pi/agent/extensions/context-manager && npm test` → 33 passed, 0 failed +- **Files changed:** + - `.pi/agent/extensions/context-manager/src/runtime.ts` + - `.pi/agent/extensions/context-manager/src/persist.ts` + - `.pi/agent/extensions/context-manager/src/runtime.test.ts` + - `.pi/agent/extensions/context-manager/src/persist.test.ts` + - `.pi/agent/extensions/context-manager/src/summaries.test.ts` + - `.pi/reviews/context-manager-task5-fix-report.md` +- **Self-review findings:** The fix stays within Task 5 scope, keeps the existing runtime API surface intact, and adds regression coverage for each review finding that previously slipped through. +- **Concerns:** None. diff --git a/.pi/reviews/context-manager-task5-implementer-report.md b/.pi/reviews/context-manager-task5-implementer-report.md new file mode 100644 index 0000000..064cbcc --- /dev/null +++ b/.pi/reviews/context-manager-task5-implementer-report.md @@ -0,0 +1,55 @@ +# Context Manager Task 5 Implementer Report + +## Status +- Completed Task 5. +- Required tests pass. +- Full extension test suite passes. + +## What you implemented +- Added `src/summaries.ts` with: + - `buildCompactionSummary()` + - `buildBranchSummary()` + - `buildResumePacket()` +- Added `src/persist.ts` with: + - `SNAPSHOT_ENTRY_TYPE` + - `RuntimeSnapshot` + - `serializeSnapshot()` + - `deserializeLatestSnapshot()` +- Added `src/runtime.ts` with `createContextManagerRuntime()` to: + - ingest transcript slices via `extractCandidates()` + `mergeCandidates()` + - observe token pressure via `zoneForTokens()` + - build packets via `buildContextPacket()` + - expose compaction, branch, and resume summaries + - update mode and context window + - snapshot and restore runtime state +- Added the required tests for summaries, persistence, and runtime. + +## What you tested and results +1. Confirmed the new tests initially failed because the new source files did not exist. + - Command: `cd .pi/agent/extensions/context-manager && npx tsx --test src/summaries.test.ts src/persist.test.ts src/runtime.test.ts` + - Result: failed with `ERR_MODULE_NOT_FOUND` for `summaries.ts`, `persist.ts`, and `runtime.ts` +2. Ran the required Task 5 tests after implementation. + - Command: `cd .pi/agent/extensions/context-manager && npx tsx --test src/summaries.test.ts src/persist.test.ts src/runtime.test.ts` + - Result: 5/5 passing +3. Ran the full extension test suite. + - Command: `cd .pi/agent/extensions/context-manager && npm test` + - Result: 29/29 passing + +## Files changed +- Created `.pi/agent/extensions/context-manager/src/summaries.ts` +- Created `.pi/agent/extensions/context-manager/src/persist.ts` +- Created `.pi/agent/extensions/context-manager/src/runtime.ts` +- Created `.pi/agent/extensions/context-manager/src/summaries.test.ts` +- Created `.pi/agent/extensions/context-manager/src/persist.test.ts` +- Created `.pi/agent/extensions/context-manager/src/runtime.test.ts` +- Created `.pi/reviews/context-manager-task5-implementer-report.md` + +## Self-review findings +- Implementation matches the task-provided API and file list. +- Reused existing `config`, `ledger`, `extract`, and `packet` modules as requested. +- Kept scope to Task 5 only; did not start Task 6. +- Verified the red/green cycle for the new tests. + +## Any concerns +- No functional concerns from this task. +- There are unrelated untracked review files already present in `.pi/reviews/`; they were not included in this task’s changes. diff --git a/.pi/reviews/context-manager-task5-legacy-fix-report.md b/.pi/reviews/context-manager-task5-legacy-fix-report.md new file mode 100644 index 0000000..41a1c34 --- /dev/null +++ b/.pi/reviews/context-manager-task5-legacy-fix-report.md @@ -0,0 +1,29 @@ +# Context Manager Task 5 Legacy Fix Report + +## Status +- DONE + +## Changes +- Updated `.pi/agent/extensions/context-manager/src/runtime.ts` so `syncSnapshotZone()` no longer preserves a stored legacy `lastZone` when `lastObservedTokens` is missing. +- Legacy snapshots without token observations now normalize back to `lastZone: "green"`, which matches the runtime's safe default when pressure cannot be recomputed. +- Added regression coverage in `.pi/agent/extensions/context-manager/src/runtime.test.ts` for both direct restore of a legacy snapshot and the real persisted path of `deserializeLatestSnapshot(...)` followed by `runtime.restore(...)`. +- Left new-schema snapshot behavior unchanged: snapshots that include `lastObservedTokens` still recompute `lastZone` against the receiving runtime policy. + +## Tests +- `cd .pi/agent/extensions/context-manager && npx tsx --test src/runtime.test.ts` → 6 passed, 0 failed +- `cd .pi/agent/extensions/context-manager && npx tsx --test src/runtime.test.ts src/persist.test.ts src/summaries.test.ts` → 11 passed, 0 failed +- `cd .pi/agent/extensions/context-manager && npm test` → 35 passed, 0 failed + +## Files Changed +- `.pi/agent/extensions/context-manager/src/runtime.ts` +- `.pi/agent/extensions/context-manager/src/runtime.test.ts` +- `.pi/reviews/context-manager-task5-legacy-fix-report.md` + +## Self-Review Findings +- The fix is narrowly scoped to restore/policy resync behavior for snapshots missing `lastObservedTokens`. +- The public API stays the same. +- New-schema snapshots keep the existing recompute behavior. +- Legacy snapshots no longer carry a stale red/yellow/compact zone into a runtime that lacks the data needed to validate it. + +## Concerns +- None. diff --git a/.pi/reviews/context-manager-task6-fix-2-report.md b/.pi/reviews/context-manager-task6-fix-2-report.md new file mode 100644 index 0000000..bbd6e3e --- /dev/null +++ b/.pi/reviews/context-manager-task6-fix-2-report.md @@ -0,0 +1,30 @@ +# Context Manager Task 6 Fix 2 Report + +## Status +Done. Fixed the remaining branch rehydration and `/ctx-mode` persistence regressions and verified them with targeted and full test runs. + +## Changes +- Updated `.pi/agent/extensions/context-manager/index.ts` rehydration to restore the latest valid snapshot ledger as the base state and replay only tracked messages that occur after that snapshot entry. +- Kept empty-ledger startup behavior when a branch has no valid snapshot. +- Preserved live in-memory mode changes during `turn_end` so `/ctx-mode aggressive` wins over an older persisted snapshot mode before the next snapshot is appended. +- Strengthened `.pi/agent/extensions/context-manager/src/extension.test.ts` with integration-style regressions for: + - `session_tree` on a snapshot-only branch producing a hidden packet from snapshot-backed ledger state. + - `/ctx-mode aggressive` persisting `mode: "aggressive"` and the corresponding aggressive-zone token classification on the next `turn_end`. + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extension.test.ts` ✅ +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` ✅ + +## Files Changed +- `.pi/agent/extensions/context-manager/index.ts` +- `.pi/agent/extensions/context-manager/src/extension.test.ts` +- `.pi/reviews/context-manager-task6-fix-2-report.md` + +## Self-review Findings +- Snapshot-only branches now retain snapshot ledger contents for packet injection and command inspection instead of dropping to an empty ledger. +- Rehydration no longer replays pre-snapshot transcript entries, which avoids rebuilding stale state on top of a newer snapshot. +- The `/ctx-mode` fix is narrowly scoped to `turn_end`; public commands and extension hook API stay unchanged. + +## Concerns +- None with the fix itself. +- The worktree still contains unrelated untracked historical review files under `.pi/reviews/`; they were left untouched and excluded from the focused commit. diff --git a/.pi/reviews/context-manager-task6-fix-report.md b/.pi/reviews/context-manager-task6-fix-report.md new file mode 100644 index 0000000..5add8dd --- /dev/null +++ b/.pi/reviews/context-manager-task6-fix-report.md @@ -0,0 +1,44 @@ +# Context Manager Task 6 Fix Report + +## Status +- Completed +- Task 6 review findings addressed in the context-manager extension +- Verification commands requested in the task were run and passed + +## Changes +- Added authoritative branch rebuild logic in `.pi/agent/extensions/context-manager/index.ts`. + - Syncs the runtime context window + - Reads the latest valid snapshot from `ctx.sessionManager.getBranch()` + - Restores snapshot metadata with an empty ledger + - Replays `user`, `assistant`, and `toolResult` messages from the active branch into the runtime + - Refreshes the footer status from the rebuilt runtime +- Hooked that rebuild logic into: + - `session_start` + - `turn_end` (before snapshot persistence) + - `session_tree` +- Kept incremental `tool_result` ingestion for in-turn responsiveness, but the branch rebuild is now authoritative at synchronization points. +- Tightened some extension-boundary typing in `index.ts` while making the fix. +- Replaced the shallow registration-only test with integration-style coverage that exercises the registered handlers. + +## Tests +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extension.test.ts` + - Passed: 3/3 +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Passed: 38/38 +- `cd /home/alex/dotfiles/.worktrees/context-manager-extension && git diff --check -- .pi/agent/extensions/context-manager/index.ts .pi/agent/extensions/context-manager/src/extension.test.ts` + - Passed with no diff/whitespace errors + +## Files Changed +- `.pi/agent/extensions/context-manager/index.ts` +- `.pi/agent/extensions/context-manager/src/extension.test.ts` +- `.pi/reviews/context-manager-task6-fix-report.md` + +## Self-review Findings +- `turn_end` now persists snapshots built from the actual current branch, so the ledger includes facts extracted from branch `user` and `assistant` messages instead of depending on `tool_result` events alone. +- `session_tree` now resets runtime state immediately after navigation, so the next `context` packet reflects the destination branch instead of the abandoned one. +- The new tests invoke real registered handlers and verify behavior, not just registration names. +- Public command names and extension surface area remained unchanged. + +## Concerns +- No blocking concerns. +- Note: branch-local mode changes made via `/ctx-mode` are still persisted on the next `turn_end` snapshot. If a branch has no snapshot yet, restoration falls back to default branch state until a snapshot exists. This was left unchanged to keep the Task 6 fix focused. diff --git a/.pi/reviews/context-manager-task6-implementer-report.md b/.pi/reviews/context-manager-task6-implementer-report.md new file mode 100644 index 0000000..cd7dceb --- /dev/null +++ b/.pi/reviews/context-manager-task6-implementer-report.md @@ -0,0 +1,48 @@ +# Context Manager Task 6 Implementer Report + +## Status +Implemented and verified. + +## What you implemented +- Wired the context manager runtime into the extension entrypoint in `.pi/agent/extensions/context-manager/index.ts`. +- Registered the required slash commands through new `src/commands.ts`: + - `/ctx-status` + - `/ctx-memory` + - `/ctx-refresh` + - `/ctx-compact` + - `/ctx-mode` +- Registered the required pi hooks in `index.ts`: + - `session_start` + - `tool_result` + - `turn_end` + - `context` + - `session_before_compact` + - `session_before_tree` +- Added `src/extension.test.ts` to verify the extension registers the expected commands and hooks. + +## What you tested and results +1. Wrote `src/extension.test.ts` first and ran: + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extension.test.ts` + - Result before implementation: **FAIL** as expected because the extension registered no commands/hooks yet. +2. Ran the required targeted test again after implementation: + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extension.test.ts` + - Result: **PASS** +3. Ran the full package test suite: + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` + - Result: **PASS** (`36` tests passed, `0` failed) + +## Files changed +- Modified: `.pi/agent/extensions/context-manager/index.ts` +- Created: `.pi/agent/extensions/context-manager/src/commands.ts` +- Created: `.pi/agent/extensions/context-manager/src/extension.test.ts` +- Created: `.pi/reviews/context-manager-task6-implementer-report.md` + +## Self-review findings +- Implementation matches the required command names and hook names exactly. +- The runtime is restored from persisted snapshots on `session_start` and persisted again on `turn_end`. +- The context hook prunes bulky tool results and injects the built packet as a hidden custom message only when packet text exists. +- Compaction and tree hooks delegate summary generation to the runtime helpers without introducing unrelated scope. + +## Any concerns +- No functional concerns with Task 6 implementation. +- The worktree already contained unrelated untracked review artifacts before this task; they were not part of this implementation. diff --git a/.pi/reviews/context-manager-task6-mode-fix-report.md b/.pi/reviews/context-manager-task6-mode-fix-report.md new file mode 100644 index 0000000..6bb6c38 --- /dev/null +++ b/.pi/reviews/context-manager-task6-mode-fix-report.md @@ -0,0 +1,20 @@ +# Context Manager Task 6 Mode Fix Report + +- **Status:** DONE +- **Changes:** + - Updated `.pi/agent/extensions/context-manager/src/commands.ts` so a successful `/ctx-mode` immediately appends a serialized `context-manager.snapshot` entry after `runtime.setMode(...)`. + - Kept the public command contract stable: the command name is unchanged, the invalid-usage warning string is unchanged, and the existing `turn_end` snapshot persistence path is still active. + - Added an integration-style regression test in `.pi/agent/extensions/context-manager/src/extension.test.ts` that starts from a `balanced` snapshot, runs `/ctx-mode aggressive`, and verifies the branch immediately persists a new snapshot with `mode: "aggressive"` before any `turn_end`. + - Updated the existing `/ctx-mode` + `turn_end` test to assert both snapshots: the immediate command-path snapshot and the later `turn_end` snapshot with refreshed token pressure data. +- **Tests:** + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npx tsx --test src/extension.test.ts` → 5 passed, 0 failed + - `cd /home/alex/dotfiles/.worktrees/context-manager-extension/.pi/agent/extensions/context-manager && npm test` → 40 passed, 0 failed +- **Files changed:** + - `.pi/agent/extensions/context-manager/src/commands.ts` + - `.pi/agent/extensions/context-manager/src/extension.test.ts` + - `.pi/reviews/context-manager-task6-mode-fix-report.md` +- **Self-review findings:** + - The fix is narrowly scoped to the `/ctx-mode` success path and reuses the existing snapshot serializer and append-entry persistence mechanism. + - The regression coverage exercises the actual command path instead of only verifying later `turn_end` behavior. + - `turn_end` persistence remains intact and now appends a fresh follow-up snapshot with updated token usage, which is explicitly covered by the adjusted integration test. +- **Concerns:** None. diff --git a/docs/superpowers/plans/2026-04-09-question-tool.md b/docs/superpowers/plans/2026-04-09-question-tool.md deleted file mode 100644 index 19b2b73..0000000 --- a/docs/superpowers/plans/2026-04-09-question-tool.md +++ /dev/null @@ -1,1114 +0,0 @@ -# Question Tool Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a global pi `question` extension that supports one or many multiple-choice questions and always appends a final “Something else…” free-text option. - -**Architecture:** Keep pi-specific UI/runtime code in `.pi/agent/extensions/question.ts`, and extract a tiny pure helper module beside it for normalization, answer/result construction, and multi-question flow decisions. Cover the helper module with Node’s built-in test runner first, then wire the custom UI and verify the live extension through pi with manual smoke checks. - -**Tech Stack:** Pi extensions API, `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `@sinclair/typebox`, Node `node:test` - ---- - -## File Structure - -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs` - - Pure helpers for normalizing question input, appending the synthetic `Something else…` option, creating structured answers, summarizing results, and multi-question flow decisions. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` - - Node `node:test` coverage for the pure helper module. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question.ts` - - The live pi extension that registers the `question` tool and renders the single-question / multi-question custom UI. -- Modify: `/home/alex/dotfiles/docs/superpowers/plans/2026-04-09-question-tool.md` - - Check boxes as work progresses. - -## Environment Notes - -- `/home/alex/.pi/agent/extensions` already resolves to `/home/alex/dotfiles/.pi/agent/extensions`, so editing files in the repo updates the live global pi extension path. -- No copy/symlink step is needed during implementation. -- Use `/reload` inside pi after file edits to reload the extension. - -### Task 1: Build the pure helper module with tests first - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` - -- [ ] **Step 1: Write the failing helper tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` with this content: - -```js -import test from "node:test"; -import assert from "node:assert/strict"; -import { - SOMETHING_ELSE_LABEL, - SOMETHING_ELSE_VALUE, - createCustomAnswer, - createPredefinedAnswer, - normalizeQuestions, - summarizeAnswers, -} from "./question-core.mjs"; - -test("normalizeQuestions adds default labels and appends the Something else option", () => { - const [question] = normalizeQuestions([ - { - id: "scope", - prompt: "Which scope fits best?", - options: [{ value: "small", label: "Small change" }], - }, - ]); - - assert.equal(question.label, "Q1"); - assert.deepEqual(question.options[0], { value: "small", label: "Small change" }); - assert.deepEqual(question.options.at(-1), { - value: SOMETHING_ELSE_VALUE, - label: SOMETHING_ELSE_LABEL, - }); -}); - -test("normalizeQuestions keeps provided labels and descriptions intact before the synthetic option", () => { - const [question] = normalizeQuestions([ - { - id: "priority", - label: "Priority", - prompt: "Which priority?", - options: [ - { value: "p0", label: "P0", description: "Need this now" }, - { value: "p1", label: "P1" }, - ], - }, - ]); - - assert.equal(question.label, "Priority"); - assert.deepEqual(question.options.slice(0, 2), [ - { value: "p0", label: "P0", description: "Need this now" }, - { value: "p1", label: "P1" }, - ]); - assert.equal(question.options[2].label, SOMETHING_ELSE_LABEL); -}); - -test("answer helpers preserve machine values and summary lines distinguish predefined vs custom answers", () => { - const questions = normalizeQuestions([ - { - id: "scope", - label: "Scope", - prompt: "Which scope fits best?", - options: [{ value: "small", label: "Small change" }], - }, - { - id: "notes", - label: "Notes", - prompt: "Anything else?", - options: [{ value: "none", label: "No extra notes" }], - }, - ]); - - const predefined = createPredefinedAnswer("scope", questions[0].options[0], 1); - const custom = createCustomAnswer("notes", "Needs to work with tmux"); - - assert.deepEqual(predefined, { - id: "scope", - value: "small", - label: "Small change", - wasCustom: false, - index: 1, - }); - assert.deepEqual(custom, { - id: "notes", - value: "Needs to work with tmux", - label: "Needs to work with tmux", - wasCustom: true, - }); - assert.deepEqual(summarizeAnswers(questions, [predefined, custom]), [ - "Scope: user selected: 1. Small change", - "Notes: user wrote: Needs to work with tmux", - ]); -}); -``` - -- [ ] **Step 2: Run the test to verify it fails** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./question-core.mjs`. - -- [ ] **Step 3: Write the minimal helper implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs` with this content: - -```js -export const SOMETHING_ELSE_VALUE = "__something_else__"; -export const SOMETHING_ELSE_LABEL = "Something else…"; - -export function normalizeQuestions(inputQuestions) { - return inputQuestions.map((question, index) => ({ - ...question, - label: question.label?.trim() ? question.label : `Q${index + 1}`, - options: [ - ...question.options, - { - value: SOMETHING_ELSE_VALUE, - label: SOMETHING_ELSE_LABEL, - }, - ], - })); -} - -export function isSomethingElseOption(option) { - return option?.value === SOMETHING_ELSE_VALUE; -} - -export function createPredefinedAnswer(questionId, option, index) { - return { - id: questionId, - value: option.value, - label: option.label, - wasCustom: false, - index, - }; -} - -export function createCustomAnswer(questionId, text) { - return { - id: questionId, - value: text, - label: text, - wasCustom: true, - }; -} - -export function summarizeAnswers(questions, answers) { - const answerById = new Map(answers.map((answer) => [answer.id, answer])); - return questions.flatMap((question) => { - const answer = answerById.get(question.id); - if (!answer) return []; - if (answer.wasCustom) { - return [`${question.label}: user wrote: ${answer.label}`]; - } - return [`${question.label}: user selected: ${answer.index}. ${answer.label}`]; - }); -} -``` - -- [ ] **Step 4: Run the test to verify it passes** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `PASS` for all three tests. - -- [ ] **Step 5: Commit the helper module** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/question-core.mjs \ - .pi/agent/extensions/question-core.test.mjs \ - docs/superpowers/plans/2026-04-09-question-tool.md - -git -C /home/alex/dotfiles commit -m "test: add question helper module" -``` - -### Task 2: Add the single-question extension shell and structured results - -**Files:** -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` - -- [ ] **Step 1: Add failing tests for cancelled and sorted answered results** - -Replace the import block in `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` with this block, then append the new tests below it: - -```js -import test from "node:test"; -import assert from "node:assert/strict"; -import { - SOMETHING_ELSE_LABEL, - SOMETHING_ELSE_VALUE, - createAnsweredResult, - createCancelledResult, - createCustomAnswer, - createPredefinedAnswer, - normalizeQuestions, - summarizeAnswers, -} from "./question-core.mjs"; -``` - -Append these tests: - -```js -test("createCancelledResult returns a structured cancelled payload", () => { - const questions = normalizeQuestions([ - { - id: "scope", - prompt: "Which scope fits best?", - options: [{ value: "small", label: "Small change" }], - }, - ]); - - assert.deepEqual(createCancelledResult(questions), { - questions, - answers: [], - cancelled: true, - }); -}); - -test("createAnsweredResult keeps answers in question order", () => { - const questions = normalizeQuestions([ - { - id: "scope", - label: "Scope", - prompt: "Which scope fits best?", - options: [{ value: "small", label: "Small change" }], - }, - { - id: "notes", - label: "Notes", - prompt: "Anything else?", - options: [{ value: "none", label: "No extra notes" }], - }, - ]); - - const second = createCustomAnswer("notes", "Custom note"); - const first = createPredefinedAnswer("scope", questions[0].options[0], 1); - const result = createAnsweredResult(questions, [second, first]); - - assert.equal(result.cancelled, false); - assert.deepEqual(result.answers.map((answer) => answer.id), ["scope", "notes"]); -}); -``` - -- [ ] **Step 2: Run the tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `FAIL` because `createAnsweredResult` and `createCancelledResult` are not exported yet. - -- [ ] **Step 3: Add the minimal result helpers** - -Append these functions to `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs`: - -```js -export function createCancelledResult(questions = []) { - return { - questions, - answers: [], - cancelled: true, - }; -} - -export function createAnsweredResult(questions, answers) { - const order = new Map(questions.map((question, index) => [question.id, index])); - return { - questions, - answers: [...answers].sort( - (left, right) => (order.get(left.id) ?? Number.POSITIVE_INFINITY) - (order.get(right.id) ?? Number.POSITIVE_INFINITY), - ), - cancelled: false, - }; -} -``` - -- [ ] **Step 4: Run the tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `PASS` for all five tests. - -- [ ] **Step 5: Create the first live extension version with single-question support** - -Create `/home/alex/dotfiles/.pi/agent/extensions/question.ts` with this content: - -```ts -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; -import { - createAnsweredResult, - createCancelledResult, - createCustomAnswer, - createPredefinedAnswer, - isSomethingElseOption, - normalizeQuestions, - summarizeAnswers, -} from "./question-core.mjs"; - -interface QuestionOption { - value: string; - label: string; - description?: string; -} - -interface Question { - id: string; - label: string; - prompt: string; - options: QuestionOption[]; -} - -interface Answer { - id: string; - value: string; - label: string; - wasCustom: boolean; - index?: number; -} - -interface QuestionResult { - questions: Question[]; - answers: Answer[]; - cancelled: boolean; -} - -const OptionSchema = Type.Object({ - value: Type.String({ description: "Machine-friendly value returned to the model" }), - label: Type.String({ description: "Human-friendly label shown in the UI" }), - description: Type.Optional(Type.String({ description: "Optional help text shown under the label" })), -}); - -const QuestionSchema = Type.Object({ - id: Type.String({ description: "Stable identifier for the answer" }), - label: Type.Optional(Type.String({ description: "Short label for summaries and tabs" })), - prompt: Type.String({ description: "Full question text shown to the user" }), - options: Type.Array(OptionSchema, { description: "Predefined options for the user to choose from" }), -}); - -const QuestionParams = Type.Object({ - questions: Type.Array(QuestionSchema, { description: "One or more questions to ask the user" }), -}); - -function errorResult(message: string, questions: Question[] = []) { - return { - content: [{ type: "text" as const, text: message }], - details: createCancelledResult(questions) as QuestionResult, - }; -} - -async function runSingleQuestion(ctx: any, question: Question): Promise { - const result = await ctx.ui.custom((tui, theme, _kb, done) => { - let optionIndex = 0; - let inputMode = false; - let cachedLines: string[] | undefined; - - const editorTheme: EditorTheme = { - borderColor: (text) => theme.fg("accent", text), - selectList: { - selectedPrefix: (text) => theme.fg("accent", text), - selectedText: (text) => theme.fg("accent", text), - description: (text) => theme.fg("muted", text), - scrollInfo: (text) => theme.fg("dim", text), - noMatch: (text) => theme.fg("warning", text), - }, - }; - - const editor = new Editor(tui, editorTheme); - editor.onSubmit = (value) => { - const trimmed = value.trim(); - if (trimmed.length === 0) { - cachedLines = undefined; - tui.requestRender(); - return; - } - done(createCustomAnswer(question.id, trimmed) as Answer); - }; - - function refresh() { - cachedLines = undefined; - tui.requestRender(); - } - - function handleInput(data: string) { - if (inputMode) { - if (matchesKey(data, Key.escape)) { - inputMode = false; - editor.setText(""); - refresh(); - return; - } - editor.handleInput(data); - refresh(); - return; - } - - if (matchesKey(data, Key.up)) { - optionIndex = Math.max(0, optionIndex - 1); - refresh(); - return; - } - - if (matchesKey(data, Key.down)) { - optionIndex = Math.min(question.options.length - 1, optionIndex + 1); - refresh(); - return; - } - - if (matchesKey(data, Key.enter)) { - const selected = question.options[optionIndex]!; - if (isSomethingElseOption(selected)) { - inputMode = true; - editor.setText(""); - refresh(); - return; - } - done(createPredefinedAnswer(question.id, selected, optionIndex + 1) as Answer); - return; - } - - if (matchesKey(data, Key.escape)) { - done(null); - } - } - - function render(width: number): string[] { - if (cachedLines) return cachedLines; - - const lines: string[] = []; - const add = (line: string) => lines.push(truncateToWidth(line, width)); - - add(theme.fg("accent", "─".repeat(width))); - add(theme.fg("text", ` ${question.prompt}`)); - lines.push(""); - - for (let index = 0; index < question.options.length; index += 1) { - const option = question.options[index]!; - const isSelected = index === optionIndex; - const prefix = isSelected ? theme.fg("accent", "> ") : " "; - const label = `${index + 1}. ${option.label}`; - add(prefix + (isSelected ? theme.fg("accent", label) : theme.fg("text", label))); - if (option.description) { - add(` ${theme.fg("muted", option.description)}`); - } - } - - if (inputMode) { - lines.push(""); - add(theme.fg("muted", " Your answer:")); - for (const line of editor.render(width - 2)) { - add(` ${line}`); - } - } - - lines.push(""); - add(theme.fg("dim", inputMode ? " Enter to submit • Esc to go back" : " ↑↓ navigate • Enter select • Esc cancel")); - add(theme.fg("accent", "─".repeat(width))); - - cachedLines = lines; - return lines; - } - - return { - render, - invalidate: () => { - cachedLines = undefined; - }, - handleInput, - }; - }); - - if (!result) { - return createCancelledResult([question]) as QuestionResult; - } - - return createAnsweredResult([question], [result]) as QuestionResult; -} - -export default function question(pi: ExtensionAPI) { - pi.registerTool({ - name: "question", - label: "Question", - description: - "Ask the user one or more multiple-choice questions. Every question automatically gets a final Something else… option for free-text answers.", - parameters: QuestionParams, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - if (!ctx.hasUI) { - return errorResult("Error: UI not available (running in non-interactive mode)"); - } - - if (params.questions.length === 0) { - return errorResult("Error: No questions provided"); - } - - const questions = normalizeQuestions(params.questions) as Question[]; - if (questions.length !== 1) { - return errorResult("Error: Multi-question flow is not implemented yet", questions); - } - - const result = await runSingleQuestion(ctx, questions[0]!); - if (result.cancelled) { - return { - content: [{ type: "text", text: "User cancelled the question flow" }], - details: result, - }; - } - - return { - content: [{ type: "text", text: summarizeAnswers(result.questions, result.answers).join("\n") }], - details: result, - }; - }, - - renderCall(args, theme) { - const questions = Array.isArray(args.questions) ? args.questions : []; - const labels = questions - .map((question: { label?: string; id?: string }) => question.label || question.id) - .filter(Boolean) - .join(", "); - - let text = theme.fg("toolTitle", theme.bold("question ")); - text += theme.fg("muted", `${questions.length} question${questions.length === 1 ? "" : "s"}`); - if (labels) { - text += theme.fg("dim", ` (${labels})`); - } - return new Text(text, 0, 0); - }, - - renderResult(result, _options, theme) { - const details = result.details as QuestionResult | undefined; - if (!details) { - const first = result.content[0]; - return new Text(first?.type === "text" ? first.text : "", 0, 0); - } - - if (details.cancelled) { - return new Text(theme.fg("warning", "Cancelled"), 0, 0); - } - - const lines = summarizeAnswers(details.questions, details.answers).map( - (line) => `${theme.fg("success", "✓ ")}${line}`, - ); - return new Text(lines.join("\n"), 0, 0); - }, - }); -} -``` - -- [ ] **Step 6: Reload pi and manually verify the single-question path** - -Run: - -```bash -cd /home/alex/dotfiles -pi -``` - -Inside pi, run this sequence: - -```text -/reload -Use the question tool to ask me one question with these options: -- stable: Stable release -- nightly: Nightly release -``` - -Expected manual checks: - -- the tool row appears as `question 1 question` -- the UI shows three options, with `Something else…` appended last -- selecting `Something else…` opens inline text entry -- pressing `Esc` in text entry returns to the option list -- selecting `stable` returns a successful tool result with a single summary line for the chosen answer - -- [ ] **Step 7: Commit the single-question extension** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/question-core.mjs \ - .pi/agent/extensions/question-core.test.mjs \ - .pi/agent/extensions/question.ts \ - docs/superpowers/plans/2026-04-09-question-tool.md - -git -C /home/alex/dotfiles commit -m "feat: add single-question pi question tool" -``` - -### Task 3: Extend the tool to multi-question review and submit flow - -**Files:** -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs` -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/question.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` - -- [ ] **Step 1: Add failing tests for multi-question flow helpers** - -Replace the import block in `/home/alex/dotfiles/.pi/agent/extensions/question-core.test.mjs` with this block, then append the new tests below it: - -```js -import test from "node:test"; -import assert from "node:assert/strict"; -import { - SOMETHING_ELSE_LABEL, - SOMETHING_ELSE_VALUE, - allQuestionsAnswered, - createAnsweredResult, - createCancelledResult, - createCustomAnswer, - createPredefinedAnswer, - nextTabAfterAnswer, - normalizeQuestions, - summarizeAnswers, -} from "./question-core.mjs"; -``` - -Append these tests: - -```js -test("allQuestionsAnswered only returns true when every question has an answer", () => { - const questions = normalizeQuestions([ - { - id: "scope", - prompt: "Scope?", - options: [{ value: "small", label: "Small" }], - }, - { - id: "priority", - prompt: "Priority?", - options: [{ value: "p1", label: "P1" }], - }, - ]); - - const answers = new Map([ - ["scope", createPredefinedAnswer("scope", questions[0].options[0], 1)], - ]); - - assert.equal(allQuestionsAnswered(questions, answers), false); - answers.set("priority", createCustomAnswer("priority", "Ship this week")); - assert.equal(allQuestionsAnswered(questions, answers), true); -}); - -test("nextTabAfterAnswer advances through questions and then to the submit tab", () => { - assert.equal(nextTabAfterAnswer(0, 3), 1); - assert.equal(nextTabAfterAnswer(1, 3), 2); - assert.equal(nextTabAfterAnswer(2, 3), 3); -}); -``` - -- [ ] **Step 2: Run the tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `FAIL` because `allQuestionsAnswered` and `nextTabAfterAnswer` are not exported yet. - -- [ ] **Step 3: Add the multi-question helper functions** - -Append these functions to `/home/alex/dotfiles/.pi/agent/extensions/question-core.mjs`: - -```js -export function allQuestionsAnswered(questions, answers) { - return questions.every((question) => answers.has(question.id)); -} - -export function nextTabAfterAnswer(currentTab, questionCount) { - return currentTab < questionCount - 1 ? currentTab + 1 : questionCount; -} -``` - -- [ ] **Step 4: Run the tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `PASS` for all seven tests. - -- [ ] **Step 5: Replace the single-question-only execution path with a unified question flow** - -In `/home/alex/dotfiles/.pi/agent/extensions/question.ts`, replace the helper import block with this block, then replace the `runSingleQuestion()` function with the `runQuestionFlow()` function below: - -```ts -import { - allQuestionsAnswered, - createAnsweredResult, - createCancelledResult, - createCustomAnswer, - createPredefinedAnswer, - isSomethingElseOption, - nextTabAfterAnswer, - normalizeQuestions, - summarizeAnswers, -} from "./question-core.mjs"; -``` - -Then replace `runSingleQuestion()` with: - -```ts -async function runQuestionFlow(ctx: any, questions: Question[]): Promise { - return ctx.ui.custom((tui, theme, _kb, done) => { - const isMulti = questions.length > 1; - let currentTab = 0; - let optionIndex = 0; - let inputMode = false; - let cachedLines: string[] | undefined; - const answers = new Map(); - - const editorTheme: EditorTheme = { - borderColor: (text) => theme.fg("accent", text), - selectList: { - selectedPrefix: (text) => theme.fg("accent", text), - selectedText: (text) => theme.fg("accent", text), - description: (text) => theme.fg("muted", text), - scrollInfo: (text) => theme.fg("dim", text), - noMatch: (text) => theme.fg("warning", text), - }, - }; - - const editor = new Editor(tui, editorTheme); - - function refresh() { - cachedLines = undefined; - tui.requestRender(); - } - - function currentQuestion(): Question | undefined { - return questions[currentTab]; - } - - function currentOptions(): QuestionOption[] { - return currentQuestion()?.options ?? []; - } - - function finish(cancelled: boolean) { - if (cancelled) { - done(createCancelledResult(questions) as QuestionResult); - return; - } - done(createAnsweredResult(questions, Array.from(answers.values())) as QuestionResult); - } - - editor.onSubmit = (value) => { - const question = currentQuestion(); - const trimmed = value.trim(); - if (!question || trimmed.length === 0) { - refresh(); - return; - } - - answers.set(question.id, createCustomAnswer(question.id, trimmed) as Answer); - inputMode = false; - editor.setText(""); - - if (!isMulti) { - finish(false); - return; - } - - currentTab = nextTabAfterAnswer(currentTab, questions.length); - optionIndex = 0; - refresh(); - }; - - function handleInput(data: string) { - if (inputMode) { - if (matchesKey(data, Key.escape)) { - inputMode = false; - editor.setText(""); - refresh(); - return; - } - editor.handleInput(data); - refresh(); - return; - } - - if (isMulti) { - if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) { - currentTab = (currentTab + 1) % (questions.length + 1); - optionIndex = 0; - refresh(); - return; - } - if (matchesKey(data, Key.shift("tab")) || matchesKey(data, Key.left)) { - currentTab = (currentTab - 1 + questions.length + 1) % (questions.length + 1); - optionIndex = 0; - refresh(); - return; - } - if (currentTab === questions.length) { - if (matchesKey(data, Key.enter) && allQuestionsAnswered(questions, answers)) { - finish(false); - return; - } - if (matchesKey(data, Key.escape)) { - finish(true); - return; - } - return; - } - } - - const question = currentQuestion(); - const options = currentOptions(); - if (!question || options.length === 0) { - return; - } - - if (matchesKey(data, Key.up)) { - optionIndex = Math.max(0, optionIndex - 1); - refresh(); - return; - } - - if (matchesKey(data, Key.down)) { - optionIndex = Math.min(options.length - 1, optionIndex + 1); - refresh(); - return; - } - - if (matchesKey(data, Key.enter)) { - const selected = options[optionIndex]!; - if (isSomethingElseOption(selected)) { - inputMode = true; - editor.setText(""); - refresh(); - return; - } - - answers.set(question.id, createPredefinedAnswer(question.id, selected, optionIndex + 1) as Answer); - if (!isMulti) { - finish(false); - return; - } - - currentTab = nextTabAfterAnswer(currentTab, questions.length); - optionIndex = 0; - refresh(); - return; - } - - if (matchesKey(data, Key.escape)) { - finish(true); - } - } - - function render(width: number): string[] { - if (cachedLines) return cachedLines; - - const lines: string[] = []; - const add = (line: string) => lines.push(truncateToWidth(line, width)); - const question = currentQuestion(); - const options = currentOptions(); - - add(theme.fg("accent", "─".repeat(width))); - - if (isMulti) { - const tabs: string[] = []; - for (let index = 0; index < questions.length; index += 1) { - const tabQuestion = questions[index]!; - const active = index === currentTab; - const answered = answers.has(tabQuestion.id); - const box = answered ? "■" : "□"; - const text = ` ${box} ${tabQuestion.label} `; - tabs.push(active ? theme.bg("selectedBg", theme.fg("text", text)) : theme.fg(answered ? "success" : "muted", text)); - } - - const submitText = " ✓ Submit "; - const submitActive = currentTab === questions.length; - const submitReady = allQuestionsAnswered(questions, answers); - tabs.push( - submitActive - ? theme.bg("selectedBg", theme.fg("text", submitText)) - : theme.fg(submitReady ? "success" : "dim", submitText), - ); - - add(` ${tabs.join(" ")}`); - lines.push(""); - } - - if (inputMode && question) { - add(theme.fg("text", ` ${question.prompt}`)); - lines.push(""); - for (let index = 0; index < options.length; index += 1) { - const option = options[index]!; - const prefix = index === optionIndex ? theme.fg("accent", "> ") : " "; - add(prefix + theme.fg(index === optionIndex ? "accent" : "text", `${index + 1}. ${option.label}`)); - if (option.description) { - add(` ${theme.fg("muted", option.description)}`); - } - } - lines.push(""); - add(theme.fg("muted", " Your answer:")); - for (const line of editor.render(width - 2)) { - add(` ${line}`); - } - } else if (isMulti && currentTab === questions.length) { - add(theme.fg("accent", theme.bold(" Ready to submit"))); - lines.push(""); - for (const reviewQuestion of questions) { - const answer = answers.get(reviewQuestion.id); - if (!answer) continue; - const label = answer.wasCustom ? `(wrote) ${answer.label}` : `${answer.index}. ${answer.label}`; - add(`${theme.fg("muted", ` ${reviewQuestion.label}: `)}${theme.fg("text", label)}`); - } - lines.push(""); - if (allQuestionsAnswered(questions, answers)) { - add(theme.fg("success", " Press Enter to submit")); - } else { - add(theme.fg("warning", " All questions must be answered before submit")); - } - } else if (question) { - add(theme.fg("text", ` ${question.prompt}`)); - lines.push(""); - for (let index = 0; index < options.length; index += 1) { - const option = options[index]!; - const prefix = index === optionIndex ? theme.fg("accent", "> ") : " "; - add(prefix + theme.fg(index === optionIndex ? "accent" : "text", `${index + 1}. ${option.label}`)); - if (option.description) { - add(` ${theme.fg("muted", option.description)}`); - } - } - } - - lines.push(""); - if (inputMode) { - add(theme.fg("dim", " Enter to submit • Esc to go back")); - } else if (isMulti) { - add(theme.fg("dim", " Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel")); - } else { - add(theme.fg("dim", " ↑↓ navigate • Enter select • Esc cancel")); - } - add(theme.fg("accent", "─".repeat(width))); - - cachedLines = lines; - return lines; - } - - return { - render, - invalidate: () => { - cachedLines = undefined; - }, - handleInput, - }; - }); -} -``` - -Then, in the `execute()` method, replace this block: - -```ts -const questions = normalizeQuestions(params.questions) as Question[]; -if (questions.length !== 1) { - return errorResult("Error: Multi-question flow is not implemented yet", questions); -} - -const result = await runSingleQuestion(ctx, questions[0]!); -``` - -with this block: - -```ts -const questions = normalizeQuestions(params.questions) as Question[]; -const result = await runQuestionFlow(ctx, questions); -``` - -- [ ] **Step 6: Reload pi and manually verify the multi-question flow** - -Run: - -```bash -cd /home/alex/dotfiles -pi -``` - -Inside pi, run this sequence: - -```text -/reload -Use the question tool to ask me two questions. -Question 1: Scope -- small: Small change -- medium: Medium change -Question 2: Priority -- p1: P1 -- p2: P2 -``` - -Expected manual checks: - -- the tool UI shows tabs for both questions plus a final `Submit` tab -- each question appends `Something else…` as the last option -- tab / left-right navigation moves between questions and the submit tab -- pressing `Enter` on the submit tab only works after both questions have answers -- choosing `Something else…` for either question opens inline text entry and returns to the multi-question flow after submission -- the final result renders one success line per answer in question order - -Run one more manual cancel check in the same session: - -```text -Use the question tool to ask me one question with two choices. -``` - -Expected manual checks: - -- pressing `Esc` from the picker cancels the tool -- the rendered result shows `Cancelled` - -- [ ] **Step 7: Commit the finished multi-question tool** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/question-core.mjs \ - .pi/agent/extensions/question-core.test.mjs \ - .pi/agent/extensions/question.ts \ - docs/superpowers/plans/2026-04-09-question-tool.md - -git -C /home/alex/dotfiles commit -m "feat: add multi-question pi question tool" -``` - -## Final Verification Checklist - -Run these before claiming the work is complete: - -```bash -cd /home/alex/dotfiles -node --test .pi/agent/extensions/question-core.test.mjs -``` - -Expected: `PASS` for all tests. - -Then run pi from `/home/alex/dotfiles`, use `/reload`, and manually verify: - -1. one predefined answer -2. one custom `Something else…` answer -3. multi-question predefined flow -4. multi-question mixed predefined/custom flow -5. submit-tab blocking until all answers exist -6. cancel from picker -7. `Esc` from text entry returns to options - -If any of those checks fail, do not mark the work complete. diff --git a/docs/superpowers/plans/2026-04-09-web-search-tools.md b/docs/superpowers/plans/2026-04-09-web-search-tools.md deleted file mode 100644 index 4a98b64..0000000 --- a/docs/superpowers/plans/2026-04-09-web-search-tools.md +++ /dev/null @@ -1,1498 +0,0 @@ -# Web Search Tools Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add modular `web_search` and `web_fetch` pi tools backed by Exa, with runtime config loaded from `~/.pi/agent/web-search.json` and an internal provider adapter layer for future providers. - -**Architecture:** Build a package-style extension in `.pi/agent/extensions/web-search/` so it can carry `exa-js` and local tests cleanly. Keep config parsing, Exa request mapping, and output formatting in pure modules covered by `node:test` via `tsx`, then layer pi-specific tool registration and renderers on top. - -**Tech Stack:** Pi extensions API, `exa-js`, `@sinclair/typebox`, `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, Node `node:test`, `tsx`, TypeScript - ---- - -## File Structure - -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/package.json` - - Local extension package manifest, npm scripts, and dependencies. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/index.ts` - - Extension entrypoint that registers `web_search` and `web_fetch`. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/schema.ts` - - TypeBox schemas for config and tool parameter validation. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.ts` - - Loads and validates `~/.pi/agent/web-search.json`, normalizes the provider list, and resolves the default provider. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.test.ts` - - Tests config parsing, normalization, and helpful error messages. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/types.ts` - - Provider-agnostic request/response interfaces used by both tools. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.ts` - - Exa-backed implementation of the provider interface. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.test.ts` - - Tests generic request → Exa call mapping and per-URL fetch failure handling. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.ts` - - Shared compact text formatting and truncation helpers. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.test.ts` - - Tests metadata-only search formatting, text truncation, and batch failure formatting. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.ts` - - `web_search` tool factory, execute path, and tool renderers. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.test.ts` - - Tests non-empty query validation and happy-path execution with a fake provider. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.ts` - - `web_fetch` tool factory, input normalization, execute path, and tool renderers. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts` - - Tests URL normalization, default text mode, and invalid URL rejection. -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/extension.test.ts` - - Smoke test that the extension entrypoint registers both tool names. -- Modify: `/home/alex/dotfiles/docs/superpowers/plans/2026-04-09-web-search-tools.md` - - Check boxes as work progresses. - -## Environment Notes - -- `/home/alex/.pi/agent/extensions` resolves to `/home/alex/dotfiles/.pi/agent/extensions`, so editing files in the repo updates the live global pi extension path. -- The config file for this feature lives outside the repo at `~/.pi/agent/web-search.json`. -- Use `/reload` inside pi after file edits to reload the extension. -- Run package-local tests from `/home/alex/dotfiles/.pi/agent/extensions/web-search`. - -### Task 1: Scaffold the extension package and build the config loader first - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/package.json` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.test.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/schema.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.test.ts` - -- [x] **Step 1: Create the extension package scaffold and install dependencies** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/package.json` with this content: - -```json -{ - "name": "pi-web-search-extension", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "test": "tsx --test src/*.test.ts src/**/*.test.ts" - }, - "pi": { - "extensions": [ - "./index.ts" - ] - }, - "dependencies": { - "@sinclair/typebox": "^0.34.49", - "exa-js": "^2.11.0" - }, - "devDependencies": { - "@mariozechner/pi-coding-agent": "^0.66.1", - "@mariozechner/pi-tui": "^0.66.1", - "@types/node": "^25.5.2", - "tsx": "^4.21.0", - "typescript": "^6.0.2" - } -} -``` - -Then run: - -```bash -mkdir -p /home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers -mkdir -p /home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npm install -``` - -Expected: `node_modules/` is created and `npm install` exits with code `0`. - -- [x] **Step 2: Write the failing config tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import { mkdtemp, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { loadWebSearchConfig, WebSearchConfigError } from "./config.ts"; - -async function writeTempConfig(contents: unknown) { - const dir = await mkdtemp(join(tmpdir(), "pi-web-search-config-")); - const file = join(dir, "web-search.json"); - const body = typeof contents === "string" ? contents : JSON.stringify(contents, null, 2); - await writeFile(file, body, "utf8"); - return file; -} - -test("loadWebSearchConfig returns a normalized default provider and provider lookup", async () => { - const file = await writeTempConfig({ - defaultProvider: "exa-main", - providers: [ - { - name: "exa-main", - type: "exa", - apiKey: "exa-test-key", - options: { - defaultSearchLimit: 7, - defaultFetchTextMaxCharacters: 9000 - } - } - ] - }); - - const config = await loadWebSearchConfig(file); - - assert.equal(config.defaultProviderName, "exa-main"); - assert.equal(config.defaultProvider.name, "exa-main"); - assert.equal(config.providersByName.get("exa-main")?.apiKey, "exa-test-key"); - assert.equal(config.providers[0]?.options?.defaultSearchLimit, 7); -}); - -test("loadWebSearchConfig rejects a missing default provider target", async () => { - const file = await writeTempConfig({ - defaultProvider: "missing", - providers: [ - { - name: "exa-main", - type: "exa", - apiKey: "exa-test-key" - } - ] - }); - - await assert.rejects( - () => loadWebSearchConfig(file), - (error) => - error instanceof WebSearchConfigError && - /defaultProvider \"missing\"/.test(error.message), - ); -}); - -test("loadWebSearchConfig rejects a missing file with a helpful example message", async () => { - const file = join(tmpdir(), "pi-web-search-does-not-exist.json"); - - await assert.rejects( - () => loadWebSearchConfig(file), - (error) => - error instanceof WebSearchConfigError && - error.message.includes(file) && - error.message.includes('"defaultProvider"') && - error.message.includes('"providers"'), - ); -}); -``` - -- [x] **Step 3: Run the config tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/config.test.ts -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./config.ts`. - -- [x] **Step 4: Write the minimal schema and config loader implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/schema.ts` with this content: - -```ts -import { Type, type Static } from "@sinclair/typebox"; - -export const ProviderOptionsSchema = Type.Object({ - defaultSearchLimit: Type.Optional(Type.Integer({ minimum: 1 })), - defaultFetchTextMaxCharacters: Type.Optional(Type.Integer({ minimum: 1 })), - defaultFetchHighlightsMaxCharacters: Type.Optional(Type.Integer({ minimum: 1 })), -}); - -export const ExaProviderConfigSchema = Type.Object({ - name: Type.String({ minLength: 1 }), - type: Type.Literal("exa"), - apiKey: Type.String({ minLength: 1 }), - options: Type.Optional(ProviderOptionsSchema), -}); - -export const WebSearchConfigSchema = Type.Object({ - defaultProvider: Type.String({ minLength: 1 }), - providers: Type.Array(ExaProviderConfigSchema, { minItems: 1 }), -}); - -export const WebSearchParamsSchema = Type.Object({ - query: Type.String({ minLength: 1, description: "Search query" }), - limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 25 })), - includeDomains: Type.Optional(Type.Array(Type.String())), - excludeDomains: Type.Optional(Type.Array(Type.String())), - startPublishedDate: Type.Optional(Type.String()), - endPublishedDate: Type.Optional(Type.String()), - category: Type.Optional(Type.String()), - provider: Type.Optional(Type.String()), -}); - -export const WebFetchParamsSchema = Type.Object({ - urls: Type.Array(Type.String(), { minItems: 1 }), - text: Type.Optional(Type.Boolean()), - highlights: Type.Optional(Type.Boolean()), - summary: Type.Optional(Type.Boolean()), - textMaxCharacters: Type.Optional(Type.Integer({ minimum: 1 })), - provider: Type.Optional(Type.String()), -}); - -export type ProviderOptions = Static; -export type ExaProviderConfig = Static; -export type WebSearchConfig = Static; -export type WebSearchParams = Static; -export type WebFetchParams = Static; -``` - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.ts` with this content: - -```ts -import { readFile } from "node:fs/promises"; -import { homedir } from "node:os"; -import { join } from "node:path"; -import { Value } from "@sinclair/typebox/value"; -import { WebSearchConfigSchema, type ExaProviderConfig, type WebSearchConfig } from "./schema.ts"; - -export interface ResolvedWebSearchConfig { - path: string; - defaultProviderName: string; - defaultProvider: ExaProviderConfig; - providers: ExaProviderConfig[]; - providersByName: Map; -} - -export class WebSearchConfigError extends Error { - constructor(message: string) { - super(message); - this.name = "WebSearchConfigError"; - } -} - -export function getDefaultWebSearchConfigPath() { - return join(homedir(), ".pi", "agent", "web-search.json"); -} - -function exampleConfigSnippet() { - return JSON.stringify( - { - defaultProvider: "exa-main", - providers: [ - { - name: "exa-main", - type: "exa", - apiKey: "exa_..." - } - ] - }, - null, - 2, - ); -} - -export function normalizeWebSearchConfig(config: WebSearchConfig, path: string): ResolvedWebSearchConfig { - const providersByName = new Map(); - - for (const provider of config.providers) { - if (!provider.apiKey.trim()) { - throw new WebSearchConfigError(`Provider \"${provider.name}\" in ${path} is missing a literal apiKey.`); - } - if (providersByName.has(provider.name)) { - throw new WebSearchConfigError(`Duplicate provider name \"${provider.name}\" in ${path}.`); - } - providersByName.set(provider.name, provider); - } - - const defaultProvider = providersByName.get(config.defaultProvider); - if (!defaultProvider) { - throw new WebSearchConfigError( - `defaultProvider \"${config.defaultProvider}\" does not match any configured provider in ${path}.`, - ); - } - - return { - path, - defaultProviderName: config.defaultProvider, - defaultProvider, - providers: [...providersByName.values()], - providersByName, - }; -} - -export async function loadWebSearchConfig(path = getDefaultWebSearchConfigPath()) { - let raw: string; - try { - raw = await readFile(path, "utf8"); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") { - throw new WebSearchConfigError( - `Missing web-search config at ${path}.\nCreate ${path} with contents like:\n${exampleConfigSnippet()}`, - ); - } - throw error; - } - - let parsed: unknown; - try { - parsed = JSON.parse(raw); - } catch (error) { - throw new WebSearchConfigError(`Invalid JSON in ${path}: ${(error as Error).message}`); - } - - if (!Value.Check(WebSearchConfigSchema, parsed)) { - const [firstError] = [...Value.Errors(WebSearchConfigSchema, parsed)]; - throw new WebSearchConfigError( - `Invalid web-search config at ${path}: ${firstError?.path ?? "/"} ${firstError?.message ?? "failed validation"}`, - ); - } - - return normalizeWebSearchConfig(parsed as WebSearchConfig, path); -} -``` - -- [x] **Step 5: Run the config tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/config.test.ts -``` - -Expected: `PASS` for all three tests. - -- [x] **Step 6: Commit the config layer** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/web-search/package.json \ - .pi/agent/extensions/web-search/src/schema.ts \ - .pi/agent/extensions/web-search/src/config.ts \ - .pi/agent/extensions/web-search/src/config.test.ts \ - docs/superpowers/plans/2026-04-09-web-search-tools.md - -git -C /home/alex/dotfiles commit -m "test: add web search config loader" -``` - -### Task 2: Add the provider-agnostic types and the Exa adapter - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.test.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/types.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.test.ts` - -- [x] **Step 1: Write the failing Exa adapter tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import { createExaProvider } from "./exa.ts"; - -const baseConfig = { - name: "exa-main", - type: "exa" as const, - apiKey: "exa-test-key", - options: { - defaultSearchLimit: 7, - defaultFetchTextMaxCharacters: 9000, - defaultFetchHighlightsMaxCharacters: 1200, - }, -}; - -test("createExaProvider maps generic search requests to Exa search with contents disabled", async () => { - let captured: { query: string; options: Record } | undefined; - - const provider = createExaProvider(baseConfig, () => ({ - async search(query, options) { - captured = { query, options }; - return { - requestId: "req-search-1", - searchTime: 123, - results: [ - { - id: "doc-1", - title: "Exa Docs", - url: "https://exa.ai/docs", - publishedDate: "2026-04-09", - author: "Exa", - score: 0.98, - }, - ], - }; - }, - async getContents() { - throw new Error("not used"); - }, - })); - - const result = await provider.search({ - query: "exa docs", - includeDomains: ["exa.ai"], - }); - - assert.deepEqual(captured, { - query: "exa docs", - options: { - contents: false, - numResults: 7, - includeDomains: ["exa.ai"], - excludeDomains: undefined, - startPublishedDate: undefined, - endPublishedDate: undefined, - category: undefined, - }, - }); - assert.equal(result.providerName, "exa-main"); - assert.equal(result.results[0]?.url, "https://exa.ai/docs"); -}); - -test("createExaProvider fetch defaults to text and preserves per-url failures", async () => { - const calls: Array<{ urls: string[]; options: Record }> = []; - - const provider = createExaProvider(baseConfig, () => ({ - async search() { - throw new Error("not used"); - }, - async getContents(urls, options) { - const requestUrls = Array.isArray(urls) ? urls : [urls]; - calls.push({ urls: requestUrls, options }); - - if (requestUrls[0] === "https://bad.example") { - throw new Error("429 rate limited"); - } - - return { - requestId: `req-${calls.length}`, - results: [ - { - url: requestUrls[0], - title: "Fetched page", - text: "Fetched body", - }, - ], - }; - }, - })); - - const result = await provider.fetch({ - urls: ["https://good.example", "https://bad.example"], - }); - - assert.equal((calls[0]?.options.text as { maxCharacters: number }).maxCharacters, 9000); - assert.deepEqual(result.results, [ - { - url: "https://good.example", - title: "Fetched page", - text: "Fetched body", - highlights: undefined, - summary: undefined, - }, - { - url: "https://bad.example", - title: null, - error: "429 rate limited", - }, - ]); -}); -``` - -- [x] **Step 2: Run the Exa adapter tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/providers/exa.test.ts -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./exa.ts`. - -- [x] **Step 3: Write the provider interface and Exa adapter implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/types.ts` with this content: - -```ts -export interface NormalizedSearchRequest { - query: string; - limit?: number; - includeDomains?: string[]; - excludeDomains?: string[]; - startPublishedDate?: string; - endPublishedDate?: string; - category?: string; - provider?: string; -} - -export interface NormalizedSearchResult { - id?: string; - title: string | null; - url: string; - publishedDate?: string; - author?: string; - score?: number; -} - -export interface NormalizedSearchResponse { - providerName: string; - requestId?: string; - searchTime?: number; - results: NormalizedSearchResult[]; -} - -export interface NormalizedFetchRequest { - urls: string[]; - text?: boolean; - highlights?: boolean; - summary?: boolean; - textMaxCharacters?: number; - provider?: string; -} - -export interface NormalizedFetchResult { - url: string; - title: string | null; - text?: string; - highlights?: string[]; - summary?: string; - error?: string; -} - -export interface NormalizedFetchResponse { - providerName: string; - requestIds?: string[]; - results: NormalizedFetchResult[]; -} - -export interface WebProvider { - name: string; - type: string; - search(request: NormalizedSearchRequest): Promise; - fetch(request: NormalizedFetchRequest): Promise; -} -``` - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.ts` with this content: - -```ts -import Exa from "exa-js"; -import type { ExaProviderConfig } from "../schema.ts"; -import type { - NormalizedFetchRequest, - NormalizedFetchResponse, - NormalizedSearchRequest, - NormalizedSearchResponse, - WebProvider, -} from "./types.ts"; - -export interface ExaClientLike { - search(query: string, options?: Record): Promise; - getContents(urls: string[] | string, options?: Record): Promise; -} - -export type ExaClientFactory = (apiKey: string) => ExaClientLike; - -export function buildSearchOptions(config: ExaProviderConfig, request: NormalizedSearchRequest) { - return { - contents: false, - numResults: request.limit ?? config.options?.defaultSearchLimit ?? 5, - includeDomains: request.includeDomains, - excludeDomains: request.excludeDomains, - startPublishedDate: request.startPublishedDate, - endPublishedDate: request.endPublishedDate, - category: request.category, - }; -} - -export function buildFetchOptions(config: ExaProviderConfig, request: NormalizedFetchRequest) { - const text = request.text ?? (!request.highlights && !request.summary); - - return { - ...(text - ? { - text: { - maxCharacters: request.textMaxCharacters ?? config.options?.defaultFetchTextMaxCharacters ?? 12000, - }, - } - : {}), - ...(request.highlights - ? { - highlights: { - maxCharacters: config.options?.defaultFetchHighlightsMaxCharacters ?? 1000, - }, - } - : {}), - ...(request.summary ? { summary: true } : {}), - }; -} - -export function createExaProvider( - config: ExaProviderConfig, - createClient: ExaClientFactory = (apiKey) => new Exa(apiKey) as unknown as ExaClientLike, -): WebProvider { - const client = createClient(config.apiKey); - - return { - name: config.name, - type: config.type, - - async search(request: NormalizedSearchRequest): Promise { - const response = await client.search(request.query, buildSearchOptions(config, request)); - return { - providerName: config.name, - requestId: response.requestId, - searchTime: response.searchTime, - results: (response.results ?? []).map((item: any) => ({ - id: item.id, - title: item.title ?? null, - url: item.url, - publishedDate: item.publishedDate, - author: item.author, - score: item.score, - })), - }; - }, - - async fetch(request: NormalizedFetchRequest): Promise { - const requestIds: string[] = []; - const options = buildFetchOptions(config, request); - - const results = await Promise.all( - request.urls.map(async (url) => { - try { - const response = await client.getContents([url], options); - if (response.requestId) { - requestIds.push(response.requestId); - } - - const item = response.results?.[0]; - if (!item) { - return { - url, - title: null, - error: "No content returned", - }; - } - - return { - url: item.url ?? url, - title: item.title ?? null, - text: typeof item.text === "string" ? item.text : undefined, - highlights: Array.isArray(item.highlights) ? item.highlights : undefined, - summary: typeof item.summary === "string" ? item.summary : undefined, - }; - } catch (error) { - return { - url, - title: null, - error: (error as Error).message, - }; - } - }), - ); - - return { - providerName: config.name, - requestIds, - results, - }; - }, - }; -} -``` - -- [x] **Step 4: Run the Exa adapter tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/providers/exa.test.ts -``` - -Expected: `PASS` for both tests. - -- [x] **Step 5: Commit the provider layer** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/web-search/src/providers/types.ts \ - .pi/agent/extensions/web-search/src/providers/exa.ts \ - .pi/agent/extensions/web-search/src/providers/exa.test.ts \ - docs/superpowers/plans/2026-04-09-web-search-tools.md - -git -C /home/alex/dotfiles commit -m "test: add exa web provider adapter" -``` - -### Task 3: Add the shared formatter and truncation helpers - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.test.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.test.ts` - -- [x] **Step 1: Write the failing formatter tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import { formatFetchOutput, formatSearchOutput, truncateText } from "./format.ts"; - -test("formatSearchOutput renders a compact metadata-only list", () => { - const output = formatSearchOutput({ - providerName: "exa-main", - results: [ - { - title: "Exa Docs", - url: "https://exa.ai/docs", - publishedDate: "2026-04-09", - author: "Exa", - score: 0.98, - }, - ], - }); - - assert.match(output, /Found 1 web result via exa-main:/); - assert.match(output, /Exa Docs/); - assert.match(output, /https:\/\/exa.ai\/docs/); -}); - -test("truncateText shortens long fetch bodies with an ellipsis", () => { - assert.equal(truncateText("abcdef", 4), "abc…"); - assert.equal(truncateText("abc", 10), "abc"); -}); - -test("formatFetchOutput includes both successful and failed URLs", () => { - const output = formatFetchOutput( - { - providerName: "exa-main", - results: [ - { - url: "https://good.example", - title: "Good", - text: "This is a very long body that should be truncated in the final output.", - }, - { - url: "https://bad.example", - title: null, - error: "429 rate limited", - }, - ], - }, - { maxCharactersPerResult: 20 }, - ); - - assert.match(output, /Status: ok/); - assert.match(output, /Status: failed/); - assert.match(output, /429 rate limited/); - assert.match(output, /This is a very long…/); -}); -``` - -- [x] **Step 2: Run the formatter tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/format.test.ts -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./format.ts`. - -- [x] **Step 3: Write the minimal formatter implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.ts` with this content: - -```ts -import type { NormalizedFetchResponse, NormalizedSearchResponse } from "./providers/types.ts"; - -export function truncateText(text: string, maxCharacters = 4000) { - if (text.length <= maxCharacters) { - return text; - } - return `${text.slice(0, Math.max(0, maxCharacters - 1))}…`; -} - -export function formatSearchOutput(response: NormalizedSearchResponse) { - if (response.results.length === 0) { - return `No web results via ${response.providerName}.`; - } - - const lines = [ - `Found ${response.results.length} web result${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`, - ]; - - for (const [index, result] of response.results.entries()) { - lines.push(`${index + 1}. ${result.title ?? "(untitled)"}`); - lines.push(` URL: ${result.url}`); - - const meta = [result.publishedDate, result.author].filter(Boolean); - if (meta.length > 0) { - lines.push(` Meta: ${meta.join(" • ")}`); - } - - if (typeof result.score === "number") { - lines.push(` Score: ${result.score}`); - } - } - - return lines.join("\n"); -} - -export interface FetchFormatOptions { - maxCharactersPerResult?: number; -} - -export function formatFetchOutput(response: NormalizedFetchResponse, options: FetchFormatOptions = {}) { - const maxCharactersPerResult = options.maxCharactersPerResult ?? 4000; - const lines = [ - `Fetched ${response.results.length} URL${response.results.length === 1 ? "" : "s"} via ${response.providerName}:`, - ]; - - for (const result of response.results) { - lines.push(""); - lines.push(`URL: ${result.url}`); - - if (result.error) { - lines.push("Status: failed"); - lines.push(`Error: ${result.error}`); - continue; - } - - lines.push("Status: ok"); - if (result.title) { - lines.push(`Title: ${result.title}`); - } - if (result.summary) { - lines.push(`Summary: ${result.summary}`); - } - if (result.highlights?.length) { - lines.push("Highlights:"); - for (const highlight of result.highlights) { - lines.push(`- ${highlight}`); - } - } - if (result.text) { - lines.push("Text:"); - lines.push(truncateText(result.text, maxCharactersPerResult)); - } - } - - return lines.join("\n"); -} -``` - -- [x] **Step 4: Run the formatter tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/format.test.ts -``` - -Expected: `PASS` for all three tests. - -- [x] **Step 5: Commit the formatter layer** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/web-search/src/format.ts \ - .pi/agent/extensions/web-search/src/format.test.ts \ - docs/superpowers/plans/2026-04-09-web-search-tools.md - -git -C /home/alex/dotfiles commit -m "test: add web tool output formatting" -``` - -### Task 4: Add the `web_search` tool and verify live search - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.test.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.test.ts` - -- [x] **Step 1: Write the failing `web_search` tool tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import { createWebSearchTool } from "./web-search.ts"; - -test("web_search executes metadata-only search through the resolved provider", async () => { - let resolvedProviderName: string | undefined; - - const tool = createWebSearchTool({ - resolveProvider: async (providerName) => { - resolvedProviderName = providerName; - return { - name: "exa-main", - type: "exa", - async search(request) { - assert.equal(request.query, "exa docs"); - return { - providerName: "exa-main", - results: [ - { - title: "Exa Docs", - url: "https://exa.ai/docs", - score: 0.98, - }, - ], - }; - }, - async fetch() { - throw new Error("not used"); - }, - }; - }, - }); - - const result = await tool.execute("tool-1", { query: "exa docs" }, undefined, undefined, undefined); - - assert.equal(resolvedProviderName, undefined); - assert.match((result.content[0] as { text: string }).text, /Exa Docs/); - assert.equal((result.details as { results: Array<{ url: string }> }).results[0]?.url, "https://exa.ai/docs"); -}); - -test("web_search rejects a blank query before resolving a provider", async () => { - const tool = createWebSearchTool({ - resolveProvider: async () => { - throw new Error("should not resolve provider for a blank query"); - }, - }); - - await assert.rejects( - () => tool.execute("tool-1", { query: " " }, undefined, undefined, undefined), - /non-empty query/, - ); -}); -``` - -- [x] **Step 2: Run the `web_search` tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/tools/web-search.test.ts -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./web-search.ts`. - -- [x] **Step 3: Write the minimal `web_search` tool implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.ts` with this content: - -```ts -import { Text } from "@mariozechner/pi-tui"; -import { formatSearchOutput } from "../format.ts"; -import type { NormalizedSearchResponse, WebProvider } from "../providers/types.ts"; -import { WebSearchParamsSchema, type WebSearchParams } from "../schema.ts"; - -interface SearchToolDeps { - resolveProvider(providerName?: string): Promise; -} - -function normalizeSearchQuery(query: string) { - const trimmed = query.trim(); - if (!trimmed) { - throw new Error("web_search requires a non-empty query."); - } - return trimmed; -} - -export function createWebSearchTool({ resolveProvider }: SearchToolDeps) { - return { - name: "web_search", - label: "Web Search", - description: "Search the web through the configured provider. Returns result metadata by default.", - parameters: WebSearchParamsSchema, - - async execute(_toolCallId: string, params: WebSearchParams) { - const provider = await resolveProvider(params.provider); - const response = await provider.search({ - query: normalizeSearchQuery(params.query), - limit: params.limit, - includeDomains: params.includeDomains, - excludeDomains: params.excludeDomains, - startPublishedDate: params.startPublishedDate, - endPublishedDate: params.endPublishedDate, - category: params.category, - provider: params.provider, - }); - - return { - content: [{ type: "text" as const, text: formatSearchOutput(response) }], - details: response, - }; - }, - - renderCall(args: Partial, theme: any) { - let text = theme.fg("toolTitle", theme.bold("web_search ")); - text += theme.fg("muted", args.query ?? ""); - return new Text(text, 0, 0); - }, - - renderResult(result: { details?: NormalizedSearchResponse }, _options: unknown, theme: any) { - const details = result.details; - if (!details) { - return new Text("", 0, 0); - } - - const lines = [ - `${theme.fg("success", "✓ ")}${details.results.length} result${details.results.length === 1 ? "" : "s"} via ${details.providerName}`, - ]; - - for (const [index, item] of details.results.slice(0, 5).entries()) { - lines.push(` ${theme.fg("muted", `${index + 1}.`)} ${item.title ?? "(untitled)"} ${theme.fg("dim", item.url)}`); - } - - return new Text(lines.join("\n"), 0, 0); - }, - }; -} -``` - -- [x] **Step 4: Run the `web_search` tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/tools/web-search.test.ts -``` - -Expected: `PASS` for both tests. - -- [x] **Step 5: Create a real Exa config file for the live smoke test** - -Run: - -```bash -read -rsp "Exa API key: " EXA_KEY && printf '\n' -mkdir -p /home/alex/.pi/agent -cat > /home/alex/.pi/agent/web-search.json < { - const config = await loadWebSearchConfig(); - const selectedName = providerName ?? config.defaultProviderName; - const providerConfig = config.providersByName.get(selectedName); - - if (!providerConfig) { - throw new Error( - `Unknown web-search provider \"${selectedName}\". Configured providers: ${[...config.providersByName.keys()].join(", ")}`, - ); - } - - switch (providerConfig.type) { - case "exa": - return createExaProvider(providerConfig); - default: - throw new Error(`Unsupported web-search provider type: ${(providerConfig as { type: string }).type}`); - } -} - -export default function webSearch(pi: ExtensionAPI) { - pi.registerTool(createWebSearchTool({ resolveProvider })); -} -``` - -Then run: - -```bash -cd /home/alex/dotfiles -pi -``` - -Inside pi, run this sequence: - -```text -/reload -Search the web for Exa docs using the web_search tool. -``` - -Expected manual checks: - -- the agent has a `web_search` tool available -- the tool call row shows `web_search` and the query text -- the result contains metadata-only output, not fetched page text -- at least one result includes `https://exa.ai/docs` - -- [x] **Step 7: Commit the `web_search` tool** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/web-search/index.ts \ - .pi/agent/extensions/web-search/src/tools/web-search.ts \ - .pi/agent/extensions/web-search/src/tools/web-search.test.ts \ - docs/superpowers/plans/2026-04-09-web-search-tools.md - -git -C /home/alex/dotfiles commit -m "feat: add web_search tool" -``` - -### Task 5: Add `web_fetch`, register both tools, and verify batch fetch behavior - -**Files:** -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.ts` -- Create: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/extension.test.ts` -- Modify: `/home/alex/dotfiles/.pi/agent/extensions/web-search/index.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts` -- Test: `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/extension.test.ts` - -- [x] **Step 1: Write the failing `web_fetch` and extension registration tests** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import { createWebFetchTool } from "./web-fetch.ts"; - -test("web_fetch prepareArguments folds a single url into urls", () => { - const tool = createWebFetchTool({ - resolveProvider: async () => { - throw new Error("not used"); - }, - }); - - assert.deepEqual(tool.prepareArguments?.({ url: "https://exa.ai/docs" }), { - url: "https://exa.ai/docs", - urls: ["https://exa.ai/docs"], - }); -}); - -test("web_fetch defaults to text and returns formatted fetch results", async () => { - let capturedRequest: Record | undefined; - - const tool = createWebFetchTool({ - resolveProvider: async () => ({ - name: "exa-main", - type: "exa", - async search() { - throw new Error("not used"); - }, - async fetch(request) { - capturedRequest = request as unknown as Record; - return { - providerName: "exa-main", - results: [ - { - url: "https://exa.ai/docs", - title: "Docs", - text: "Body", - }, - ], - }; - }, - }), - }); - - const result = await tool.execute("tool-1", { urls: ["https://exa.ai/docs"] }, undefined, undefined, undefined); - - assert.equal(capturedRequest?.text, true); - assert.match((result.content[0] as { text: string }).text, /Body/); - assert.equal((result.details as { results: Array<{ title: string }> }).results[0]?.title, "Docs"); -}); - -test("web_fetch rejects malformed URLs", async () => { - const tool = createWebFetchTool({ - resolveProvider: async () => { - throw new Error("should not resolve provider for invalid URLs"); - }, - }); - - await assert.rejects( - () => tool.execute("tool-1", { urls: ["not-a-url"] }, undefined, undefined, undefined), - /Invalid URL/, - ); -}); -``` - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/extension.test.ts` with this content: - -```ts -import test from "node:test"; -import assert from "node:assert/strict"; -import webSearchExtension from "../index.ts"; - -test("the extension entrypoint registers both web_search and web_fetch", () => { - const registeredTools: string[] = []; - - webSearchExtension({ - registerTool(tool: { name: string }) { - registeredTools.push(tool.name); - }, - } as any); - - assert.deepEqual(registeredTools, ["web_search", "web_fetch"]); -}); -``` - -- [x] **Step 2: Run the new tests to verify they fail** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/tools/web-fetch.test.ts src/extension.test.ts -``` - -Expected: `FAIL` with an `ERR_MODULE_NOT_FOUND` error for `./web-fetch.ts`. - -- [x] **Step 3: Write the minimal `web_fetch` tool implementation** - -Create `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.ts` with this content: - -```ts -import { Text } from "@mariozechner/pi-tui"; -import { formatFetchOutput } from "../format.ts"; -import type { NormalizedFetchResponse, WebProvider } from "../providers/types.ts"; -import { WebFetchParamsSchema, type WebFetchParams } from "../schema.ts"; - -interface FetchToolDeps { - resolveProvider(providerName?: string): Promise; -} - -function normalizeUrl(value: string) { - try { - return new URL(value).toString(); - } catch { - throw new Error(`Invalid URL: ${value}`); - } -} - -function normalizeFetchParams(params: WebFetchParams & { url?: string }) { - const urls = (Array.isArray(params.urls) ? params.urls : []).map(normalizeUrl); - if (urls.length === 0) { - throw new Error("web_fetch requires at least one URL."); - } - - return { - urls, - text: params.text ?? (!params.highlights && !params.summary), - highlights: params.highlights ?? false, - summary: params.summary ?? false, - textMaxCharacters: params.textMaxCharacters, - provider: params.provider, - }; -} - -export function createWebFetchTool({ resolveProvider }: FetchToolDeps) { - return { - name: "web_fetch", - label: "Web Fetch", - description: "Fetch page contents through the configured provider. Returns text by default.", - parameters: WebFetchParamsSchema, - - prepareArguments(args: unknown) { - if (!args || typeof args !== "object") { - return args; - } - - const input = args as { url?: unknown; urls?: unknown }; - if (typeof input.url === "string" && !Array.isArray(input.urls)) { - return { - ...input, - urls: [input.url], - }; - } - - return args; - }, - - async execute(_toolCallId: string, params: WebFetchParams) { - const normalized = normalizeFetchParams(params as WebFetchParams & { url?: string }); - const provider = await resolveProvider(normalized.provider); - const response = await provider.fetch(normalized); - - return { - content: [{ type: "text" as const, text: formatFetchOutput(response) }], - details: response, - }; - }, - - renderCall(args: Partial & { url?: string }, theme: any) { - const urls = Array.isArray(args.urls) ? args.urls : typeof args.url === "string" ? [args.url] : []; - let text = theme.fg("toolTitle", theme.bold("web_fetch ")); - text += theme.fg("muted", `${urls.length} url${urls.length === 1 ? "" : "s"}`); - return new Text(text, 0, 0); - }, - - renderResult(result: { details?: NormalizedFetchResponse }, _options: unknown, theme: any) { - const details = result.details; - if (!details) { - return new Text("", 0, 0); - } - - const failed = details.results.filter((item) => item.error).length; - const succeeded = details.results.length - failed; - return new Text( - `${theme.fg("success", "✓ ")}${succeeded} ok${failed ? ` • ${theme.fg("warning", `${failed} failed`)}` : ""}`, - 0, - 0, - ); - }, - }; -} -``` - -- [x] **Step 4: Replace the temporary entrypoint with the final entrypoint that registers both tools** - -Replace `/home/alex/dotfiles/.pi/agent/extensions/web-search/index.ts` with this content: - -```ts -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { loadWebSearchConfig } from "./src/config.ts"; -import { createExaProvider } from "./src/providers/exa.ts"; -import type { WebProvider } from "./src/providers/types.ts"; -import { createWebFetchTool } from "./src/tools/web-fetch.ts"; -import { createWebSearchTool } from "./src/tools/web-search.ts"; - -async function resolveProvider(providerName?: string): Promise { - const config = await loadWebSearchConfig(); - const selectedName = providerName ?? config.defaultProviderName; - const providerConfig = config.providersByName.get(selectedName); - - if (!providerConfig) { - throw new Error( - `Unknown web-search provider \"${selectedName}\". Configured providers: ${[...config.providersByName.keys()].join(", ")}`, - ); - } - - switch (providerConfig.type) { - case "exa": - return createExaProvider(providerConfig); - default: - throw new Error(`Unsupported web-search provider type: ${(providerConfig as { type: string }).type}`); - } -} - -export default function webSearch(pi: ExtensionAPI) { - pi.registerTool(createWebSearchTool({ resolveProvider })); - pi.registerTool(createWebFetchTool({ resolveProvider })); -} -``` - -- [x] **Step 5: Run the `web_fetch` and entrypoint smoke tests to verify they pass** - -Run: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npx tsx --test src/tools/web-fetch.test.ts src/extension.test.ts -``` - -Expected: `PASS` for all four tests. - -- [x] **Step 6: Reload pi and manually verify search + single fetch + batch fetch** - -Run: - -```bash -cd /home/alex/dotfiles -pi -``` - -Inside pi, run this sequence: - -```text -/reload -Search the web for Exa docs using the web_search tool. -Then fetch https://exa.ai/docs using the web_fetch tool. -Then fetch https://exa.ai/docs and https://exa.ai using the web_fetch tool. -``` - -Expected manual checks: - -- both `web_search` and `web_fetch` are available -- `web_search` still returns metadata only by default -- `web_fetch` returns page text by default -- `web_fetch` accepts multiple URLs in one call -- batch fetch output is clearly separated per URL -- if one URL fails, the result still includes the successful URL output and a failure section for the bad URL - -- [x] **Step 7: Commit the completed web tools extension** - -Run: - -```bash -git -C /home/alex/dotfiles add \ - .pi/agent/extensions/web-search/index.ts \ - .pi/agent/extensions/web-search/src/tools/web-fetch.ts \ - .pi/agent/extensions/web-search/src/tools/web-fetch.test.ts \ - .pi/agent/extensions/web-search/src/extension.test.ts \ - docs/superpowers/plans/2026-04-09-web-search-tools.md - -git -C /home/alex/dotfiles commit -m "feat: add web search tools extension" -``` - -## Final Verification Checklist - -Run these before claiming the work is complete: - -```bash -cd /home/alex/dotfiles/.pi/agent/extensions/web-search -npm test -``` - -Expected: `PASS` for all config, provider, formatter, tool, and entrypoint tests. - -Then run pi from `/home/alex/dotfiles`, use `/reload`, and manually verify: - -1. `web_search` returns metadata-only results -2. `web_search` handles a configured Exa provider successfully -3. `web_fetch` returns text by default -4. `web_fetch` accepts a single URL -5. `web_fetch` accepts multiple URLs -6. invalid URLs fail before the provider is called -7. missing or invalid `~/.pi/agent/web-search.json` produces a helpful error -8. both tools remain available after `/reload` - -If any of those checks fail, do not mark the work complete. diff --git a/docs/superpowers/specs/2026-04-09-question-tool-design.md b/docs/superpowers/specs/2026-04-09-question-tool-design.md deleted file mode 100644 index ee0f97e..0000000 --- a/docs/superpowers/specs/2026-04-09-question-tool-design.md +++ /dev/null @@ -1,279 +0,0 @@ -# Question Tool Design - -**Date:** 2026-04-09 -**Project:** `/home/alex/dotfiles` -**Target file:** `.pi/agent/extensions/question.ts` - -## Goal - -Add a tracked pi extension that gives the agent a single `question` tool for asking either one question or multiple questions in interactive mode, while always preserving a final user escape hatch: **"Something else…"** opens inline free-text entry when none of the listed options fit. - -## Context - -- Pi supports custom tools through TypeScript extensions placed in auto-discovered extension directories. -- This dotfiles repo already tracks pi configuration under `.pi/agent/`. -- The working extension directory `.pi/agent/extensions/` is currently empty. -- Pi’s upstream examples already include: - - a single-question `question.ts` example - - a multi-question `questionnaire.ts` example -- The requested tool should combine those use cases into one obvious agent-facing tool. - -## User-Approved Requirements - -1. The tool must be tracked in this repo at: - - `/home/alex/dotfiles/.pi/agent/extensions/question.ts` -2. The tool name should be: - - `question` -3. The tool must support both: - - a single question - - multiple questions in one interaction -4. Every question is multiple-choice, but the UI must always append a final choice: - - **"Something else…"** -5. Choosing **"Something else…"** must allow direct user text entry. -6. Question options should support machine-friendly values and user-facing labels: - - `{ value, label, description? }` -7. This should be a unified tool, not separate `question` and `questionnaire` tools. - -## Recommended Approach - -Implement a single extension modeled after pi’s upstream `question.ts` and `questionnaire.ts` examples: - -- one registered tool: `question` -- one parameter shape: `questions: Question[]` -- one UI that adapts to question count: - - single-question picker for `questions.length === 1` - - multi-question review flow for `questions.length > 1` - -This keeps the agent-facing API simple while still supporting richer user clarification flows. - -## Tool Contract - -The extension will register a tool with this conceptual input shape: - -```ts -{ - questions: Array<{ - id: string; - label?: string; - prompt: string; - options: Array<{ - value: string; - label: string; - description?: string; - }>; - }>; -} -``` - -### Field intent - -- `id`: stable identifier for the answer -- `label`: short summary label for tabs/review UI; defaults to `Q1`, `Q2`, etc. -- `prompt`: the full question shown to the user -- `options`: predefined choices the model wants the user to pick from - -### Normalization rules - -Before rendering the UI: - -1. Ensure at least one question exists. -2. Ensure each question has a usable short label. -3. Preserve the provided predefined options as-is. -4. Append a final synthetic option to every question: - - label: `Something else…` - - behavior: switch into inline text entry -5. Do not require the model to explicitly include the synthetic option. - -## Interaction Design - -### Single-question mode - -When exactly one question is provided: - -- display the prompt -- display numbered predefined options -- automatically display the final appended option: - - `Something else…` -- selecting a predefined option completes the tool immediately -- selecting `Something else…` opens inline free-text entry -- `Esc` in the picker cancels the tool -- `Esc` in text entry exits text entry and returns to the option list - -### Multi-question mode - -When multiple questions are provided: - -- show one question at a time -- allow tab or left/right navigation between questions -- append `Something else…` to every question -- after answering one question, move to the next question -- include a final review/submit step summarizing all current answers -- allow navigating back to change answers before final submission -- submit only from the review step - -This provides a guided flow without requiring separate tools. - -## Answer Model - -The tool result should always remain structured. - -Conceptual result shape: - -```ts -{ - questions: Question[]; - answers: Array<{ - id: string; - value: string; - label: string; - wasCustom: boolean; - index?: number; - }>; - cancelled: boolean; -} -``` - -### Predefined option answers - -For a predefined choice: - -- `value` = the provided option value -- `label` = the provided option label -- `wasCustom` = `false` -- `index` = 1-based index of the selected predefined option - -### Custom answers via “Something else…” - -For a typed answer: - -- `value` = typed text -- `label` = typed text -- `wasCustom` = `true` -- `index` is omitted - -This gives the agent consistent structured data while preserving user freedom. - -## Rendering - -The extension should provide readable tool renderers: - -### `renderCall` - -Show: - -- tool name (`question`) -- question count -- short labels or summary where useful - -### `renderResult` - -Show: - -- `Cancelled` when the user aborts -- one concise success line per answered question -- whether an answer was predefined or custom when helpful - -The rendering should remain compact in normal use and not dump full raw JSON unless the default fallback is needed. - -## Error Handling - -The tool should return structured results for expected user/runtime states instead of throwing. - -### Non-interactive mode - -If pi is running without interactive UI support: - -- return a clear text result indicating UI is unavailable -- mark the interaction as `cancelled: true` in details -- do not crash the session - -### Invalid input - -If `questions` is empty: - -- return a clear text result like `Error: No questions provided` -- include a structured details payload with `cancelled: true` - -### User cancel - -If the user cancels from the picker or review flow: - -- return `cancelled: true` -- do not throw an exception - -### Empty custom text - -If the user enters free-text mode and submits an empty value: - -- do not accept an empty answer -- keep the user in text-entry mode until they provide non-empty text or press `Esc` -- avoid returning meaningless blank answers to the model - -## File Structure - -Implementation stays in one file unless complexity clearly justifies splitting later: - -- Create: `/home/alex/dotfiles/.pi/agent/extensions/question.ts` - -Internal sections inside the file should stay logically separated: - -1. types and schemas -2. question normalization helpers -3. single-question UI flow -4. multi-question UI flow -5. tool registration -6. call/result rendering - -## Loading and Usage - -Because the file will live in an auto-discovered project extension directory, the expected activation flow is: - -1. start pi from the dotfiles repo or a directory where the project extension is in scope -2. use `/reload` if pi is already running -3. allow the model to call `question` when clarification is needed - -## Testing Strategy - -No dedicated automated test harness is required for the first version. - -Manual verification should cover: - -1. **Single question, predefined answer** - - tool returns selected option value/label -2. **Single question, custom answer** - - selecting `Something else…` opens text entry and returns typed text -3. **Single question, cancel** - - cancellation returns structured cancelled result -4. **Multi-question, all predefined** - - step-through and final review work correctly -5. **Multi-question, mixed predefined/custom** - - at least one typed answer and one predefined answer are preserved correctly -6. **Multi-question, edit before submit** - - user can revisit and change answers before final submission -7. **Empty custom submission** - - blank text is rejected or bounced back safely -8. **Non-interactive mode** - - tool returns a clear UI-unavailable result - -## Non-Goals - -The first version will not add: - -- separate text-only question types -- nested conditional question trees -- validation rules beyond basic non-empty custom text handling -- persistence beyond normal pi session/tool result storage -- a separate `questionnaire` tool name - -## Acceptance Criteria - -The work is complete when: - -1. `.pi/agent/extensions/question.ts` exists in this repo -2. pi discovers the extension via project auto-discovery -3. the agent has a single `question` tool -4. the tool supports both one-question and multi-question flows -5. every question automatically ends with `Something else…` -6. selecting `Something else…` allows direct typed input -7. results are structured and distinguish custom answers from predefined ones -8. cancel/error states return cleanly without crashing the session diff --git a/docs/superpowers/specs/2026-04-09-web-search-tools-design.md b/docs/superpowers/specs/2026-04-09-web-search-tools-design.md deleted file mode 100644 index 0f3c015..0000000 --- a/docs/superpowers/specs/2026-04-09-web-search-tools-design.md +++ /dev/null @@ -1,391 +0,0 @@ -# Web Search Tools Design - -**Date:** 2026-04-09 -**Project:** `/home/alex/dotfiles` -**Target files:** -- `.pi/agent/extensions/web-search/package.json` -- `.pi/agent/extensions/web-search/index.ts` -- `.pi/agent/extensions/web-search/src/schema.ts` -- `.pi/agent/extensions/web-search/src/config.ts` -- `.pi/agent/extensions/web-search/src/providers/types.ts` -- `.pi/agent/extensions/web-search/src/providers/exa.ts` -- `.pi/agent/extensions/web-search/src/tools/web-search.ts` -- `.pi/agent/extensions/web-search/src/tools/web-fetch.ts` -- `.pi/agent/extensions/web-search/src/format.ts` -- tests alongside the new modules - -## Goal - -Add two generic pi tools, `web_search` and `web_fetch`, implemented as a modular extension package that uses Exa as the first provider while keeping the internal design extensible for future providers. - -## Context - -- This dotfiles repo already tracks pi configuration under `.pi/agent/`. -- The current extension workspace contains a tracked `question` extension and small pure helper tests. -- Pi extensions can be packaged as directories with `index.ts` and their own `package.json`, which is the best fit when third-party dependencies are needed. -- The requested feature is explicitly about pi extensions and custom tools, not built-in model providers. -- The user wants: - - generic tool names now - - Exa as the first provider - - configuration read from a separate global file, not `settings.json` - - configuration stored only at the global scope - -## User-Approved Requirements - -1. Add two generic tools: - - `web_search` - - `web_fetch` -2. Use Exa as the initial provider. -3. Keep the implementation extensible so other providers can be added later. -4. Do **not** read configuration from environment variables. -5. Do **not** read configuration from `settings.json`. -6. Read configuration from a dedicated global file: - - `~/.pi/agent/web-search.json` -7. Use a provider-list-based config shape, not a single-provider-only schema. -8. Store credentials as literal values in that config file. -9. `web_search` should return **metadata only** by default. -10. `web_fetch` should accept **one URL or multiple URLs**. -11. `web_fetch` should return **text** by default. -12. The implementation direction should be the modular/package-style structure, not the minimal Exa-shaped shortcut. - -## Recommended Architecture - -Implement the feature as a dedicated extension package at: - -- `/home/alex/dotfiles/.pi/agent/extensions/web-search/` - -This package will register two generic tools and route both through a provider registry. At runtime, the extension loads `~/.pi/agent/web-search.json`, validates it, normalizes the provider list into an internal lookup map, resolves the configured default provider, and then executes requests through a provider adapter. - -For the first version, the only adapter is Exa. However, the tool-facing layer remains provider-agnostic, so future providers only need to implement the shared provider interface and be added to config validation/registry wiring. - -This is intentionally more structured than a single-file Exa wrapper because the user explicitly wants future extensibility without changing tool names or reworking the public API later. - -## File Structure - -### Extension package - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/package.json` - - declares the extension package - - declares `exa-js` as a dependency - - points pi at the extension entrypoint - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/index.ts` - - extension entrypoint - - registers `web_search` and `web_fetch` - - wires together config loading, provider registry, tool handlers, and shared formatting - -### Shared schemas and config - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/schema.ts` - - TypeBox schemas for tool parameters - - TypeBox schemas for `web-search.json` - - shared TypeScript types derived from the schemas where useful - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/config.ts` - - reads `~/.pi/agent/web-search.json` - - validates config shape - - normalizes provider list into an internal map keyed by provider name - - resolves default provider - -### Provider abstraction - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/types.ts` - - generic request and response types for search/fetch - - provider interface used by the tool layer - - normalized internal result shapes independent of Exa SDK types - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/providers/exa.ts` - - Exa-backed implementation of the provider interface - - translates generic search requests into Exa `search(...)` - - translates generic fetch requests into Exa `getContents(...)` - - isolates all Exa-specific request/response details - -### Tool handlers and formatting - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-search.ts` - - `web_search` schema, execution logic, and tool rendering helpers - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/tools/web-fetch.ts` - - `web_fetch` schema, execution logic, and tool rendering helpers - -- **Create:** `/home/alex/dotfiles/.pi/agent/extensions/web-search/src/format.ts` - - shared output shaping - - compact text summaries for the LLM - - truncation behavior for large results - - per-result formatting for batch fetches and partial failures - -## Config File Design - -The extension will read exactly one file: - -- `~/.pi/agent/web-search.json` - -Initial conceptual shape: - -```json -{ - "defaultProvider": "exa-main", - "providers": [ - { - "name": "exa-main", - "type": "exa", - "apiKey": "exa_...", - "options": { - "defaultSearchLimit": 5, - "defaultFetchTextMaxCharacters": 12000 - } - } - ] -} -``` - -### Config rules - -- `defaultProvider` must match one provider entry by name. -- `providers` must be a non-empty array. -- Each provider entry must include: - - `name` - - `type` - - `apiKey` -- `apiKey` is a literal string in the first version. -- `type` is validated so the runtime can select the correct adapter. -- Exa-specific defaults may live under `options`, but they must remain optional. - -### Config non-goals - -The first version will **not**: - -- read provider config from project-local files -- merge config from multiple files -- read credentials from env vars -- support shell-command-based credential resolution -- write or edit `web-search.json` automatically - -If the file is missing or invalid, the tools should return a clear error telling the user where the file belongs and showing a minimal valid example. - -## Tool Contract - -### `web_search` - -Purpose: search the web and return result metadata with a generic surface that can outlive Exa. - -Conceptual input shape: - -```ts -{ - query: string; - limit?: number; - includeDomains?: string[]; - excludeDomains?: string[]; - startPublishedDate?: string; - endPublishedDate?: string; - category?: string; - provider?: string; -} -``` - -### Default behavior - -- returns metadata only -- does not fetch page text by default -- uses the default configured provider unless `provider` explicitly selects another configured provider - -### Result shape intent - -Each search result should preserve a normalized subset of provider output such as: - -- `title` -- `url` -- `publishedDate` -- `author` -- `score` -- provider-specific stable identifiers only if useful for follow-up operations - -The tool’s text output should stay compact and easy for the model to scan. - -### `web_fetch` - -Purpose: fetch contents for one or more URLs with a generic interface. - -Conceptual input shape: - -```ts -{ - urls: string[]; - text?: boolean; - highlights?: boolean; - summary?: boolean; - textMaxCharacters?: number; - provider?: string; -} -``` - -### Input normalization - -The canonical tool shape is `urls: string[]`, where a single URL is represented as a one-element array. For robustness, the implementation may also accept a top-level `url` string through argument normalization and fold it into `urls`, but the stable contract exposed in schemas and docs should remain `urls: string[]`. - -### Default behavior - -- when no content mode is specified, fetch text -- batch requests are allowed -- the default configured provider is used unless overridden - -### Result shape intent - -Each fetched item should preserve normalized per-URL results, including: - -- `url` -- `title` where available -- `text` by default -- optional `highlights` -- optional `summary` -- per-item failure details for partial batch failures - -## Provider Abstraction - -The provider interface should express the minimum shared behaviors needed by the tools: - -```ts -interface WebSearchProvider { - type: string; - search(request: NormalizedSearchRequest): Promise; - fetch(request: NormalizedFetchRequest): Promise; -} -``` - -### Exa adapter responsibilities - -The Exa adapter will: - -- instantiate an Exa client from the configured literal API key -- use Exa search without contents for `web_search` default behavior -- use Exa `getContents(...)` for `web_fetch` -- map Exa response fields into normalized provider-agnostic result types -- keep Exa-only fields contained inside the adapter unless they are intentionally promoted into the shared result model later - -This keeps future provider additions focused: implement the same interface, extend config validation, and register the adapter. - -## Rendering and Output Design - -The extension should provide compact tool rendering so calls and results are readable inside pi. - -### `renderCall` - -- `web_search`: show tool name and the query -- `web_fetch`: show tool name and URL count (or the single URL) - -### `renderResult` - -- `web_search`: show result count and a short numbered list of titles/URLs -- `web_fetch`: show fetched count, failed count if any, and a concise per-URL summary - -### LLM-facing text output - -The text returned to the model should be concise and predictable: - -- search: compact metadata list only by default -- fetch: truncated text payloads with enough context to be useful -- batch fetch: clearly separated per-URL sections - -Large outputs must be truncated with the shared truncation utilities pattern used by pi tool examples. - -## Error Handling - -Expected runtime failures should be handled cleanly and descriptively. - -### Config errors - -- missing `~/.pi/agent/web-search.json` -- invalid JSON -- schema mismatch -- empty provider list -- unknown `defaultProvider` -- unknown explicitly requested provider -- missing literal API key - -These should return actionable errors naming the exact issue. - -### Input errors - -- empty search query -- malformed URL(s) -- empty URL list after normalization - -These should be rejected before any provider request is made. - -### Provider/runtime errors - -- Exa authentication failures -- network failures -- rate limits -- unexpected response shapes - -These should return a concise summary in tool content while preserving richer diagnostics in `details`. - -### Partial failures - -For batch `web_fetch`, mixed outcomes should not fail the entire request unless every target fails. Successful pages should still be returned together with per-URL failure entries. - -## Testing Strategy - -The design intentionally separates pure logic from pi wiring so most behavior can be tested without loading pi itself. - -### Automated tests - -Cover: - -1. config parsing and normalization -2. provider-list validation -3. default-provider resolution -4. generic request → Exa request mapping -5. Exa response → normalized response mapping -6. compact formatting for metadata-only search -7. truncation for long fetch results -8. batch fetch formatting with partial failures -9. helpful error messages when config is absent or invalid - -### Test style - -- prefer pure module tests for config, normalization, and formatting -- inject a fake Exa-like client into the Exa adapter instead of making live network calls -- keep extension entrypoint tests to smoke coverage only - -### Manual verification - -After implementation: - -1. create `~/.pi/agent/web-search.json` -2. reload pi -3. run one `web_search` call -4. run one single-URL `web_fetch` call -5. run one multi-URL `web_fetch` call -6. confirm missing/invalid config errors are readable - -## Non-Goals - -The first version will not add: - -- other providers besides Exa -- project-local web-search config -- automatic setup commands or interactive config editing -- provider-specific passthrough options in the public tool API -- rich snippet/highlight defaults for search -- live network integration tests in the normal automated suite - -## Acceptance Criteria - -The work is complete when: - -1. pi discovers a new extension package at `.pi/agent/extensions/web-search/` -2. the agent has two generic tools: - - `web_search` - - `web_fetch` -3. the implementation uses an internal provider abstraction -4. Exa is the first working provider implementation -5. the runtime reads global config from `~/.pi/agent/web-search.json` -6. config uses a provider-list shape with a default provider selector -7. credentials are read as literal values from that file -8. `web_search` returns metadata only by default -9. `web_fetch` accepts one or multiple URLs and returns text by default -10. missing config, invalid config, and provider failures return clean, actionable tool errors -11. core mapping/formatting/config logic is covered by automated tests