zidane 7.2.4 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +34 -6
- package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
- package/dist/acp-DQDhi2dj.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -1
- package/dist/acp-cli.js +9 -66
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +10 -590
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +3 -3
- package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
- package/dist/agent-DSLNmBp0.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
- package/dist/anthropic-CD00LhUP.js.map +1 -0
- package/dist/artifacts-CV2hUKnL.js.map +1 -1
- package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
- package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
- package/dist/call-tool-dHlbGOIY.js +70 -0
- package/dist/call-tool-dHlbGOIY.js.map +1 -0
- package/dist/chat/pure.d.ts +372 -6
- package/dist/chat/pure.d.ts.map +1 -1
- package/dist/chat/pure.js +35 -244
- package/dist/chat/pure.js.map +1 -1
- package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
- package/dist/chat-JPJJ8uu5.js.map +1 -0
- package/dist/chat.d.ts +4 -6
- package/dist/chat.js +23 -12
- package/dist/cli-shared-C-YCaglL.js +159 -0
- package/dist/cli-shared-C-YCaglL.js.map +1 -0
- package/dist/client-core-BQSyDALT.d.ts +2115 -0
- package/dist/client-core-BQSyDALT.d.ts.map +1 -0
- package/dist/codemode-worker.d.ts +1 -0
- package/dist/codemode-worker.js +155 -0
- package/dist/codemode-worker.js.map +1 -0
- package/dist/codemode.d.ts +352 -0
- package/dist/codemode.d.ts.map +1 -0
- package/dist/codemode.js +5 -0
- package/dist/connection-Dr-Js4yZ.d.ts +28 -0
- package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
- package/dist/connection-loop-gdh22LTI.js +112 -0
- package/dist/connection-loop-gdh22LTI.js.map +1 -0
- package/dist/context-segments-BHKlagL6.js +1016 -0
- package/dist/context-segments-BHKlagL6.js.map +1 -0
- package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
- package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +19 -8
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +1 -440
- package/dist/contexts/docker.d.ts +36 -7
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +1 -456
- package/dist/contexts/e2b.d.ts +68 -13
- package/dist/contexts/e2b.d.ts.map +1 -1
- package/dist/contexts/e2b.js +1 -382
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +5 -0
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
- package/dist/contexts-d7UolYot.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/cred-keys-GpBg71cP.js +105 -0
- package/dist/cred-keys-GpBg71cP.js.map +1 -0
- package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
- package/dist/credentials-JttEuRON.js.map +1 -0
- package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
- package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
- package/dist/daemon-cli.d.ts +1 -1
- package/dist/daemon-cli.js +3 -2
- package/dist/daemon-cli.js.map +1 -1
- package/dist/daytona-DG3omipZ.js +513 -0
- package/dist/daytona-DG3omipZ.js.map +1 -0
- package/dist/defaults-Cx3zvDOV.js +23 -0
- package/dist/defaults-Cx3zvDOV.js.map +1 -0
- package/dist/dispatcher-BMlu2BmZ.js +4941 -0
- package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
- package/dist/docker-BZhMMJUp.js +727 -0
- package/dist/docker-BZhMMJUp.js.map +1 -0
- package/dist/e2b-CEVOdRib.js +480 -0
- package/dist/e2b-CEVOdRib.js.map +1 -0
- package/dist/edit-utils-EGosADZq.js +234 -0
- package/dist/edit-utils-EGosADZq.js.map +1 -0
- package/dist/environment-8wsKE2Ln.js +214 -0
- package/dist/environment-8wsKE2Ln.js.map +1 -0
- package/dist/eval-BNcvle_J.js +1595 -0
- package/dist/eval-BNcvle_J.js.map +1 -0
- package/dist/eval.d.ts +2 -2
- package/dist/eval.js +1 -1489
- package/dist/executor-Cx5-8yqi.js +621 -0
- package/dist/executor-Cx5-8yqi.js.map +1 -0
- package/dist/extension-authoring-DyGGmq9V.js +385 -0
- package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +3 -3
- package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
- package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
- package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
- package/dist/format-C2NQEVUH.js.map +1 -0
- package/dist/format-bytes-C7tfc3Zm.js +55 -0
- package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
- package/dist/headless-C_rNUuYC.js +960 -0
- package/dist/headless-C_rNUuYC.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -413
- package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
- package/dist/history-search-CcYzF8-0.js.map +1 -0
- package/dist/history-search-worker.d.ts +1 -1
- package/dist/history-search-worker.js +1 -1
- package/dist/host-D7NrI03V.js +378 -0
- package/dist/host-D7NrI03V.js.map +1 -0
- package/dist/host-signals-BcP5QKUD.js +118 -0
- package/dist/host-signals-BcP5QKUD.js.map +1 -0
- package/dist/identity-CwfO8K-t.js +26 -0
- package/dist/identity-CwfO8K-t.js.map +1 -0
- package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
- package/dist/index-BGe1fQT7.d.ts.map +1 -0
- package/dist/index-BVQsIKGn.d.ts +448 -0
- package/dist/index-BVQsIKGn.d.ts.map +1 -0
- package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
- package/dist/index-CHABJd-L.d.ts.map +1 -0
- package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
- package/dist/index-D6a7qXpg.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +27 -23
- package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
- package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
- package/dist/ipc/remote.d.ts +313 -0
- package/dist/ipc/remote.d.ts.map +1 -0
- package/dist/ipc/remote.js +4 -0
- package/dist/ipc.d.ts +6 -964
- package/dist/ipc.js +403 -4
- package/dist/ipc.js.map +1 -0
- package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
- package/dist/isolation-D9L650W5.d.ts.map +1 -0
- package/dist/json-rpc--HXQti1E.js +350 -0
- package/dist/json-rpc--HXQti1E.js.map +1 -0
- package/dist/links-j8rED1sR.js +603 -0
- package/dist/links-j8rED1sR.js.map +1 -0
- package/dist/local-terminal-D8QThwo_.js +252 -0
- package/dist/local-terminal-D8QThwo_.js.map +1 -0
- package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
- package/dist/logger-Gtqlayx8.js.map +1 -0
- package/dist/logs-view-CWG8aUAG.js.map +1 -1
- package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
- package/dist/mcp-_wecH9Qe.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/media-sniff-UKijEaiQ.js.map +1 -1
- package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
- package/dist/messages-BqiwcD2Q.js.map +1 -0
- package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
- package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
- package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
- package/dist/openai-compat-CFrpH7uq.js.map +1 -0
- package/dist/orchestration-BCGmB8Ps.js +511 -0
- package/dist/orchestration-BCGmB8Ps.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/output/terminal.js +13 -7
- package/dist/output/terminal.js.map +1 -1
- package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
- package/dist/plugins-DS7mahzZ.js.map +1 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +1 -2
- package/dist/prelude-Cxo4t74B.js +517 -0
- package/dist/prelude-Cxo4t74B.js.map +1 -0
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -2
- package/dist/project-root-B8zADr4o.js +167 -0
- package/dist/project-root-B8zADr4o.js.map +1 -0
- package/dist/project-user-paths-BSgtrRxl.js +58 -0
- package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
- package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
- package/dist/prompts-DYcvqShf.d.ts.map +1 -0
- package/dist/protocol-DnhqqSmE.js +13 -0
- package/dist/protocol-DnhqqSmE.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +1 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +3 -2
- package/dist/providers/cerebras.js.map +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -2
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +15 -12
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +3 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +3 -3
- package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
- package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/registry-D6ShBWJU.js +5234 -0
- package/dist/registry-D6ShBWJU.js.map +1 -0
- package/dist/remote-hosts-BtA_9ZGp.js +97 -0
- package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
- package/dist/restate.d.ts +2 -3
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +631 -572
- package/dist/restate.js.map +1 -1
- package/dist/run-summary-CklLiZ4y.js +288 -0
- package/dist/run-summary-CklLiZ4y.js.map +1 -0
- package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
- package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
- package/dist/session/sqlite.d.ts +1 -2
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +1 -616
- package/dist/session-factory-BUGY5P40.js +1576 -0
- package/dist/session-factory-BUGY5P40.js.map +1 -0
- package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
- package/dist/session-xh9JXu6i.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/settings-keys-Ckf29NeM.js +677 -0
- package/dist/settings-keys-Ckf29NeM.js.map +1 -0
- package/dist/shell-path-Dqaoow2J.js +412 -0
- package/dist/shell-path-Dqaoow2J.js.map +1 -0
- package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
- package/dist/skills-DKqZJm4X.js.map +1 -0
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -2
- package/dist/source-mPExaUm8.js +381 -0
- package/dist/source-mPExaUm8.js.map +1 -0
- package/dist/sqlite-DV1DQzN2.js +698 -0
- package/dist/sqlite-DV1DQzN2.js.map +1 -0
- package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
- package/dist/src-BRMYRJib.js.map +1 -0
- package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
- package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
- package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
- package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
- package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
- package/dist/timeout-DEaqslLO.d.ts.map +1 -1
- package/dist/todo-edits-Dn251YUP.js +1790 -0
- package/dist/todo-edits-Dn251YUP.js.map +1 -0
- package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
- package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
- package/dist/tool-state-Dku6YqAB.js +989 -0
- package/dist/tool-state-Dku6YqAB.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -2
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +1 -2
- package/dist/tools/prompts.d.ts +1 -1
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/web-search.d.ts +1 -2
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +2 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools-_DnT3AGM.js +578 -0
- package/dist/tools-_DnT3AGM.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +4 -2
- package/dist/tui.d.ts +534 -517
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +34933 -27242
- package/dist/tui.js.map +1 -1
- package/dist/turn-operations-B8sokwi5.js +333 -0
- package/dist/turn-operations-B8sokwi5.js.map +1 -0
- package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
- package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
- package/dist/types-C7ZFOztE.d.ts +132 -0
- package/dist/types-C7ZFOztE.d.ts.map +1 -0
- package/dist/types-CH5XpIHw.js +114 -0
- package/dist/types-CH5XpIHw.js.map +1 -0
- package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
- package/dist/types-Cu0ZGDu9.js.map +1 -0
- package/dist/types-IqegbJEE.d.ts +624 -0
- package/dist/types-IqegbJEE.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -1
- package/dist/url-D8HCl65b.js +1259 -0
- package/dist/url-D8HCl65b.js.map +1 -0
- package/dist/wasm-BVT-j5cl.js +33 -0
- package/dist/wasm-BVT-j5cl.js.map +1 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
- package/dist/ws-CIHMvlSh.js +952 -0
- package/dist/ws-CIHMvlSh.js.map +1 -0
- package/dist/ws-client-N7NxzvQp.js +230 -0
- package/dist/ws-client-N7NxzvQp.js.map +1 -0
- package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
- package/dist/xai-CvJTeLrI.js.map +1 -0
- package/dist/xdg-zweed0-t.js +117 -0
- package/dist/xdg-zweed0-t.js.map +1 -0
- package/docs/ACP.md +52 -0
- package/docs/ARCHITECTURE.md +103 -6
- package/docs/ARTIFACTS.md +8 -4
- package/docs/CHAT.md +165 -14
- package/docs/EXECUTION_CONTEXT.md +8 -0
- package/docs/EXTENSIONS.md +123 -2
- package/docs/HEADLESS.md +2 -0
- package/docs/IPC.md +775 -64
- package/docs/MACHINES.md +456 -0
- package/docs/PANELS.md +3 -0
- package/docs/README.md +4 -0
- package/docs/RESTATE.md +3 -2
- package/docs/RUN_IN_BACKGROUND.md +10 -2
- package/docs/SERVER.md +1085 -0
- package/docs/SKILL.md +6 -1
- package/docs/TERMINAL.md +75 -0
- package/docs/TUI.md +65 -14
- package/package.json +58 -26
- package/scripts/eval.ts +4 -0
- package/dist/acp-D-tVyUjw.js.map +0 -1
- package/dist/agent-D0zZnGbt.js.map +0 -1
- package/dist/anthropic-C2aole0G.js.map +0 -1
- package/dist/approvals-DKaJZYlm.js.map +0 -1
- package/dist/atomic-write-B7zr1ddF.js.map +0 -1
- package/dist/auth-BG2rVKSt.js.map +0 -1
- package/dist/completion-core-BRo86R_L.js.map +0 -1
- package/dist/contexts/daytona.js.map +0 -1
- package/dist/contexts/docker.js.map +0 -1
- package/dist/contexts/e2b.js.map +0 -1
- package/dist/contexts-CRE9pw07.js.map +0 -1
- package/dist/eval.js.map +0 -1
- package/dist/format-BF745pLL.js.map +0 -1
- package/dist/glob-DluQFSw1.js +0 -270
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/headless.js.map +0 -1
- package/dist/history-search-C4bk1Wz_.js.map +0 -1
- package/dist/index-BgN1T3lk.d.ts.map +0 -1
- package/dist/index-C37_ppWv.d.ts.map +0 -1
- package/dist/index-C6NEd5y_.d.ts.map +0 -1
- package/dist/ipc-zVSyo6K1.js +0 -331
- package/dist/ipc-zVSyo6K1.js.map +0 -1
- package/dist/ipc.d.ts.map +0 -1
- package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
- package/dist/json-rpc-rSWMUh9b.js +0 -192
- package/dist/json-rpc-rSWMUh9b.js.map +0 -1
- package/dist/logger--C1VQjwJ.js.map +0 -1
- package/dist/mcp-CgNd-f3F.js.map +0 -1
- package/dist/messages-2jcOA_5R.js.map +0 -1
- package/dist/openai-compat-Dy56weK8.js.map +0 -1
- package/dist/output-cap-C9zukcCj.js +0 -100
- package/dist/output-cap-C9zukcCj.js.map +0 -1
- package/dist/panels-B6gRk4bf.js +0 -789
- package/dist/panels-B6gRk4bf.js.map +0 -1
- package/dist/plugins-gbI7SBA6.js.map +0 -1
- package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
- package/dist/run-summary-BU_NgwUT.js +0 -269
- package/dist/run-summary-BU_NgwUT.js.map +0 -1
- package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
- package/dist/session/sqlite.js.map +0 -1
- package/dist/session-BqRP1hS7.js.map +0 -1
- package/dist/shell-path-h5czzllR.js +0 -1549
- package/dist/shell-path-h5czzllR.js.map +0 -1
- package/dist/skills-BC3dodWp.js.map +0 -1
- package/dist/src-CkNQKFKr.js.map +0 -1
- package/dist/todo-edits-UOwgxGKu.js +0 -528
- package/dist/todo-edits-UOwgxGKu.js.map +0 -1
- package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
- package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
- package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
- package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
- package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
- package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
- package/dist/turn-operations-DrbJlpq9.js.map +0 -1
- package/dist/types-CWgENVLY.js.map +0 -1
- package/dist/xai-CazjStOK.js.map +0 -1
package/dist/extensions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $a as
|
|
2
|
-
export { type ArtifactDetectHookOptions, type ArtifactResolveOptions, BUILTIN_ARTIFACT_KINDS, type BuildExtensionRegistryOptions, type DiscoverAndBuildExtensionRegistryOptions, EMPTY_EXTENSION_REGISTRY, ENTRY_CANDIDATES, EXTENSION_BUS_MAX_PAYLOAD_BYTES, type ExtensionAgentPluginOptions, type ExtensionApiVersion, type ExtensionArtifactDetection, type ExtensionArtifactKindRegistration, type ExtensionArtifactQuery, type ExtensionArtifactRendererEntry, type ExtensionArtifactRendererRegistration, type ExtensionArtifactRow, type ExtensionArtifactScope, type ExtensionArtifactsSurface, type ExtensionBannerOptions, type ExtensionBehaviorTransform, type ExtensionBus, type ExtensionBusChannel, type ExtensionBusEmitOptions, type ExtensionBusEnvelope, type ExtensionBusMessage, type ExtensionBusSurface, type ExtensionCapability, type ExtensionCatalogPartition, type ExtensionConfigActionItem, type ExtensionConfigActionResult, type ExtensionConfigActionStatus, type ExtensionConfigBooleanItem, type ExtensionConfigCategory, type ExtensionConfigChangeBehavior, type ExtensionConfigEnumItem, type ExtensionConfigItem, type ExtensionConfigItemBase, type ExtensionConfigModelItem, type ExtensionConfigNumberItem, type ExtensionConfigScope, type ExtensionConfigStringItem, type ExtensionConfigSurface, type ExtensionConfigValue, type ExtensionConfigValueItem, type ExtensionContext, type ExtensionContextTransform, type ExtensionContributions, type ExtensionCustomRenderer, type ExtensionCustomRendererEntry, type ExtensionDefinition, type ExtensionFetchResult, type ExtensionFooterHint, type ExtensionGuiPanel, type ExtensionGuiSurface, type ExtensionHostRuntimeBridge, type ExtensionHostRuntimeImpl, type ExtensionHud, type ExtensionHudState, type ExtensionHudStatus, type ExtensionHudWriter, type ExtensionInstallCandidate, ExtensionInstallError, type ExtensionInstallErrorCode, type ExtensionInstallIo, type ExtensionInstallProvenance, type ExtensionInstallResult, type ExtensionInstallScope, type ExtensionInstallSource, type ExtensionKeybinding, type ExtensionKeybindingDispatchContext, type ExtensionManifest, type ExtensionMcpConfigTransform, type ExtensionModal, type ExtensionModalNode, type ExtensionModelEntry, type ExtensionNotification, type ExtensionNotifyOptions, type ExtensionNotifyTone, type ExtensionOrderedContribution, type ExtensionPaletteCommand, type ExtensionPaletteItem, type ExtensionPaletteProvider, type ExtensionPaletteProviderEntry, type ExtensionPaletteRunResult, type ExtensionPaletteWhen, type ExtensionPanel, type ExtensionPanelEntry, type ExtensionPaths, type ExtensionPermission, type ExtensionPromptBridge, type ExtensionPromptDelivery, type ExtensionPromptTransform, type ExtensionProviderMiddleware, type ExtensionRegistry, type ExtensionRunTransform, type ExtensionRuntimeEnv, type ExtensionRuntimeSurface, type ExtensionScanEntry, type ExtensionSessionStateSurface, type ExtensionSessionSummary, type ExtensionSessionsSurface, type ExtensionSkillPolicy, type ExtensionSource, type ExtensionStatusOptions, type ExtensionStorageScope, type ExtensionStorageSurface, type ExtensionToolCatalogTransform, type ExtensionToolPolicy, type ExtensionToolRenderProps, type ExtensionToolRenderer, type ExtensionToolRendererEntry, type ExtensionToolRendererRegistration, type ExtensionToolResultMetadataRegistration, type ExtensionToolResultPatch, type ExtensionToolResultTransform, type ExtensionToolVerdict, type ExtensionTuiSurface, type ExtensionWorkspacePair, type ExtensionWorkspaceSurface, type GuiContributions, type InstallExtensionFromSpecOptions, type InstalledExtensionTargetOptions, type LoadedExtension, type LoadedPluginRecord, PANEL_CONTENT_LIMITS, type PanelAction, type PanelContent, type PanelCustomItem, type PanelIcon, type PanelItem, type PanelKeyValueItem, type PanelLoadContext, type PanelMarkdownItem, type PanelRowItem, type PanelSection, type StoredExtensionConfigItem, type TuiContributions, type WireExtensionConfigItem, type WireExtensionConfigSchema, artifactKindLabel, branchKind, buildExtensionPreset, buildExtensionRegistry, clampExtensionConfigNumber, coerceExtensionConfigInput, commitKind, composeExtensionPreset, createArtifactDetectHook, createArtifactsSurface, createExtensionBus, createExtensionHostRuntimeBridge, createExtensionHud, createExtensionPromptBridge, createWorkspaceSurface, defaultExtensionScanPaths, defineExtension, discoverAndBuildExtensionRegistry, discoverExtensions, disposeExtensionRegistry, fetchExtensionSource, fileKind, filterExtensionRegistry, filterRegistryPlugins, githubTokenSource, inlineLoadedExtension, installExtensionFromSpec, isExtensionDefinition, isValidExtensionConfigItem, isValidExtensionConfigValue, listExtensionCandidates, mergeContributions, normalizePanelContent, panelBadgeText, parseExtensionInstallSource, parseExtensionScanPathEnv, parseShortstat, partitionExtensionCatalog, pullRequestKind, readExtensionInstallProvenance, refreshArtifact, registerExtensionHostModules, removeInstalledExtension, renderArtifactRow, resolveArtifactKind, resolveExtensionConfig, resolveExtensionConfigValue, sanitizeExtensionConfigActionPatch, scanExtensionDirectories, serializedPayloadBytes, toWireConfigSchema, updateInstalledExtension };
|
|
1
|
+
import { $a as defineExtension, $c as normalizeBadgeEffect, $o as ExtensionMcpToolResult, $s as ExtensionArtifactScope, Aa as fetchExtensionSource, Ac as WireExtensionConfigSchema, Ao as ExtensionArtifactsSurface, As as ExtensionToolRenderer, Ba as defaultExtensionScanPaths, Bc as BadgeActionEffect, Bo as ExtensionContributions, Bs as LoadedPluginRecord, Ca as ExtensionInstallIo, Cc as ExtensionConfigScope, Cd as normalizePanelLoadContext, Co as ExtensionApiVersion, Cs as ExtensionSkillPolicy, Da as ExtensionInstallSource, Dc as ExtensionConfigValueItem, Do as ExtensionArtifactRendererEntry, Ds as ExtensionToolCatalogTransform, Ea as ExtensionInstallScope, Ec as ExtensionConfigValue, Eo as ExtensionArtifactQuery, Es as ExtensionStorageSurface, Fa as removeInstalledExtension, Fc as resolveExtensionConfig, Fo as ExtensionBusMessage, Fs as ExtensionToolResultTransform, Ga as scanExtensionDirectories, Gc as ExtensionBadgeActions, Go as ExtensionGuiPanel, Gs as ExtensionHudState, Ha as inlineLoadedExtension, Hc as BadgeMenuItem, Ho as ExtensionCustomRendererEntry, Hs as TuiContributions, Ia as updateInstalledExtension, Ic as resolveExtensionConfigValue, Io as ExtensionBusSurface, Is as ExtensionToolVerdict, Ja as ExtensionPromptBridge, Jc as badgePromptText, Jo as ExtensionKeybindingDispatchContext, Js as ExtensionNotification, Ka as DiscoverAndBuildExtensionRegistryOptions, Kc as artifactBadgeTarget, Ko as ExtensionGuiSurface, Ks as ExtensionHudStatus, La as registerExtensionHostModules, Lc as sanitizeExtensionConfigActionPatch, Lo as ExtensionCapability, Ls as ExtensionTuiSurface, Ma as listExtensionCandidates, Mc as coerceExtensionConfigInput, Mo as ExtensionBadgeTone, Ms as ExtensionToolRendererRegistration, Na as parseExtensionInstallSource, Nc as isValidExtensionConfigItem, No as ExtensionBehaviorTransform, Ns as ExtensionToolResultMetadataRegistration, Oa as InstallExtensionFromSpecOptions, Oc as StoredExtensionConfigItem, Oo as ExtensionArtifactRendererRegistration, Os as ExtensionToolPolicy, Pa as readExtensionInstallProvenance, Pc as isValidExtensionConfigValue, Po as ExtensionBusEmitOptions, Ps as ExtensionToolResultPatch, Qa as createMcpSurface, Qc as mergeBadgeMenu, Qo as ExtensionMcpSurface, Qs as createExtensionHud, Ra as ENTRY_CANDIDATES, Rc as toWireConfigSchema, Ro as ExtensionContext, Rs as GuiContributions, Sa as ExtensionInstallErrorCode, Sc as ExtensionConfigNumberItem, Sd as normalizePanelContent, So as ExtensionAgentPluginOptions, Ss as ExtensionSessionStateSurface, Ta as ExtensionInstallResult, Tc as ExtensionConfigSurface, To as ExtensionArtifactKindRegistration, Ts as ExtensionStorageScope, Ua as isExtensionDefinition, Uc as BadgeTarget, Uo as ExtensionDefinition, Us as ExtensionBannerOptions, Va as discoverExtensions, Vc as BadgeActionsMatch, Vo as ExtensionCustomRenderer, Vs as SessionBadgeEntry, Wa as parseExtensionScanPathEnv, Wc as BadgeTargetKind, Wo as ExtensionFooterHint, Ws as ExtensionHud, Xa as createExtensionPromptBridge, Xc as builtinBadgeEffect, Xo as ExtensionMcpConfigTransform, Xs as ExtensionNotifyTone, Ya as ExtensionPromptDelivery, Yc as builtinBadgeActions, Yo as ExtensionManifest, Ys as ExtensionNotifyOptions, Za as ExtensionMcpHost, Zc as extensionBadgeActions, Zo as ExtensionMcpServerInfo, Zs as ExtensionStatusOptions, _a as findReferences, _c as ExtensionConfigChangeBehavior, _d as PanelKeyValueItem, _o as sessionBadges, _s as ExtensionRegistry, aa as composeExtensionPreset, ac as ExtensionSessionsSurface, ao as serializedPayloadBytes, as as ExtensionPaletteProvider, ba as ExtensionInstallCandidate, bc as ExtensionConfigItemBase, bd as PanelRowItem, bo as ArtifactResolveOptions, bs as ExtensionSessionBadge, ca as filterRegistryPlugins, cc as ExtensionWorkspaceSurface, cd as ExtensionPanel, co as branchKind, cs as ExtensionPaletteWhen, da as ReferenceMatch, dc as createWorkspaceSurface, dd as PanelAction, do as githubTokenSource, ds as ExtensionPromptTransform, ec as ExtensionHostRuntimeBridge, el as normalizeBadgeTarget, eo as EXTENSION_BUS_MAX_PAYLOAD_BYTES, es as ExtensionModal, fa as ReferenceProviderWire, fc as ExtensionConfigActionItem, fd as PanelContent, fo as parseShortstat, fs as ExtensionProviderMiddleware, ga as createReferenceResolver, gc as ExtensionConfigCategory, gd as PanelItem, go as resolveArtifactKind, gs as ExtensionReferenceView, ha as compileReferencePattern, hc as ExtensionConfigBooleanItem, hd as PanelIcon, ho as renderArtifactRow, hs as ExtensionReferenceSurface, ia as buildExtensionRegistry, ic as ExtensionSessionSummary, io as createExtensionBus, is as ExtensionPaletteItem, ja as installExtensionFromSpec, jc as clampExtensionConfigNumber, jo as ExtensionBadgeIcon, js as ExtensionToolRendererEntry, ka as InstalledExtensionTargetOptions, kc as WireExtensionConfigItem, ko as ExtensionArtifactRow, ks as ExtensionToolRenderProps, la as mergeContributions, lc as createArtifactsSurface, ld as ExtensionPanelEntry, lo as commitKind, ls as ExtensionPaths, ma as ReferenceResolverOptions, mc as ExtensionConfigActionStatus, md as PanelFocus, mo as refreshArtifact, ms as ExtensionReferenceProvider, na as ExtensionCatalogPartition, nc as ExtensionModelEntry, nl as sessionBadgeTarget, no as ExtensionBusChannel, ns as ExtensionOrderedContribution, oa as disposeExtensionRegistry, oc as ExtensionWorkspaceOps, oo as BUILTIN_ARTIFACT_KINDS, os as ExtensionPaletteProviderEntry, pa as ReferenceResolver, pc as ExtensionConfigActionResult, pd as PanelCustomItem, po as pullRequestKind, ps as ExtensionReferencePattern, qa as discoverAndBuildExtensionRegistry, qc as badgeMenu, qo as ExtensionKeybinding, qs as ExtensionHudWriter, ra as buildExtensionPreset, rc as ExtensionRuntimeSurface, ro as ExtensionBusEnvelope, rs as ExtensionPaletteCommand, sa as filterExtensionRegistry, sc as ExtensionWorkspacePair, so as artifactKindLabel, ss as ExtensionPaletteRunResult, ta as BuildExtensionRegistryOptions, tc as ExtensionHostRuntimeImpl, tl as runBadgeAction, to as ExtensionBus, ts as ExtensionModalNode, ua as partitionExtensionCatalog, uc as createExtensionHostRuntimeBridge, ud as PANEL_CONTENT_LIMITS, uo as fileKind, us as ExtensionPermission, va as referenceProviderWires, vc as ExtensionConfigEnumItem, vd as PanelLoadContext, vo as ArtifactDetectHookOptions, vs as ExtensionRunTransform, wa as ExtensionInstallProvenance, wc as ExtensionConfigStringItem, wd as panelBadgeText, wo as ExtensionArtifactDetection, ws as ExtensionSource, xa as ExtensionInstallError, xc as ExtensionConfigModelItem, xd as PanelSection, xo as EMPTY_EXTENSION_REGISTRY, xs as ExtensionSessionBadgeCardRow, ya as ExtensionFetchResult, yc as ExtensionConfigItem, yd as PanelMarkdownItem, yo as createArtifactDetectHook, ys as ExtensionRuntimeEnv, za as ExtensionScanEntry, zc as BadgeAction, zo as ExtensionContextTransform, zs as LoadedExtension } from "./index-BGe1fQT7.js";
|
|
2
|
+
export { type ArtifactDetectHookOptions, type ArtifactResolveOptions, BUILTIN_ARTIFACT_KINDS, type BadgeAction, type BadgeActionEffect, type BadgeActionsMatch, type BadgeMenuItem, type BadgeTarget, type BadgeTargetKind, type BuildExtensionRegistryOptions, type DiscoverAndBuildExtensionRegistryOptions, EMPTY_EXTENSION_REGISTRY, ENTRY_CANDIDATES, EXTENSION_BUS_MAX_PAYLOAD_BYTES, type ExtensionAgentPluginOptions, type ExtensionApiVersion, type ExtensionArtifactDetection, type ExtensionArtifactKindRegistration, type ExtensionArtifactQuery, type ExtensionArtifactRendererEntry, type ExtensionArtifactRendererRegistration, type ExtensionArtifactRow, type ExtensionArtifactScope, type ExtensionArtifactsSurface, type ExtensionBadgeActions, type ExtensionBadgeIcon, type ExtensionBadgeTone, type ExtensionBannerOptions, type ExtensionBehaviorTransform, type ExtensionBus, type ExtensionBusChannel, type ExtensionBusEmitOptions, type ExtensionBusEnvelope, type ExtensionBusMessage, type ExtensionBusSurface, type ExtensionCapability, type ExtensionCatalogPartition, type ExtensionConfigActionItem, type ExtensionConfigActionResult, type ExtensionConfigActionStatus, type ExtensionConfigBooleanItem, type ExtensionConfigCategory, type ExtensionConfigChangeBehavior, type ExtensionConfigEnumItem, type ExtensionConfigItem, type ExtensionConfigItemBase, type ExtensionConfigModelItem, type ExtensionConfigNumberItem, type ExtensionConfigScope, type ExtensionConfigStringItem, type ExtensionConfigSurface, type ExtensionConfigValue, type ExtensionConfigValueItem, type ExtensionContext, type ExtensionContextTransform, type ExtensionContributions, type ExtensionCustomRenderer, type ExtensionCustomRendererEntry, type ExtensionDefinition, type ExtensionFetchResult, type ExtensionFooterHint, type ExtensionGuiPanel, type ExtensionGuiSurface, type ExtensionHostRuntimeBridge, type ExtensionHostRuntimeImpl, type ExtensionHud, type ExtensionHudState, type ExtensionHudStatus, type ExtensionHudWriter, type ExtensionInstallCandidate, ExtensionInstallError, type ExtensionInstallErrorCode, type ExtensionInstallIo, type ExtensionInstallProvenance, type ExtensionInstallResult, type ExtensionInstallScope, type ExtensionInstallSource, type ExtensionKeybinding, type ExtensionKeybindingDispatchContext, type ExtensionManifest, type ExtensionMcpConfigTransform, type ExtensionMcpHost, type ExtensionMcpServerInfo, type ExtensionMcpSurface, type ExtensionMcpToolResult, type ExtensionModal, type ExtensionModalNode, type ExtensionModelEntry, type ExtensionNotification, type ExtensionNotifyOptions, type ExtensionNotifyTone, type ExtensionOrderedContribution, type ExtensionPaletteCommand, type ExtensionPaletteItem, type ExtensionPaletteProvider, type ExtensionPaletteProviderEntry, type ExtensionPaletteRunResult, type ExtensionPaletteWhen, type ExtensionPanel, type ExtensionPanelEntry, type ExtensionPaths, type ExtensionPermission, type ExtensionPromptBridge, type ExtensionPromptDelivery, type ExtensionPromptTransform, type ExtensionProviderMiddleware, type ExtensionReferencePattern, type ExtensionReferenceProvider, type ExtensionReferenceSurface, type ExtensionReferenceView, type ExtensionRegistry, type ExtensionRunTransform, type ExtensionRuntimeEnv, type ExtensionRuntimeSurface, type ExtensionScanEntry, type ExtensionSessionBadge, type ExtensionSessionBadgeCardRow, type ExtensionSessionStateSurface, type ExtensionSessionSummary, type ExtensionSessionsSurface, type ExtensionSkillPolicy, type ExtensionSource, type ExtensionStatusOptions, type ExtensionStorageScope, type ExtensionStorageSurface, type ExtensionToolCatalogTransform, type ExtensionToolPolicy, type ExtensionToolRenderProps, type ExtensionToolRenderer, type ExtensionToolRendererEntry, type ExtensionToolRendererRegistration, type ExtensionToolResultMetadataRegistration, type ExtensionToolResultPatch, type ExtensionToolResultTransform, type ExtensionToolVerdict, type ExtensionTuiSurface, type ExtensionWorkspaceOps, type ExtensionWorkspacePair, type ExtensionWorkspaceSurface, type GuiContributions, type InstallExtensionFromSpecOptions, type InstalledExtensionTargetOptions, type LoadedExtension, type LoadedPluginRecord, PANEL_CONTENT_LIMITS, type PanelAction, type PanelContent, type PanelCustomItem, type PanelFocus, type PanelIcon, type PanelItem, type PanelKeyValueItem, type PanelLoadContext, type PanelMarkdownItem, type PanelRowItem, type PanelSection, type ReferenceMatch, type ReferenceProviderWire, type ReferenceResolver, type ReferenceResolverOptions, type SessionBadgeEntry, type StoredExtensionConfigItem, type TuiContributions, type WireExtensionConfigItem, type WireExtensionConfigSchema, artifactBadgeTarget, artifactKindLabel, badgeMenu, badgePromptText, branchKind, buildExtensionPreset, buildExtensionRegistry, builtinBadgeActions, builtinBadgeEffect, clampExtensionConfigNumber, coerceExtensionConfigInput, commitKind, compileReferencePattern, composeExtensionPreset, createArtifactDetectHook, createArtifactsSurface, createExtensionBus, createExtensionHostRuntimeBridge, createExtensionHud, createExtensionPromptBridge, createMcpSurface, createReferenceResolver, createWorkspaceSurface, defaultExtensionScanPaths, defineExtension, discoverAndBuildExtensionRegistry, discoverExtensions, disposeExtensionRegistry, extensionBadgeActions, fetchExtensionSource, fileKind, filterExtensionRegistry, filterRegistryPlugins, findReferences, githubTokenSource, inlineLoadedExtension, installExtensionFromSpec, isExtensionDefinition, isValidExtensionConfigItem, isValidExtensionConfigValue, listExtensionCandidates, mergeBadgeMenu, mergeContributions, normalizeBadgeEffect, normalizeBadgeTarget, normalizePanelContent, normalizePanelLoadContext, panelBadgeText, parseExtensionInstallSource, parseExtensionScanPathEnv, parseShortstat, partitionExtensionCatalog, pullRequestKind, readExtensionInstallProvenance, referenceProviderWires, refreshArtifact, registerExtensionHostModules, removeInstalledExtension, renderArtifactRow, resolveArtifactKind, resolveExtensionConfig, resolveExtensionConfigValue, runBadgeAction, sanitizeExtensionConfigActionPatch, scanExtensionDirectories, serializedPayloadBytes, sessionBadgeTarget, sessionBadges, toWireConfigSchema, updateInstalledExtension };
|
package/dist/extensions.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export { BUILTIN_ARTIFACT_KINDS, EMPTY_EXTENSION_REGISTRY, ENTRY_CANDIDATES, EXTENSION_BUS_MAX_PAYLOAD_BYTES, ExtensionInstallError, PANEL_CONTENT_LIMITS, artifactKindLabel, branchKind, buildExtensionPreset, buildExtensionRegistry, clampExtensionConfigNumber, coerceExtensionConfigInput, commitKind, composeExtensionPreset, createArtifactDetectHook, createArtifactsSurface, createExtensionBus, createExtensionHostRuntimeBridge, createExtensionHud, createExtensionPromptBridge, createWorkspaceSurface, defaultExtensionScanPaths, defineExtension, discoverAndBuildExtensionRegistry, discoverExtensions, disposeExtensionRegistry, fetchExtensionSource, fileKind, filterExtensionRegistry, filterRegistryPlugins, githubTokenSource, inlineLoadedExtension, installExtensionFromSpec, isExtensionDefinition, isValidExtensionConfigItem, isValidExtensionConfigValue, listExtensionCandidates, mergeContributions, normalizePanelContent, panelBadgeText, parseExtensionInstallSource, parseExtensionScanPathEnv, parseShortstat, partitionExtensionCatalog, pullRequestKind, readExtensionInstallProvenance, refreshArtifact, registerExtensionHostModules, removeInstalledExtension, renderArtifactRow, resolveArtifactKind, resolveExtensionConfig, resolveExtensionConfigValue, sanitizeExtensionConfigActionPatch, scanExtensionDirectories, serializedPayloadBytes, toWireConfigSchema, updateInstalledExtension };
|
|
1
|
+
import { An as toWireConfigSchema, Bn as parseShortstat, Cn as clampExtensionConfigNumber, Ct as createMcpSurface, Dn as resolveExtensionConfig, En as isValidExtensionConfigValue, Et as createWorkspaceSurface, Fn as artifactKindLabel, Gn as sessionBadges, Hn as refreshArtifact, In as branchKind, Kn as createArtifactDetectHook, Ln as commitKind, Mn as createExtensionBus, Nn as serializedPayloadBytes, On as resolveExtensionConfigValue, Pn as BUILTIN_ARTIFACT_KINDS, Rn as fileKind, Sn as defineExtension, St as partitionExtensionCatalog, Tn as isValidExtensionConfigItem, Tt as createExtensionHostRuntimeBridge, Un as renderArtifactRow, Vn as pullRequestKind, Wn as resolveArtifactKind, _n as isExtensionDefinition, _t as composeExtensionPreset, at as ExtensionInstallError, bt as filterRegistryPlugins, ct as listExtensionCandidates, dt as removeInstalledExtension, fn as registerExtensionHostModules, ft as updateInstalledExtension, gn as inlineLoadedExtension, gt as buildExtensionRegistry, hn as discoverExtensions, ht as buildExtensionPreset, it as createExtensionPromptBridge, jn as EXTENSION_BUS_MAX_PAYLOAD_BYTES, kn as sanitizeExtensionConfigActionPatch, lt as parseExtensionInstallSource, mn as defaultExtensionScanPaths, mt as discoverAndBuildExtensionRegistry, ot as fetchExtensionSource, pn as ENTRY_CANDIDATES, pt as createExtensionHud, rt as EMPTY_EXTENSION_REGISTRY, st as installExtensionFromSpec, ut as readExtensionInstallProvenance, vn as parseExtensionScanPathEnv, vt as disposeExtensionRegistry, wn as coerceExtensionConfigInput, wt as createArtifactsSurface, xt as mergeContributions, yn as scanExtensionDirectories, yt as filterExtensionRegistry, zn as githubTokenSource } from "./agent-DSLNmBp0.js";
|
|
2
|
+
import { C as normalizeBadgeTarget, S as normalizeBadgeEffect, T as sessionBadgeTarget, _ as badgePromptText, a as normalizePanelLoadContext, b as extensionBadgeActions, c as createReferenceResolver, g as badgeMenu, h as artifactBadgeTarget, i as normalizePanelContent, l as findReferences, o as panelBadgeText, r as PANEL_CONTENT_LIMITS, s as compileReferencePattern, u as referenceProviderWires, v as builtinBadgeActions, w as runBadgeAction, x as mergeBadgeMenu, y as builtinBadgeEffect } from "./context-segments-BHKlagL6.js";
|
|
3
|
+
export { BUILTIN_ARTIFACT_KINDS, EMPTY_EXTENSION_REGISTRY, ENTRY_CANDIDATES, EXTENSION_BUS_MAX_PAYLOAD_BYTES, ExtensionInstallError, PANEL_CONTENT_LIMITS, artifactBadgeTarget, artifactKindLabel, badgeMenu, badgePromptText, branchKind, buildExtensionPreset, buildExtensionRegistry, builtinBadgeActions, builtinBadgeEffect, clampExtensionConfigNumber, coerceExtensionConfigInput, commitKind, compileReferencePattern, composeExtensionPreset, createArtifactDetectHook, createArtifactsSurface, createExtensionBus, createExtensionHostRuntimeBridge, createExtensionHud, createExtensionPromptBridge, createMcpSurface, createReferenceResolver, createWorkspaceSurface, defaultExtensionScanPaths, defineExtension, discoverAndBuildExtensionRegistry, discoverExtensions, disposeExtensionRegistry, extensionBadgeActions, fetchExtensionSource, fileKind, filterExtensionRegistry, filterRegistryPlugins, findReferences, githubTokenSource, inlineLoadedExtension, installExtensionFromSpec, isExtensionDefinition, isValidExtensionConfigItem, isValidExtensionConfigValue, listExtensionCandidates, mergeBadgeMenu, mergeContributions, normalizeBadgeEffect, normalizeBadgeTarget, normalizePanelContent, normalizePanelLoadContext, panelBadgeText, parseExtensionInstallSource, parseExtensionScanPathEnv, parseShortstat, partitionExtensionCatalog, pullRequestKind, readExtensionInstallProvenance, referenceProviderWires, refreshArtifact, registerExtensionHostModules, removeInstalledExtension, renderArtifactRow, resolveArtifactKind, resolveExtensionConfig, resolveExtensionConfigValue, runBadgeAction, sanitizeExtensionConfigActionPatch, scanExtensionDirectories, serializedPayloadBytes, sessionBadgeTarget, sessionBadges, toWireConfigSchema, updateInstalledExtension };
|
|
@@ -144,8 +144,8 @@ function stripHtmlBlock(html) {
|
|
|
144
144
|
* truncated with an explicit marker. The pinned reader also stops
|
|
145
145
|
* buffering past 2× the cap so a multi-MB body can't OOM us.
|
|
146
146
|
*/
|
|
147
|
-
const DEFAULT_MAX_BYTES =
|
|
148
|
-
const HARD_MAX_BYTES =
|
|
147
|
+
const DEFAULT_MAX_BYTES = 204800;
|
|
148
|
+
const HARD_MAX_BYTES = 1048576;
|
|
149
149
|
const HTTP_TIMEOUT_MS = 15e3;
|
|
150
150
|
const MAX_REDIRECTS = 5;
|
|
151
151
|
/**
|
|
@@ -412,7 +412,7 @@ async function fetchWithSsrfGuard(startUrl, headers, signal, maxBufferBytes, dep
|
|
|
412
412
|
* caller gates on status before writing.
|
|
413
413
|
*/
|
|
414
414
|
const FETCH_CACHE_MAX_ENTRIES = 64;
|
|
415
|
-
const FETCH_CACHE_MAX_BYTES =
|
|
415
|
+
const FETCH_CACHE_MAX_BYTES = 16777216;
|
|
416
416
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
417
417
|
let fetchCacheBytes = 0;
|
|
418
418
|
function cacheKey(url, cap, allowHosts) {
|
|
@@ -532,4 +532,4 @@ const fetchUrl = {
|
|
|
532
532
|
//#endregion
|
|
533
533
|
export { isBlockedAddress as a, stripHtmlInline as c, fetchWithSsrfGuard as i, clearFetchUrlCache as n, isHostAllowed as o, fetchUrl as r, pinnedRequest as s, __fetchCacheTestApi as t };
|
|
534
534
|
|
|
535
|
-
//# sourceMappingURL=fetch-url-
|
|
535
|
+
//# sourceMappingURL=fetch-url-Ct2TjDWt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-url-DX75gpL0.js","names":["dns","request","httpsRequest","httpRequest"],"sources":["../src/tools/_html.ts","../src/tools/fetch-url.ts"],"sourcesContent":["/**\n * Shared HTML → plain-text helpers used by the web-egress tools (`web_search`\n * snippet extraction, `fetch_url` body decoding).\n *\n * Best-effort by contract: a real HTML parser would be more correct but the\n * payload is always model context, never user-facing rendered output, so a\n * tag-stripper plus a curated entity table covers the practical cases\n * (Wikipedia, GitHub READMEs, MDN docs, Stack Overflow answers). When a tool\n * needs richer parsing it should reach for an actual parser rather than\n * extending these helpers ad-hoc.\n */\n\n/**\n * Curated named-entity table. Kept narrow on purpose — the long tail\n * (`Å`, `×`, mathematical refs, …) is out of scope because the\n * model handles unknown entities fine when they pass through verbatim. Add\n * entries when an entity is observed mangling real output, not speculatively.\n *\n * Keys are stored without the leading `&` / trailing `;` and matched\n * case-insensitively by the decoder.\n */\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n apos: '\\'',\n bull: '•',\n copy: '\\u00A9',\n ensp: ' ',\n emsp: ' ',\n gt: '>',\n hellip: '\\u2026',\n laquo: '\\u00AB',\n ldquo: '\\u201C',\n lsquo: '\\u2018',\n lt: '<',\n mdash: '\\u2014',\n middot: '\\u00B7',\n nbsp: ' ',\n ndash: '\\u2013',\n quot: '\"',\n raquo: '\\u00BB',\n rdquo: '\\u201D',\n reg: '\\u00AE',\n rsquo: '\\u2019',\n thinsp: ' ',\n trade: '\\u2122',\n}\n\n/**\n * `String.fromCodePoint` raises on out-of-range / non-finite inputs; the\n * agent's untrusted-payload assumption means we can't let a single broken\n * numeric reference crash the whole decode. Empty string on rejection is\n * the safest outcome (matches what most browsers do for invalid refs).\n */\nfunction safeFromCodePoint(cp: number): string {\n if (!Number.isFinite(cp) || cp < 0 || cp > 0x10FFFF)\n return ''\n try {\n return String.fromCodePoint(cp)\n }\n catch {\n return ''\n }\n}\n\n/**\n * Decode numeric character references (`&#x...;` / `&#NNN;`) and the curated\n * subset of named entities in {@link NAMED_ENTITIES}. Unknown named entities\n * are left as-is so the model can detect them rather than seeing a silently\n * corrupted token.\n *\n * `&` is decoded LAST so a double-escaped string like `&lt;` resolves\n * to `<` (not `<`) — that's the spec-correct single-pass decode shape.\n */\nexport function decodeHtmlEntities(text: string): string {\n let s = text\n .replace(/&#x([0-9a-f]+);/gi, (_, h) => safeFromCodePoint(Number.parseInt(h, 16)))\n .replace(/&#(\\d+);/g, (_, n) => safeFromCodePoint(Number.parseInt(n, 10)))\n s = s.replace(/&([a-z][a-z0-9]*);/gi, (full, name) => {\n const key = name.toLowerCase()\n // Defer `amp` so `&lt;` doesn't double-decode in a single pass.\n if (key === 'amp')\n return full\n const replacement = NAMED_ENTITIES[key]\n return replacement ?? full\n })\n return s.replace(/&/gi, '&')\n}\n\n/**\n * Strip `<script>`, `<style>`, `<noscript>`, comments, and every other tag.\n * Block-level openers (`<br>`, `<p>`, `<div>`, `<li>`, `<tr>`, `<h1-6>`) are\n * replaced with newlines so the resulting plaintext preserves readable\n * structure. Other tags collapse to spaces.\n */\nexport function stripHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ')\n .replace(/<(?:br|p|div|li|tr|h[1-6])[^>]*>/gi, '\\n')\n .replace(/<[^>]+>/g, ' ')\n}\n\n/**\n * Inline variant — strip tags + decode entities + collapse all whitespace\n * (including line breaks) into single spaces. For short snippets where\n * structural breaks would be visual noise (search-result titles, link text).\n */\nexport function stripHtmlInline(html: string): string {\n return decodeHtmlEntities(stripHtml(html))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * Block variant — strip tags + decode entities, preserve newlines emitted\n * by block-level openers, then collapse inline whitespace per-line and drop\n * empty lines. For full-page reductions where structure aids reading.\n */\nexport function stripHtmlBlock(html: string): string {\n return decodeHtmlEntities(stripHtml(html))\n .split('\\n')\n .map(line => line.replace(/[ \\t]+/g, ' ').trim())\n .filter(line => line.length > 0)\n .join('\\n')\n}\n","import type { RequestOptions as HttpRequestOptions } from 'node:http'\nimport type { RequestOptions as HttpsRequestOptions } from 'node:https'\nimport type { ToolContext, ToolDef } from './types'\nimport { Buffer } from 'node:buffer'\nimport { promises as dns } from 'node:dns'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport { isIP } from 'node:net'\nimport { errorMessage } from '../errors'\nimport { stripHtmlBlock } from './_html'\nimport { toolPrompts } from './prompts'\n\n/**\n * Fetch a URL and return its text content.\n *\n * Companion to `web_search` (when registered) — after the model finds a\n * promising result it fetches the page to read the body in full. HTML is\n * reduced to plain text because the model rarely benefits from the markup\n * and we want to keep the payload inside the per-turn output budget.\n *\n * Restrictions:\n * - Only http(s) URLs. Anything else (file://, data:, ftp:) is rejected so\n * the tool can't be coerced into reading local files or arbitrary\n * resources.\n * - SSRF guard with TOCTOU defense: the host is DNS-resolved against the\n * loopback / link-local / private / reserved blocklist BEFORE the\n * request, and the resolved IP is then PINNED at the connection layer\n * via `node:http(s).request({ host: <resolved IP>, headers: { Host: ... },\n * servername: ... })`. This closes the classic DNS-rebinding gap where a\n * `globalThis.fetch` re-resolves the hostname and connects to a freshly\n * minted private IP. TLS verification still runs against the original\n * hostname via the explicit `servername` option.\n *\n * Note: the more idiomatic Node fix (an `undici.Agent` with a custom\n * `connect.lookup`) is silently a no-op on Bun ≤1.3 — both\n * `undici.Agent` and `node:https.Agent.lookup` are stubbed out. The\n * explicit `host: IP` + `Host:` header dance is the only mechanism that\n * actually pins on the current Bun runtime.\n * - Redirects are followed manually (up to `MAX_REDIRECTS`) and each hop\n * is re-validated, so a public hostname that 302s into a metadata\n * endpoint is rejected at the redirect step.\n * - 15s hard timeout per request.\n * - Output capped at `max_bytes` (default 200 KiB) — long pages are\n * truncated with an explicit marker. The pinned reader also stops\n * buffering past 2× the cap so a multi-MB body can't OOM us.\n */\n\nconst DEFAULT_MAX_BYTES = 200 * 1024\nconst HARD_MAX_BYTES = 1024 * 1024\nconst HTTP_TIMEOUT_MS = 15_000\nconst MAX_REDIRECTS = 5\n\n/**\n * IPv4 ranges that the SSRF guard refuses to fetch from. Covers the cloud\n * metadata services (AWS/Azure 169.254.169.254, GCP routes through the same\n * link-local block) plus every RFC1918 / loopback / reserved block a model\n * could be prompt-injected into hitting.\n */\nfunction isBlockedIPv4(ip: string): boolean {\n const parts = ip.split('.').map(Number)\n if (parts.length !== 4 || parts.some(p => !Number.isInteger(p) || p < 0 || p > 255))\n return true\n const [a, b] = parts as [number, number, number, number]\n if (a === 0)\n return true // 0.0.0.0/8 unspecified / \"this network\"\n if (a === 10)\n return true // 10/8 private\n if (a === 127)\n return true // loopback\n if (a === 169 && b === 254)\n return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31)\n return true // 172.16/12 private\n if (a === 192 && b === 168)\n return true // 192.168/16 private\n if (a === 192 && b === 0 && parts[2] === 0)\n return true // 192.0.0/24 IETF\n if (a === 198 && (b === 18 || b === 19))\n return true // 198.18/15 benchmark\n if (a >= 224)\n return true // multicast + reserved (224/4, 240/4)\n return false\n}\n\nfunction isBlockedIPv6(ip: string): boolean {\n const lower = ip.toLowerCase()\n // Normalise IPv4-mapped (::ffff:1.2.3.4) — recurse on the v4 tail. URL\n // parsing tends to canonicalise the dotted form into the hex pair\n // `::ffff:a9fe:a9fe`, so accept both shapes.\n const v4MappedDotted = lower.match(/^::ffff:([0-9.]+)$/)\n if (v4MappedDotted)\n return isBlockedIPv4(v4MappedDotted[1]!)\n const v4MappedHex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/)\n if (v4MappedHex) {\n const hi = Number.parseInt(v4MappedHex[1]!, 16)\n const lo = Number.parseInt(v4MappedHex[2]!, 16)\n const ipv4 = `${(hi >> 8) & 0xFF}.${hi & 0xFF}.${(lo >> 8) & 0xFF}.${lo & 0xFF}`\n return isBlockedIPv4(ipv4)\n }\n if (lower === '::' || lower === '::1')\n return true\n // fc00::/7 unique-local (covers fc.. and fd..)\n if (/^f[cd][0-9a-f]{0,2}:/.test(lower))\n return true\n // fe80::/10 link-local\n if (/^fe[89ab][0-9a-f]?:/.test(lower))\n return true\n // ff00::/8 multicast\n if (lower.startsWith('ff'))\n return true\n return false\n}\n\nexport function isBlockedAddress(ip: string): boolean {\n const family = isIP(ip)\n if (family === 4)\n return isBlockedIPv4(ip)\n if (family === 6)\n return isBlockedIPv6(ip)\n return true // not a recognised IP literal — refuse\n}\n\n/**\n * Normalize a hostname for allowlist comparison: lowercase, strip a trailing\n * dot (FQDN root) and IPv6 brackets, drop a leading dot on allowlist entries.\n */\nfunction normalizeHost(host: string): string {\n let h = host.trim().toLowerCase()\n if (h.startsWith('[') && h.endsWith(']'))\n h = h.slice(1, -1)\n if (h.endsWith('.'))\n h = h.slice(0, -1)\n if (h.startsWith('.'))\n h = h.slice(1)\n return h\n}\n\n/**\n * Site identity for cross-host redirect detection: the lowercased hostname\n * with a leading `www.` stripped, so `example.com` ⇄ `www.example.com` and a\n * path-only redirect aren't flagged as cross-host. Returns `null` for an\n * unparseable URL (caller skips the note rather than guessing).\n */\nfunction sameSiteHost(url: string): string | null {\n try {\n return new URL(url).hostname.toLowerCase().replace(/^www\\./, '')\n }\n catch {\n return null\n }\n}\n\n/**\n * Host-suffix allowlist check. An empty / undefined list means \"no allowlist\"\n * → every host passes (the SSRF blocklist is the only gate). Otherwise the\n * host must equal, or be a subdomain of, one of the entries. Subdomain match\n * is on a dot boundary so `example.com` matches `docs.example.com` but not\n * `notexample.com`.\n */\nexport function isHostAllowed(hostname: string, allowHosts: readonly string[] | undefined): boolean {\n if (!allowHosts || allowHosts.length === 0)\n return true\n const host = normalizeHost(hostname)\n for (const entry of allowHosts) {\n const allowed = normalizeHost(entry)\n if (allowed.length === 0)\n continue\n if (host === allowed || host.endsWith(`.${allowed}`))\n return true\n }\n return false\n}\n\n/**\n * Resolve `hostname` and refuse if any answer falls in a blocked range.\n *\n * Returns the first allowed `{ address, family }` so the caller can pin\n * the connection to it. Throws on any blocked answer so a multi-record\n * response with one private record fails closed.\n */\nasync function resolveAndCheck(hostname: string, allowHosts?: readonly string[]): Promise<{ address: string, family: 4 | 6 }> {\n // Egress allowlist (when configured) gates BEFORE DNS so a non-approved\n // host never even triggers a lookup. Enforced here so every redirect hop\n // is re-checked, not just the initial URL.\n if (!isHostAllowed(hostname, allowHosts))\n throw new Error(`refused: ${hostname} is not in the configured egress allowlist`)\n // `URL.hostname` keeps brackets around IPv6 literals (`[::1]`) — strip them\n // before the IP check so `isIP` recognises the literal and `dns.lookup`\n // doesn't try to resolve `[::1]` as a name.\n const bare = hostname.startsWith('[') && hostname.endsWith(']')\n ? hostname.slice(1, -1)\n : hostname\n const family = isIP(bare)\n if (family === 4 || family === 6) {\n if (isBlockedAddress(bare))\n throw new Error(`refused: ${bare} is in a blocked range`)\n return { address: bare, family }\n }\n const answers = await dns.lookup(bare, { all: true, verbatim: true })\n if (answers.length === 0)\n throw new Error(`DNS lookup returned no records for ${bare}`)\n for (const { address } of answers) {\n if (isBlockedAddress(address))\n throw new Error(`refused: ${bare} resolves to blocked address ${address}`)\n }\n const first = answers[0]!\n const fam = first.family === 6 ? 6 : 4\n return { address: first.address, family: fam }\n}\n\n/**\n * Pinned HTTP/HTTPS request. Connects to `pinnedIp` directly (bypassing\n * Node/Bun's own DNS resolution) while preserving the `Host:` header and\n * (for HTTPS) TLS SNI on the URL's original hostname. This is the\n * concrete defense against DNS rebinding — see the file-level docstring.\n *\n * Reads up to `maxBufferBytes` of body and then forcibly closes the\n * connection. The body is decoded as UTF-8; binary responses degrade to\n * mojibake but the tool's contract is text payloads anyway.\n */\ninterface PinnedResponse {\n status: number\n url: string\n contentType: string\n body: string\n location: string | null\n /**\n * True when the body was cut at `maxBufferBytes` before the server\n * finished sending. Optional so test seams that stub `requestImpl`\n * don't have to populate it; absent means \"not truncated\".\n */\n truncated?: boolean\n}\n\nfunction buildHostHeader(url: URL): string {\n if (!url.port)\n return url.hostname\n const defaultPort = url.protocol === 'https:' ? '443' : '80'\n return url.port === defaultPort ? url.hostname : `${url.hostname}:${url.port}`\n}\n\nexport function pinnedRequest(\n url: URL,\n pinnedIp: string,\n family: 4 | 6,\n headers: Record<string, string>,\n signal: AbortSignal,\n maxBufferBytes: number,\n): Promise<PinnedResponse> {\n return new Promise((resolve, reject) => {\n const isHttps = url.protocol === 'https:'\n const port = url.port\n ? Number.parseInt(url.port, 10)\n : (isHttps ? 443 : 80)\n\n // `host` is the literal IP, `servername` is the original hostname so\n // TLS cert verification still matches the cert's SAN/CN against the\n // user-visible name. Node uses `servername` for `checkServerIdentity`\n // automatically when set.\n const options: HttpRequestOptions & HttpsRequestOptions = {\n host: pinnedIp,\n port,\n path: `${url.pathname}${url.search}`,\n method: 'GET',\n family,\n headers: { ...headers, Host: buildHostHeader(url) },\n ...(isHttps ? { servername: url.hostname } : {}),\n }\n\n const request = isHttps ? httpsRequest : httpRequest\n const chunks: Buffer[] = []\n let received = 0\n // Single-shot settle guard. Bun's `node:http` is quirky around\n // `req.destroy(err)` — passing an error doesn't reliably surface as an\n // `error` event the way it does on Node. We track the settle state\n // ourselves and call `resolve` / `reject` directly from the abort /\n // timeout paths so the promise terminates even when the underlying\n // socket teardown is silent.\n let settled = false\n // Forward-declared so the `finish` / abort / timeout handlers can\n // close over `req` before it's assigned below. The request object is\n // wired into them via mutable refs because the handlers must be set\n // up *before* `request(options, callback)` fires its first event.\n let req: ReturnType<typeof httpRequest> | undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const teardown = (): void => {\n try { req?.destroy() }\n catch { /* tear-down best effort */ }\n }\n\n const finish = (settler: () => void): void => {\n if (settled)\n return\n settled = true\n if (timer)\n clearTimeout(timer)\n signal.removeEventListener('abort', onSignalAbort)\n settler()\n }\n\n function onSignalAbort(): void {\n finish(() => reject(new Error('request aborted')))\n teardown()\n }\n\n // Pre-aborted signal — short-circuit so we don't even open a socket.\n // (This is the realistic shape when a user hits Ctrl+C while the agent\n // is already shutting down: the signal is `.aborted` by the time the\n // tool function runs.)\n if (signal.aborted) {\n reject(new Error('request aborted'))\n return\n }\n\n timer = setTimeout(() => {\n finish(() => reject(new Error(`request timed out after ${HTTP_TIMEOUT_MS}ms`)))\n teardown()\n }, HTTP_TIMEOUT_MS)\n\n signal.addEventListener('abort', onSignalAbort, { once: true })\n\n req = request(options, (res) => {\n const status = res.statusCode ?? 0\n const headersOut = res.headers\n const contentType = (headersOut['content-type'] ?? '').toString().toLowerCase()\n const location = typeof headersOut.location === 'string' ? headersOut.location : null\n\n // Short-circuit on redirect responses — body is irrelevant.\n if (status >= 300 && status < 400 && location) {\n res.resume() // drain so the socket can be released\n finish(() => resolve({ status, url: url.toString(), contentType, body: '', location }))\n return\n }\n\n let bodyTruncated = false\n res.on('data', (chunk: Buffer) => {\n if (received >= maxBufferBytes) {\n // Stop accumulating; tear the socket down so the server stops\n // sending. We still resolve cleanly from `end` (or `close` if\n // the server doesn't finish flushing before the kill lands).\n bodyTruncated = true\n teardown()\n return\n }\n // Keep only what fits in the budget — a chunk straddling the cap\n // would otherwise overshoot it by up to one chunk. A partial slice\n // means the body was cut, so flag it and stop the transfer.\n const room = maxBufferBytes - received\n if (chunk.length > room) {\n chunks.push(chunk.subarray(0, room))\n received += room\n bodyTruncated = true\n teardown()\n return\n }\n received += chunk.length\n chunks.push(chunk)\n })\n res.on('end', () => {\n const body = Buffer.concat(chunks).toString('utf-8')\n finish(() => resolve({ status, url: url.toString(), contentType, body, location: null, truncated: bodyTruncated }))\n })\n res.on('close', () => {\n // Socket torn down before `end` (e.g. when our maxBufferBytes\n // trigger destroyed the request). Resolve with whatever we\n // buffered so far — `truncated` tells the caller the body is cut.\n if (!settled) {\n const body = Buffer.concat(chunks).toString('utf-8')\n finish(() => resolve({ status, url: url.toString(), contentType, body, location: null, truncated: bodyTruncated }))\n }\n })\n res.on('error', err => finish(() => reject(err)))\n })\n\n req.on('error', (err: Error) => finish(() => reject(err)))\n req.end()\n })\n}\n\n/**\n * Walk up to {@link MAX_REDIRECTS} hops, validating the target host of each\n * 3xx response against the SSRF blocklist before following.\n *\n * `deps` is an internal seam for tests — production callers use the\n * defaults. Both knobs are intentionally opaque: hosts that want to\n * customize the blocklist should use a higher-level mechanism (a future\n * `behavior.fetchUrlAllowHosts` setting) rather than reaching into this.\n */\nexport interface FetchSsrfDeps {\n /** Resolve + validate a hostname. Defaults to {@link resolveAndCheck}. */\n resolver?: (hostname: string) => Promise<{ address: string, family: 4 | 6 }>\n /** Execute a single pinned HTTP request. Defaults to {@link pinnedRequest}. */\n requestImpl?: typeof pinnedRequest\n}\n\nexport async function fetchWithSsrfGuard(\n startUrl: URL,\n headers: Record<string, string>,\n signal: AbortSignal,\n maxBufferBytes: number,\n deps: FetchSsrfDeps = {},\n allowHosts?: readonly string[],\n): Promise<PinnedResponse> {\n // Default resolver binds the egress allowlist; a test-injected resolver\n // owns its own policy.\n const resolver = deps.resolver ?? ((hostname: string) => resolveAndCheck(hostname, allowHosts))\n const requestImpl = deps.requestImpl ?? pinnedRequest\n let current = startUrl\n for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n const { address, family } = await resolver(current.hostname)\n const res = await requestImpl(current, address, family, headers, signal, maxBufferBytes)\n if (!res.location)\n return res\n if (hop === MAX_REDIRECTS)\n throw new Error(`too many redirects (>${MAX_REDIRECTS})`)\n let next: URL\n try {\n next = new URL(res.location, current)\n }\n catch {\n throw new Error(`invalid redirect target: ${res.location}`)\n }\n if (next.protocol !== 'http:' && next.protocol !== 'https:')\n throw new Error(`refused redirect to non-http(s) target: ${next.protocol}`)\n current = next\n }\n throw new Error('redirect loop exited unexpectedly')\n}\n\n/**\n * Opt-in, process-local response cache for `fetch_url`. Disabled unless\n * `behavior.fetchUrlCacheTtlMs` is set (> 0). Bounded by entry count and total\n * cached bytes; eviction is oldest-first (insertion order via `Map`, which is\n * a good-enough LRU because we delete-then-set on hit to refresh recency).\n *\n * Keyed on `<url>\\u0000<cap>\\u0000<egress-policy>` so two calls with different\n * `max_bytes` (hence different truncation) don't alias, and — because the\n * cache is module-global — an agent with a permissive `fetchUrlAllowHosts`\n * can't populate entries that a co-resident agent with a stricter allowlist\n * would then be served. Only successful text responses are stored — the\n * caller gates on status before writing.\n */\nconst FETCH_CACHE_MAX_ENTRIES = 64\nconst FETCH_CACHE_MAX_BYTES = 16 * 1024 * 1024 // 16 MiB total\n\ninterface FetchCacheEntry {\n /** Fully-formatted tool output (header + body) ready to return verbatim. */\n output: string\n expiresAt: number\n bytes: number\n}\n\nconst fetchCache = new Map<string, FetchCacheEntry>()\nlet fetchCacheBytes = 0\n\nfunction cacheKey(url: string, cap: number, allowHosts?: readonly string[]): string {\n // Normalize + sort so equivalent policies (`['A.com', 'b.com']` vs\n // `['b.com', 'a.com.']`) share entries; an absent/empty allowlist keys\n // as the empty policy.\n const policy = (allowHosts ?? []).map(normalizeHost).sort().join(',')\n return `${url}\\u0000${cap}\\u0000${policy}`\n}\n\nfunction cacheGet(key: string): string | null {\n const entry = fetchCache.get(key)\n if (!entry)\n return null\n if (Date.now() >= entry.expiresAt) {\n fetchCache.delete(key)\n fetchCacheBytes -= entry.bytes\n return null\n }\n // Refresh recency: re-insert at the tail of the Map's iteration order.\n fetchCache.delete(key)\n fetchCache.set(key, entry)\n return entry.output\n}\n\nfunction cacheSet(key: string, output: string, ttlMs: number): void {\n const bytes = Buffer.byteLength(output, 'utf-8')\n // A single oversize payload that can't fit under the byte cap is simply not\n // cached (still returned to the caller) rather than evicting everything.\n if (bytes > FETCH_CACHE_MAX_BYTES)\n return\n // Drop any stale entry for this key first so the byte accounting stays exact.\n const prev = fetchCache.get(key)\n if (prev) {\n fetchCache.delete(key)\n fetchCacheBytes -= prev.bytes\n }\n fetchCache.set(key, { output, expiresAt: Date.now() + ttlMs, bytes })\n fetchCacheBytes += bytes\n // Evict oldest-first until both caps are satisfied.\n while (fetchCache.size > FETCH_CACHE_MAX_ENTRIES || fetchCacheBytes > FETCH_CACHE_MAX_BYTES) {\n const oldest = fetchCache.keys().next().value\n if (oldest === undefined)\n break\n const victim = fetchCache.get(oldest)!\n fetchCache.delete(oldest)\n fetchCacheBytes -= victim.bytes\n }\n}\n\n/** Test seam — drop all cached entries. */\nexport function clearFetchUrlCache(): void {\n fetchCache.clear()\n fetchCacheBytes = 0\n}\n\n/**\n * Test seam — exercise the cache get/set/eviction in isolation without\n * standing up the full SSRF + HTTP path. Not part of the public tool surface;\n * `execute` uses the module-private functions directly.\n */\nexport const __fetchCacheTestApi = {\n key: cacheKey,\n get: cacheGet,\n set: cacheSet,\n size: () => fetchCache.size,\n bytes: () => fetchCacheBytes,\n}\n\nexport const fetchUrl: ToolDef = {\n isConcurrencySafe: true,\n spec: {\n name: 'fetch_url',\n description: toolPrompts.fetch_url,\n inputSchema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'HTTP or HTTPS URL to fetch.',\n },\n max_bytes: {\n type: 'number',\n description: `Truncate the response body beyond this many bytes. Default: ${DEFAULT_MAX_BYTES}. Hard cap: ${HARD_MAX_BYTES}.`,\n },\n },\n required: ['url'],\n },\n },\n async execute({ url, max_bytes }, ctx: ToolContext) {\n const raw = typeof url === 'string' ? url.trim() : ''\n if (!raw)\n return 'fetch_url error: `url` is required'\n let parsed: URL\n try {\n parsed = new URL(raw)\n }\n catch {\n return `fetch_url error: invalid URL: ${raw}`\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')\n return `fetch_url error: only http(s) URLs are allowed (got ${parsed.protocol})`\n\n const cap = Math.min(\n typeof max_bytes === 'number' && max_bytes > 0 ? Math.floor(max_bytes) : DEFAULT_MAX_BYTES,\n HARD_MAX_BYTES,\n )\n // Buffer up to 2× the cap so HTML overhead doesn't starve the visible\n // text budget — a 200 KiB cap on rendered text can legitimately need to\n // read ~400 KiB of raw HTML before stripping. Hard ceiling is still\n // governed by `HARD_MAX_BYTES * 2` which is comfortably below OOM.\n const bufferCap = Math.min(cap * 2, HARD_MAX_BYTES * 2)\n\n // Opt-in response cache (off unless `behavior.fetchUrlCacheTtlMs > 0`).\n // The egress allowlist is part of the key AND re-checked before serving\n // a hit — the cache is module-global, so neither a policy change within\n // an agent nor a co-resident agent's entries may bypass this agent's\n // allowlist.\n const allowHosts = ctx.behavior?.fetchUrlAllowHosts\n const ttlMs = ctx.behavior?.fetchUrlCacheTtlMs\n const cacheEnabled = typeof ttlMs === 'number' && ttlMs > 0\n const key = cacheKey(parsed.toString(), cap, allowHosts)\n if (cacheEnabled && isHostAllowed(parsed.hostname, allowHosts)) {\n const hit = cacheGet(key)\n if (hit !== null)\n return hit\n }\n\n try {\n const res = await fetchWithSsrfGuard(\n parsed,\n {\n 'user-agent': 'Mozilla/5.0 (compatible; zidane/1.0)',\n 'accept': 'text/html,text/plain,application/json;q=0.9,*/*;q=0.5',\n 'accept-encoding': 'identity', // no gzip — we don't decompress\n },\n ctx.signal,\n bufferCap,\n {},\n allowHosts,\n )\n const body = res.body\n const isHtml = res.contentType.includes('html') || /^\\s*<!doctype html|^\\s*<html/i.test(body)\n const text = isHtml ? stripHtmlBlock(body) : body\n const truncated = text.length > cap\n // Two distinct truncation reasons: the rendered text overflowing `cap`,\n // and the raw transfer being cut at the buffer ceiling (`res.truncated`)\n // — the latter can leave the rendered text under `cap` (HTML overhead\n // stripped away), so it needs its own marker or the model would treat\n // a partial body as complete.\n const out = truncated\n ? `${text.slice(0, cap)}\\n\\n[truncated at ${cap} bytes; full length ${text.length}]`\n : res.truncated\n ? `${text}\\n\\n[response truncated: body exceeded the ${bufferCap}-byte transfer buffer]`\n : text\n // `res.url` reflects the FINAL hop after redirects; flag a cross-host\n // landing so the model knows the content came from a different origin\n // than it asked for (open-redirect awareness). Same-host path changes\n // and www add/strip are treated as the same site and not flagged.\n const finalHost = sameSiteHost(res.url)\n const requestedHost = sameSiteHost(parsed.toString())\n const redirectNote = finalHost && requestedHost && finalHost !== requestedHost\n ? `Note: ${parsed.hostname} redirected to a different host (${new URL(res.url).hostname}).\\n`\n : ''\n const header = `URL: ${res.url}\\nStatus: ${res.status}\\nContent-Type: ${res.contentType || '(none)'}\\n${redirectNote}\\n`\n const formatted = header + out\n // Cache only successful, complete text responses — never errors /\n // redirects / transfer-truncated bodies (a retry may get the rest).\n if (cacheEnabled && res.status >= 200 && res.status < 300 && res.truncated !== true)\n cacheSet(key, formatted, ttlMs)\n return formatted\n }\n catch (err) {\n return `fetch_url error: ${errorMessage(err)}`\n }\n },\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fetch-url-Ct2TjDWt.js","names":["dns","request","httpsRequest","httpRequest"],"sources":["../src/tools/_html.ts","../src/tools/fetch-url.ts"],"sourcesContent":["/**\n * Shared HTML → plain-text helpers used by the web-egress tools (`web_search`\n * snippet extraction, `fetch_url` body decoding).\n *\n * Best-effort by contract: a real HTML parser would be more correct but the\n * payload is always model context, never user-facing rendered output, so a\n * tag-stripper plus a curated entity table covers the practical cases\n * (Wikipedia, GitHub READMEs, MDN docs, Stack Overflow answers). When a tool\n * needs richer parsing it should reach for an actual parser rather than\n * extending these helpers ad-hoc.\n */\n\n/**\n * Curated named-entity table. Kept narrow on purpose — the long tail\n * (`Å`, `×`, mathematical refs, …) is out of scope because the\n * model handles unknown entities fine when they pass through verbatim. Add\n * entries when an entity is observed mangling real output, not speculatively.\n *\n * Keys are stored without the leading `&` / trailing `;` and matched\n * case-insensitively by the decoder.\n */\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n apos: '\\'',\n bull: '•',\n copy: '\\u00A9',\n ensp: ' ',\n emsp: ' ',\n gt: '>',\n hellip: '\\u2026',\n laquo: '\\u00AB',\n ldquo: '\\u201C',\n lsquo: '\\u2018',\n lt: '<',\n mdash: '\\u2014',\n middot: '\\u00B7',\n nbsp: ' ',\n ndash: '\\u2013',\n quot: '\"',\n raquo: '\\u00BB',\n rdquo: '\\u201D',\n reg: '\\u00AE',\n rsquo: '\\u2019',\n thinsp: ' ',\n trade: '\\u2122',\n}\n\n/**\n * `String.fromCodePoint` raises on out-of-range / non-finite inputs; the\n * agent's untrusted-payload assumption means we can't let a single broken\n * numeric reference crash the whole decode. Empty string on rejection is\n * the safest outcome (matches what most browsers do for invalid refs).\n */\nfunction safeFromCodePoint(cp: number): string {\n if (!Number.isFinite(cp) || cp < 0 || cp > 0x10FFFF)\n return ''\n try {\n return String.fromCodePoint(cp)\n }\n catch {\n return ''\n }\n}\n\n/**\n * Decode numeric character references (`&#x...;` / `&#NNN;`) and the curated\n * subset of named entities in {@link NAMED_ENTITIES}. Unknown named entities\n * are left as-is so the model can detect them rather than seeing a silently\n * corrupted token.\n *\n * `&` is decoded LAST so a double-escaped string like `&lt;` resolves\n * to `<` (not `<`) — that's the spec-correct single-pass decode shape.\n */\nexport function decodeHtmlEntities(text: string): string {\n let s = text\n .replace(/&#x([0-9a-f]+);/gi, (_, h) => safeFromCodePoint(Number.parseInt(h, 16)))\n .replace(/&#(\\d+);/g, (_, n) => safeFromCodePoint(Number.parseInt(n, 10)))\n s = s.replace(/&([a-z][a-z0-9]*);/gi, (full, name) => {\n const key = name.toLowerCase()\n // Defer `amp` so `&lt;` doesn't double-decode in a single pass.\n if (key === 'amp')\n return full\n const replacement = NAMED_ENTITIES[key]\n return replacement ?? full\n })\n return s.replace(/&/gi, '&')\n}\n\n/**\n * Strip `<script>`, `<style>`, `<noscript>`, comments, and every other tag.\n * Block-level openers (`<br>`, `<p>`, `<div>`, `<li>`, `<tr>`, `<h1-6>`) are\n * replaced with newlines so the resulting plaintext preserves readable\n * structure. Other tags collapse to spaces.\n */\nexport function stripHtml(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ')\n .replace(/<(?:br|p|div|li|tr|h[1-6])[^>]*>/gi, '\\n')\n .replace(/<[^>]+>/g, ' ')\n}\n\n/**\n * Inline variant — strip tags + decode entities + collapse all whitespace\n * (including line breaks) into single spaces. For short snippets where\n * structural breaks would be visual noise (search-result titles, link text).\n */\nexport function stripHtmlInline(html: string): string {\n return decodeHtmlEntities(stripHtml(html))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * Block variant — strip tags + decode entities, preserve newlines emitted\n * by block-level openers, then collapse inline whitespace per-line and drop\n * empty lines. For full-page reductions where structure aids reading.\n */\nexport function stripHtmlBlock(html: string): string {\n return decodeHtmlEntities(stripHtml(html))\n .split('\\n')\n .map(line => line.replace(/[ \\t]+/g, ' ').trim())\n .filter(line => line.length > 0)\n .join('\\n')\n}\n","import type { RequestOptions as HttpRequestOptions } from 'node:http'\nimport type { RequestOptions as HttpsRequestOptions } from 'node:https'\nimport type { ToolContext, ToolDef } from './types'\nimport { Buffer } from 'node:buffer'\nimport { promises as dns } from 'node:dns'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport { isIP } from 'node:net'\nimport { errorMessage } from '../errors'\nimport { stripHtmlBlock } from './_html'\nimport { toolPrompts } from './prompts'\n\n/**\n * Fetch a URL and return its text content.\n *\n * Companion to `web_search` (when registered) — after the model finds a\n * promising result it fetches the page to read the body in full. HTML is\n * reduced to plain text because the model rarely benefits from the markup\n * and we want to keep the payload inside the per-turn output budget.\n *\n * Restrictions:\n * - Only http(s) URLs. Anything else (file://, data:, ftp:) is rejected so\n * the tool can't be coerced into reading local files or arbitrary\n * resources.\n * - SSRF guard with TOCTOU defense: the host is DNS-resolved against the\n * loopback / link-local / private / reserved blocklist BEFORE the\n * request, and the resolved IP is then PINNED at the connection layer\n * via `node:http(s).request({ host: <resolved IP>, headers: { Host: ... },\n * servername: ... })`. This closes the classic DNS-rebinding gap where a\n * `globalThis.fetch` re-resolves the hostname and connects to a freshly\n * minted private IP. TLS verification still runs against the original\n * hostname via the explicit `servername` option.\n *\n * Note: the more idiomatic Node fix (an `undici.Agent` with a custom\n * `connect.lookup`) is silently a no-op on Bun ≤1.3 — both\n * `undici.Agent` and `node:https.Agent.lookup` are stubbed out. The\n * explicit `host: IP` + `Host:` header dance is the only mechanism that\n * actually pins on the current Bun runtime.\n * - Redirects are followed manually (up to `MAX_REDIRECTS`) and each hop\n * is re-validated, so a public hostname that 302s into a metadata\n * endpoint is rejected at the redirect step.\n * - 15s hard timeout per request.\n * - Output capped at `max_bytes` (default 200 KiB) — long pages are\n * truncated with an explicit marker. The pinned reader also stops\n * buffering past 2× the cap so a multi-MB body can't OOM us.\n */\n\nconst DEFAULT_MAX_BYTES = 200 * 1024\nconst HARD_MAX_BYTES = 1024 * 1024\nconst HTTP_TIMEOUT_MS = 15_000\nconst MAX_REDIRECTS = 5\n\n/**\n * IPv4 ranges that the SSRF guard refuses to fetch from. Covers the cloud\n * metadata services (AWS/Azure 169.254.169.254, GCP routes through the same\n * link-local block) plus every RFC1918 / loopback / reserved block a model\n * could be prompt-injected into hitting.\n */\nfunction isBlockedIPv4(ip: string): boolean {\n const parts = ip.split('.').map(Number)\n if (parts.length !== 4 || parts.some(p => !Number.isInteger(p) || p < 0 || p > 255))\n return true\n const [a, b] = parts as [number, number, number, number]\n if (a === 0)\n return true // 0.0.0.0/8 unspecified / \"this network\"\n if (a === 10)\n return true // 10/8 private\n if (a === 127)\n return true // loopback\n if (a === 169 && b === 254)\n return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31)\n return true // 172.16/12 private\n if (a === 192 && b === 168)\n return true // 192.168/16 private\n if (a === 192 && b === 0 && parts[2] === 0)\n return true // 192.0.0/24 IETF\n if (a === 198 && (b === 18 || b === 19))\n return true // 198.18/15 benchmark\n if (a >= 224)\n return true // multicast + reserved (224/4, 240/4)\n return false\n}\n\nfunction isBlockedIPv6(ip: string): boolean {\n const lower = ip.toLowerCase()\n // Normalise IPv4-mapped (::ffff:1.2.3.4) — recurse on the v4 tail. URL\n // parsing tends to canonicalise the dotted form into the hex pair\n // `::ffff:a9fe:a9fe`, so accept both shapes.\n const v4MappedDotted = lower.match(/^::ffff:([0-9.]+)$/)\n if (v4MappedDotted)\n return isBlockedIPv4(v4MappedDotted[1]!)\n const v4MappedHex = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/)\n if (v4MappedHex) {\n const hi = Number.parseInt(v4MappedHex[1]!, 16)\n const lo = Number.parseInt(v4MappedHex[2]!, 16)\n const ipv4 = `${(hi >> 8) & 0xFF}.${hi & 0xFF}.${(lo >> 8) & 0xFF}.${lo & 0xFF}`\n return isBlockedIPv4(ipv4)\n }\n if (lower === '::' || lower === '::1')\n return true\n // fc00::/7 unique-local (covers fc.. and fd..)\n if (/^f[cd][0-9a-f]{0,2}:/.test(lower))\n return true\n // fe80::/10 link-local\n if (/^fe[89ab][0-9a-f]?:/.test(lower))\n return true\n // ff00::/8 multicast\n if (lower.startsWith('ff'))\n return true\n return false\n}\n\nexport function isBlockedAddress(ip: string): boolean {\n const family = isIP(ip)\n if (family === 4)\n return isBlockedIPv4(ip)\n if (family === 6)\n return isBlockedIPv6(ip)\n return true // not a recognised IP literal — refuse\n}\n\n/**\n * Normalize a hostname for allowlist comparison: lowercase, strip a trailing\n * dot (FQDN root) and IPv6 brackets, drop a leading dot on allowlist entries.\n */\nfunction normalizeHost(host: string): string {\n let h = host.trim().toLowerCase()\n if (h.startsWith('[') && h.endsWith(']'))\n h = h.slice(1, -1)\n if (h.endsWith('.'))\n h = h.slice(0, -1)\n if (h.startsWith('.'))\n h = h.slice(1)\n return h\n}\n\n/**\n * Site identity for cross-host redirect detection: the lowercased hostname\n * with a leading `www.` stripped, so `example.com` ⇄ `www.example.com` and a\n * path-only redirect aren't flagged as cross-host. Returns `null` for an\n * unparseable URL (caller skips the note rather than guessing).\n */\nfunction sameSiteHost(url: string): string | null {\n try {\n return new URL(url).hostname.toLowerCase().replace(/^www\\./, '')\n }\n catch {\n return null\n }\n}\n\n/**\n * Host-suffix allowlist check. An empty / undefined list means \"no allowlist\"\n * → every host passes (the SSRF blocklist is the only gate). Otherwise the\n * host must equal, or be a subdomain of, one of the entries. Subdomain match\n * is on a dot boundary so `example.com` matches `docs.example.com` but not\n * `notexample.com`.\n */\nexport function isHostAllowed(hostname: string, allowHosts: readonly string[] | undefined): boolean {\n if (!allowHosts || allowHosts.length === 0)\n return true\n const host = normalizeHost(hostname)\n for (const entry of allowHosts) {\n const allowed = normalizeHost(entry)\n if (allowed.length === 0)\n continue\n if (host === allowed || host.endsWith(`.${allowed}`))\n return true\n }\n return false\n}\n\n/**\n * Resolve `hostname` and refuse if any answer falls in a blocked range.\n *\n * Returns the first allowed `{ address, family }` so the caller can pin\n * the connection to it. Throws on any blocked answer so a multi-record\n * response with one private record fails closed.\n */\nasync function resolveAndCheck(hostname: string, allowHosts?: readonly string[]): Promise<{ address: string, family: 4 | 6 }> {\n // Egress allowlist (when configured) gates BEFORE DNS so a non-approved\n // host never even triggers a lookup. Enforced here so every redirect hop\n // is re-checked, not just the initial URL.\n if (!isHostAllowed(hostname, allowHosts))\n throw new Error(`refused: ${hostname} is not in the configured egress allowlist`)\n // `URL.hostname` keeps brackets around IPv6 literals (`[::1]`) — strip them\n // before the IP check so `isIP` recognises the literal and `dns.lookup`\n // doesn't try to resolve `[::1]` as a name.\n const bare = hostname.startsWith('[') && hostname.endsWith(']')\n ? hostname.slice(1, -1)\n : hostname\n const family = isIP(bare)\n if (family === 4 || family === 6) {\n if (isBlockedAddress(bare))\n throw new Error(`refused: ${bare} is in a blocked range`)\n return { address: bare, family }\n }\n const answers = await dns.lookup(bare, { all: true, verbatim: true })\n if (answers.length === 0)\n throw new Error(`DNS lookup returned no records for ${bare}`)\n for (const { address } of answers) {\n if (isBlockedAddress(address))\n throw new Error(`refused: ${bare} resolves to blocked address ${address}`)\n }\n const first = answers[0]!\n const fam = first.family === 6 ? 6 : 4\n return { address: first.address, family: fam }\n}\n\n/**\n * Pinned HTTP/HTTPS request. Connects to `pinnedIp` directly (bypassing\n * Node/Bun's own DNS resolution) while preserving the `Host:` header and\n * (for HTTPS) TLS SNI on the URL's original hostname. This is the\n * concrete defense against DNS rebinding — see the file-level docstring.\n *\n * Reads up to `maxBufferBytes` of body and then forcibly closes the\n * connection. The body is decoded as UTF-8; binary responses degrade to\n * mojibake but the tool's contract is text payloads anyway.\n */\ninterface PinnedResponse {\n status: number\n url: string\n contentType: string\n body: string\n location: string | null\n /**\n * True when the body was cut at `maxBufferBytes` before the server\n * finished sending. Optional so test seams that stub `requestImpl`\n * don't have to populate it; absent means \"not truncated\".\n */\n truncated?: boolean\n}\n\nfunction buildHostHeader(url: URL): string {\n if (!url.port)\n return url.hostname\n const defaultPort = url.protocol === 'https:' ? '443' : '80'\n return url.port === defaultPort ? url.hostname : `${url.hostname}:${url.port}`\n}\n\nexport function pinnedRequest(\n url: URL,\n pinnedIp: string,\n family: 4 | 6,\n headers: Record<string, string>,\n signal: AbortSignal,\n maxBufferBytes: number,\n): Promise<PinnedResponse> {\n return new Promise((resolve, reject) => {\n const isHttps = url.protocol === 'https:'\n const port = url.port\n ? Number.parseInt(url.port, 10)\n : (isHttps ? 443 : 80)\n\n // `host` is the literal IP, `servername` is the original hostname so\n // TLS cert verification still matches the cert's SAN/CN against the\n // user-visible name. Node uses `servername` for `checkServerIdentity`\n // automatically when set.\n const options: HttpRequestOptions & HttpsRequestOptions = {\n host: pinnedIp,\n port,\n path: `${url.pathname}${url.search}`,\n method: 'GET',\n family,\n headers: { ...headers, Host: buildHostHeader(url) },\n ...(isHttps ? { servername: url.hostname } : {}),\n }\n\n const request = isHttps ? httpsRequest : httpRequest\n const chunks: Buffer[] = []\n let received = 0\n // Single-shot settle guard. Bun's `node:http` is quirky around\n // `req.destroy(err)` — passing an error doesn't reliably surface as an\n // `error` event the way it does on Node. We track the settle state\n // ourselves and call `resolve` / `reject` directly from the abort /\n // timeout paths so the promise terminates even when the underlying\n // socket teardown is silent.\n let settled = false\n // Forward-declared so the `finish` / abort / timeout handlers can\n // close over `req` before it's assigned below. The request object is\n // wired into them via mutable refs because the handlers must be set\n // up *before* `request(options, callback)` fires its first event.\n let req: ReturnType<typeof httpRequest> | undefined\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const teardown = (): void => {\n try { req?.destroy() }\n catch { /* tear-down best effort */ }\n }\n\n const finish = (settler: () => void): void => {\n if (settled)\n return\n settled = true\n if (timer)\n clearTimeout(timer)\n signal.removeEventListener('abort', onSignalAbort)\n settler()\n }\n\n function onSignalAbort(): void {\n finish(() => reject(new Error('request aborted')))\n teardown()\n }\n\n // Pre-aborted signal — short-circuit so we don't even open a socket.\n // (This is the realistic shape when a user hits Ctrl+C while the agent\n // is already shutting down: the signal is `.aborted` by the time the\n // tool function runs.)\n if (signal.aborted) {\n reject(new Error('request aborted'))\n return\n }\n\n timer = setTimeout(() => {\n finish(() => reject(new Error(`request timed out after ${HTTP_TIMEOUT_MS}ms`)))\n teardown()\n }, HTTP_TIMEOUT_MS)\n\n signal.addEventListener('abort', onSignalAbort, { once: true })\n\n req = request(options, (res) => {\n const status = res.statusCode ?? 0\n const headersOut = res.headers\n const contentType = (headersOut['content-type'] ?? '').toString().toLowerCase()\n const location = typeof headersOut.location === 'string' ? headersOut.location : null\n\n // Short-circuit on redirect responses — body is irrelevant.\n if (status >= 300 && status < 400 && location) {\n res.resume() // drain so the socket can be released\n finish(() => resolve({ status, url: url.toString(), contentType, body: '', location }))\n return\n }\n\n let bodyTruncated = false\n res.on('data', (chunk: Buffer) => {\n if (received >= maxBufferBytes) {\n // Stop accumulating; tear the socket down so the server stops\n // sending. We still resolve cleanly from `end` (or `close` if\n // the server doesn't finish flushing before the kill lands).\n bodyTruncated = true\n teardown()\n return\n }\n // Keep only what fits in the budget — a chunk straddling the cap\n // would otherwise overshoot it by up to one chunk. A partial slice\n // means the body was cut, so flag it and stop the transfer.\n const room = maxBufferBytes - received\n if (chunk.length > room) {\n chunks.push(chunk.subarray(0, room))\n received += room\n bodyTruncated = true\n teardown()\n return\n }\n received += chunk.length\n chunks.push(chunk)\n })\n res.on('end', () => {\n const body = Buffer.concat(chunks).toString('utf-8')\n finish(() => resolve({ status, url: url.toString(), contentType, body, location: null, truncated: bodyTruncated }))\n })\n res.on('close', () => {\n // Socket torn down before `end` (e.g. when our maxBufferBytes\n // trigger destroyed the request). Resolve with whatever we\n // buffered so far — `truncated` tells the caller the body is cut.\n if (!settled) {\n const body = Buffer.concat(chunks).toString('utf-8')\n finish(() => resolve({ status, url: url.toString(), contentType, body, location: null, truncated: bodyTruncated }))\n }\n })\n res.on('error', err => finish(() => reject(err)))\n })\n\n req.on('error', (err: Error) => finish(() => reject(err)))\n req.end()\n })\n}\n\n/**\n * Walk up to {@link MAX_REDIRECTS} hops, validating the target host of each\n * 3xx response against the SSRF blocklist before following.\n *\n * `deps` is an internal seam for tests — production callers use the\n * defaults. Both knobs are intentionally opaque: hosts that want to\n * customize the blocklist should use a higher-level mechanism (a future\n * `behavior.fetchUrlAllowHosts` setting) rather than reaching into this.\n */\nexport interface FetchSsrfDeps {\n /** Resolve + validate a hostname. Defaults to {@link resolveAndCheck}. */\n resolver?: (hostname: string) => Promise<{ address: string, family: 4 | 6 }>\n /** Execute a single pinned HTTP request. Defaults to {@link pinnedRequest}. */\n requestImpl?: typeof pinnedRequest\n}\n\nexport async function fetchWithSsrfGuard(\n startUrl: URL,\n headers: Record<string, string>,\n signal: AbortSignal,\n maxBufferBytes: number,\n deps: FetchSsrfDeps = {},\n allowHosts?: readonly string[],\n): Promise<PinnedResponse> {\n // Default resolver binds the egress allowlist; a test-injected resolver\n // owns its own policy.\n const resolver = deps.resolver ?? ((hostname: string) => resolveAndCheck(hostname, allowHosts))\n const requestImpl = deps.requestImpl ?? pinnedRequest\n let current = startUrl\n for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n const { address, family } = await resolver(current.hostname)\n const res = await requestImpl(current, address, family, headers, signal, maxBufferBytes)\n if (!res.location)\n return res\n if (hop === MAX_REDIRECTS)\n throw new Error(`too many redirects (>${MAX_REDIRECTS})`)\n let next: URL\n try {\n next = new URL(res.location, current)\n }\n catch {\n throw new Error(`invalid redirect target: ${res.location}`)\n }\n if (next.protocol !== 'http:' && next.protocol !== 'https:')\n throw new Error(`refused redirect to non-http(s) target: ${next.protocol}`)\n current = next\n }\n throw new Error('redirect loop exited unexpectedly')\n}\n\n/**\n * Opt-in, process-local response cache for `fetch_url`. Disabled unless\n * `behavior.fetchUrlCacheTtlMs` is set (> 0). Bounded by entry count and total\n * cached bytes; eviction is oldest-first (insertion order via `Map`, which is\n * a good-enough LRU because we delete-then-set on hit to refresh recency).\n *\n * Keyed on `<url>\\u0000<cap>\\u0000<egress-policy>` so two calls with different\n * `max_bytes` (hence different truncation) don't alias, and — because the\n * cache is module-global — an agent with a permissive `fetchUrlAllowHosts`\n * can't populate entries that a co-resident agent with a stricter allowlist\n * would then be served. Only successful text responses are stored — the\n * caller gates on status before writing.\n */\nconst FETCH_CACHE_MAX_ENTRIES = 64\nconst FETCH_CACHE_MAX_BYTES = 16 * 1024 * 1024 // 16 MiB total\n\ninterface FetchCacheEntry {\n /** Fully-formatted tool output (header + body) ready to return verbatim. */\n output: string\n expiresAt: number\n bytes: number\n}\n\nconst fetchCache = new Map<string, FetchCacheEntry>()\nlet fetchCacheBytes = 0\n\nfunction cacheKey(url: string, cap: number, allowHosts?: readonly string[]): string {\n // Normalize + sort so equivalent policies (`['A.com', 'b.com']` vs\n // `['b.com', 'a.com.']`) share entries; an absent/empty allowlist keys\n // as the empty policy.\n const policy = (allowHosts ?? []).map(normalizeHost).sort().join(',')\n return `${url}\\u0000${cap}\\u0000${policy}`\n}\n\nfunction cacheGet(key: string): string | null {\n const entry = fetchCache.get(key)\n if (!entry)\n return null\n if (Date.now() >= entry.expiresAt) {\n fetchCache.delete(key)\n fetchCacheBytes -= entry.bytes\n return null\n }\n // Refresh recency: re-insert at the tail of the Map's iteration order.\n fetchCache.delete(key)\n fetchCache.set(key, entry)\n return entry.output\n}\n\nfunction cacheSet(key: string, output: string, ttlMs: number): void {\n const bytes = Buffer.byteLength(output, 'utf-8')\n // A single oversize payload that can't fit under the byte cap is simply not\n // cached (still returned to the caller) rather than evicting everything.\n if (bytes > FETCH_CACHE_MAX_BYTES)\n return\n // Drop any stale entry for this key first so the byte accounting stays exact.\n const prev = fetchCache.get(key)\n if (prev) {\n fetchCache.delete(key)\n fetchCacheBytes -= prev.bytes\n }\n fetchCache.set(key, { output, expiresAt: Date.now() + ttlMs, bytes })\n fetchCacheBytes += bytes\n // Evict oldest-first until both caps are satisfied.\n while (fetchCache.size > FETCH_CACHE_MAX_ENTRIES || fetchCacheBytes > FETCH_CACHE_MAX_BYTES) {\n const oldest = fetchCache.keys().next().value\n if (oldest === undefined)\n break\n const victim = fetchCache.get(oldest)!\n fetchCache.delete(oldest)\n fetchCacheBytes -= victim.bytes\n }\n}\n\n/** Test seam — drop all cached entries. */\nexport function clearFetchUrlCache(): void {\n fetchCache.clear()\n fetchCacheBytes = 0\n}\n\n/**\n * Test seam — exercise the cache get/set/eviction in isolation without\n * standing up the full SSRF + HTTP path. Not part of the public tool surface;\n * `execute` uses the module-private functions directly.\n */\nexport const __fetchCacheTestApi = {\n key: cacheKey,\n get: cacheGet,\n set: cacheSet,\n size: () => fetchCache.size,\n bytes: () => fetchCacheBytes,\n}\n\nexport const fetchUrl: ToolDef = {\n isConcurrencySafe: true,\n spec: {\n name: 'fetch_url',\n description: toolPrompts.fetch_url,\n inputSchema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'HTTP or HTTPS URL to fetch.',\n },\n max_bytes: {\n type: 'number',\n description: `Truncate the response body beyond this many bytes. Default: ${DEFAULT_MAX_BYTES}. Hard cap: ${HARD_MAX_BYTES}.`,\n },\n },\n required: ['url'],\n },\n },\n async execute({ url, max_bytes }, ctx: ToolContext) {\n const raw = typeof url === 'string' ? url.trim() : ''\n if (!raw)\n return 'fetch_url error: `url` is required'\n let parsed: URL\n try {\n parsed = new URL(raw)\n }\n catch {\n return `fetch_url error: invalid URL: ${raw}`\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')\n return `fetch_url error: only http(s) URLs are allowed (got ${parsed.protocol})`\n\n const cap = Math.min(\n typeof max_bytes === 'number' && max_bytes > 0 ? Math.floor(max_bytes) : DEFAULT_MAX_BYTES,\n HARD_MAX_BYTES,\n )\n // Buffer up to 2× the cap so HTML overhead doesn't starve the visible\n // text budget — a 200 KiB cap on rendered text can legitimately need to\n // read ~400 KiB of raw HTML before stripping. Hard ceiling is still\n // governed by `HARD_MAX_BYTES * 2` which is comfortably below OOM.\n const bufferCap = Math.min(cap * 2, HARD_MAX_BYTES * 2)\n\n // Opt-in response cache (off unless `behavior.fetchUrlCacheTtlMs > 0`).\n // The egress allowlist is part of the key AND re-checked before serving\n // a hit — the cache is module-global, so neither a policy change within\n // an agent nor a co-resident agent's entries may bypass this agent's\n // allowlist.\n const allowHosts = ctx.behavior?.fetchUrlAllowHosts\n const ttlMs = ctx.behavior?.fetchUrlCacheTtlMs\n const cacheEnabled = typeof ttlMs === 'number' && ttlMs > 0\n const key = cacheKey(parsed.toString(), cap, allowHosts)\n if (cacheEnabled && isHostAllowed(parsed.hostname, allowHosts)) {\n const hit = cacheGet(key)\n if (hit !== null)\n return hit\n }\n\n try {\n const res = await fetchWithSsrfGuard(\n parsed,\n {\n 'user-agent': 'Mozilla/5.0 (compatible; zidane/1.0)',\n 'accept': 'text/html,text/plain,application/json;q=0.9,*/*;q=0.5',\n 'accept-encoding': 'identity', // no gzip — we don't decompress\n },\n ctx.signal,\n bufferCap,\n {},\n allowHosts,\n )\n const body = res.body\n const isHtml = res.contentType.includes('html') || /^\\s*<!doctype html|^\\s*<html/i.test(body)\n const text = isHtml ? stripHtmlBlock(body) : body\n const truncated = text.length > cap\n // Two distinct truncation reasons: the rendered text overflowing `cap`,\n // and the raw transfer being cut at the buffer ceiling (`res.truncated`)\n // — the latter can leave the rendered text under `cap` (HTML overhead\n // stripped away), so it needs its own marker or the model would treat\n // a partial body as complete.\n const out = truncated\n ? `${text.slice(0, cap)}\\n\\n[truncated at ${cap} bytes; full length ${text.length}]`\n : res.truncated\n ? `${text}\\n\\n[response truncated: body exceeded the ${bufferCap}-byte transfer buffer]`\n : text\n // `res.url` reflects the FINAL hop after redirects; flag a cross-host\n // landing so the model knows the content came from a different origin\n // than it asked for (open-redirect awareness). Same-host path changes\n // and www add/strip are treated as the same site and not flagged.\n const finalHost = sameSiteHost(res.url)\n const requestedHost = sameSiteHost(parsed.toString())\n const redirectNote = finalHost && requestedHost && finalHost !== requestedHost\n ? `Note: ${parsed.hostname} redirected to a different host (${new URL(res.url).hostname}).\\n`\n : ''\n const header = `URL: ${res.url}\\nStatus: ${res.status}\\nContent-Type: ${res.contentType || '(none)'}\\n${redirectNote}\\n`\n const formatted = header + out\n // Cache only successful, complete text responses — never errors /\n // redirects / transfer-truncated bodies (a retry may get the rest).\n if (cacheEnabled && res.status >= 200 && res.status < 300 && res.truncated !== true)\n cacheSet(key, formatted, ttlMs)\n return formatted\n }\n catch (err) {\n return `fetch_url error: ${errorMessage(err)}`\n }\n },\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,43 @@ function fmtTokens(n) {
|
|
|
17
17
|
return `${(n / 1e6).toFixed(2)}M`;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
+
* Live cost, readable: sub-dollar runs keep a tenth of a cent (so the figure
|
|
21
|
+
* visibly moves while a cheap run streams), then cents with grouping:
|
|
22
|
+
* `$0.123`, `$4.56`, `$1,646.17`. `null` for nothing spent, never `$0.00`.
|
|
23
|
+
*/
|
|
24
|
+
function formatCostLabel(usd) {
|
|
25
|
+
if (!(usd > 0)) return null;
|
|
26
|
+
if (usd < 1) return `$${usd.toFixed(3)}`;
|
|
27
|
+
return `$${usd.toLocaleString("en-US", {
|
|
28
|
+
minimumFractionDigits: 2,
|
|
29
|
+
maximumFractionDigits: 2
|
|
30
|
+
})}`;
|
|
31
|
+
}
|
|
32
|
+
const SPAWN_ELAPSED = /^(?:\d+(?:\.\d+)?(?:ms|s)|\d+m(?:\d+s)?|\d+h(?:\d+m)?)$/;
|
|
33
|
+
/**
|
|
34
|
+
* A worker's end as a sentence, from its spawn-end marker text (`done · 42s
|
|
35
|
+
* · $0.01 · 3.1k in / 400 out`, the only record on replay): `Finished in
|
|
36
|
+
* 42s`, `Failed after 1m 5s`, `Stopped after 9s`, and what it cost and used.
|
|
37
|
+
*/
|
|
38
|
+
function describeSpawnEnd(text) {
|
|
39
|
+
const [tag = "", ...parts] = text.split(" · ");
|
|
40
|
+
const elapsed = parts.find((seg) => SPAWN_ELAPSED.test(seg));
|
|
41
|
+
const rest = parts.filter((seg) => seg !== elapsed).join(" · ");
|
|
42
|
+
const failed = tag.startsWith("error");
|
|
43
|
+
const verb = failed ? "Failed" : tag.startsWith("aborted") ? "Stopped" : "Finished";
|
|
44
|
+
return {
|
|
45
|
+
head: `${verb}${elapsed ? ` ${verb === "Finished" ? "in" : "after"} ${elapsed.replace(/(m|h)(\d)/g, "$1 $2")}` : ""}`,
|
|
46
|
+
rest,
|
|
47
|
+
failed
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Run time as a person reads it: `42s`, `1m 05s`, `2h 03m`. */
|
|
51
|
+
function formatElapsed(sec) {
|
|
52
|
+
if (sec < 60) return `${sec}s`;
|
|
53
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m ${String(sec % 60).padStart(2, "0")}s`;
|
|
54
|
+
return `${Math.floor(sec / 3600)}h ${String(Math.floor(sec % 3600 / 60)).padStart(2, "0")}m`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
20
57
|
* USD cost, dollar-prefixed — 2 decimals, 4 below a cent so tiny spends
|
|
21
58
|
* stay visible (a haiku worker's $0.0007 must not render as $0.00).
|
|
22
59
|
*/
|
|
@@ -138,6 +175,6 @@ function compactPath(path, maxWidth, home) {
|
|
|
138
175
|
return display;
|
|
139
176
|
}
|
|
140
177
|
//#endregion
|
|
141
|
-
export {
|
|
178
|
+
export { fmtTokens as a, formatElapsed as c, previewLine as d, shortId as f, fmtCost as i, formatTaskStatus as l, compactPath as n, formatCostLabel as o, describeSpawnEnd as r, formatDuration as s, ageString as t, formatTaskSummary as u };
|
|
142
179
|
|
|
143
|
-
//# sourceMappingURL=format-
|
|
180
|
+
//# sourceMappingURL=format-C2NQEVUH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-C2NQEVUH.js","names":[],"sources":["../src/chat/format.ts"],"sourcesContent":["export { formatBytes, humanBytes } from '../format-bytes'\n\n/**\n * Resolve the user's home directory from env (no static os-module import),\n * so this module stays node-free and importable from a browser-context\n * renderer (Electron's renderer, Vite) via `zidane/chat/pure`. Reads the\n * platform env vars Node/Electron populate; returns '' when unavailable\n * (renderer), in which case `compactPath` falls back to the verbatim path.\n */\nfunction resolveHome(): string {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env\n return env?.HOME ?? env?.USERPROFILE ?? ''\n}\n\n/** Compact token formatter — 12_415 → \"12.4k\", 1_234_567 → \"1.23M\". */\nexport function fmtTokens(n: number): string {\n if (n < 1000)\n return String(n)\n if (n < 1_000_000)\n return `${(n / 1000).toFixed(n < 10_000 ? 2 : 1)}k`\n return `${(n / 1_000_000).toFixed(2)}M`\n}\n\n/**\n * Live cost, readable: sub-dollar runs keep a tenth of a cent (so the figure\n * visibly moves while a cheap run streams), then cents with grouping:\n * `$0.123`, `$4.56`, `$1,646.17`. `null` for nothing spent, never `$0.00`.\n */\nexport function formatCostLabel(usd: number): string | null {\n if (!(usd > 0))\n return null\n if (usd < 1)\n return `$${usd.toFixed(3)}`\n return `$${usd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`\n}\n\nconst SPAWN_ELAPSED = /^(?:\\d+(?:\\.\\d+)?(?:ms|s)|\\d+m(?:\\d+s)?|\\d+h(?:\\d+m)?)$/\n\n/**\n * A worker's end as a sentence, from its spawn-end marker text (`done · 42s\n * · $0.01 · 3.1k in / 400 out`, the only record on replay): `Finished in\n * 42s`, `Failed after 1m 5s`, `Stopped after 9s`, and what it cost and used.\n */\nexport function describeSpawnEnd(text: string): { head: string, rest: string, failed: boolean } {\n const [tag = '', ...parts] = text.split(' · ')\n const elapsed = parts.find(seg => SPAWN_ELAPSED.test(seg))\n const rest = parts.filter(seg => seg !== elapsed).join(' · ')\n const failed = tag.startsWith('error')\n const verb = failed ? 'Failed' : tag.startsWith('aborted') ? 'Stopped' : 'Finished'\n const time = elapsed ? ` ${verb === 'Finished' ? 'in' : 'after'} ${elapsed.replace(/(m|h)(\\d)/g, '$1 $2')}` : ''\n return { head: `${verb}${time}`, rest, failed }\n}\n\n/** Run time as a person reads it: `42s`, `1m 05s`, `2h 03m`. */\nexport function formatElapsed(sec: number): string {\n if (sec < 60)\n return `${sec}s`\n if (sec < 3600)\n return `${Math.floor(sec / 60)}m ${String(sec % 60).padStart(2, '0')}s`\n return `${Math.floor(sec / 3600)}h ${String(Math.floor((sec % 3600) / 60)).padStart(2, '0')}m`\n}\n\n/**\n * USD cost, dollar-prefixed — 2 decimals, 4 below a cent so tiny spends\n * stay visible (a haiku worker's $0.0007 must not render as $0.00).\n */\nexport function fmtCost(cost: number): string {\n const digits = cost.toFixed(cost < 0.01 ? 4 : 2)\n return `$${digits}`\n}\n\n/** Compact relative-time formatter — \"just now / 5m / 3h / 2d\". */\nexport function ageString(ts: number, now: number = Date.now()): string {\n const m = Math.floor((now - ts) / 60_000)\n if (m < 1)\n return 'just now'\n if (m < 60)\n return `${m}m ago`\n const h = Math.floor(m / 60)\n if (h < 24)\n return `${h}h ago`\n return `${Math.floor(h / 24)}d ago`\n}\n\n/** Six-char short form of a session id for headers and lists. */\nexport function shortId(id: string): string {\n return id.replace(/-/g, '').slice(0, 6)\n}\n\n/**\n * Single-line preview of a multi-line string, capped at `max` chars and\n * ellipsis-terminated when truncated.\n *\n * Whitespace runs (newlines, tabs, multiple spaces) collapse into one\n * space so the rendered output stays on a single visual row no matter\n * how the input was shaped. Used by every transcript \"preview\" surface\n * (spawn-start task, `tool: shell (background): <command>`,\n * `<task-notification>` summary line, etc.) — without the whitespace\n * collapse, a 60-char `slice` on a string with an inline `\\n\\n` paints\n * the second paragraph below the first, producing the visible\n * \"preview text spills onto multiple lines\" bug (and, downstream,\n * misaligned spawn markers when the wrapped lines collide with\n * other events).\n *\n * Reserves one slot for the `…` so the displayed width is exactly\n * `max` when truncation kicks in.\n */\nexport function previewLine(s: string, max: number): string {\n const single = s.replace(/\\s+/g, ' ').trim()\n if (single.length <= max)\n return single\n return `${single.slice(0, max - 1)}…`\n}\n\n/**\n * Compact human-readable duration formatter shared by background-task\n * surfaces (the `<task-notification>` summary, the TUI banner, the\n * `shell_kill` tool result, etc.).\n *\n * Format ladder:\n * - `< 1s` → `\"Nms\"`\n * - `< 10s` → `\"N.Ns\"` (one decimal)\n * - `< 1m` → `\"Ns\"` (whole seconds)\n * - `< 1h` → `\"NmNs\"` / `\"Nm\"` when seconds round to 0\n * - `≥ 1h` → `\"NhNm\"` / `\"Nh\"` when minutes round to 0\n *\n * Single source of truth so a 60s task renders the same across the\n * model-facing XML summary and the user-facing banner. Earlier\n * separate formatters disagreed (XML said `\"60.0s\"`, banner said `\"1m\"`)\n * which was confusing to the user reading both side by side.\n */\nexport function formatDuration(ms: number): string {\n if (ms < 0)\n ms = 0\n if (ms < 1000)\n return `${ms}ms`\n if (ms < 60_000)\n return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`\n const minutes = Math.floor(ms / 60_000)\n const seconds = Math.floor((ms % 60_000) / 1000)\n if (minutes < 60)\n return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`\n const hours = Math.floor(minutes / 60)\n const remMinutes = minutes % 60\n return remMinutes > 0 ? `${hours}h${remMinutes}m` : `${hours}h`\n}\n\n/**\n * Status label for a terminated background task — `\"exited <code>\"`\n * for natural exits, `\"killed\"` (with the signal name when known)\n * for our-issued SIGTERMs.\n *\n * Pulled out as its own function so the `<task-notification>` XML\n * summary, the TUI banner header, the `shell_kill` tool result, and\n * future surfaces all read the same string.\n */\nexport function formatTaskStatus(info: {\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n}): string {\n return info.status === 'killed'\n ? `killed${info.signal ? ` (${info.signal})` : ''}`\n : `exited ${info.exitCode}`\n}\n\n/**\n * One-line summary of a terminated background task — the shape used by\n * the `<task-notification>` XML's `<summary>` tag AND the TUI banner's\n * `event.text` fallback string. Three dot-separated segments:\n *\n * `<command preview · status · duration>`\n *\n * Centralizes the format so live + replay + wire all agree, and so a\n * future cosmetic tweak (separator glyph, segment ordering) lands in\n * exactly one place.\n */\nexport function formatTaskSummary(info: {\n command: string\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n durationMs: number\n}, maxCommandChars = 80): string {\n return `${previewLine(info.command, maxCommandChars)} · ${formatTaskStatus(info)} · ${formatDuration(info.durationMs)}`\n}\n\n/**\n * Compact an absolute path for display: replace the user's `$HOME`\n * prefix with `~` (so `/Users/yael/Code/zidane` → `~/Code/zidane`),\n * and optionally left-truncate with an ellipsis when the result\n * still exceeds `maxWidth` (so the path's *tail* — the part the user\n * recognizes — stays visible: `…/zidane` rather than `/Users/yaeluil…`).\n *\n * `maxWidth` is the maximum *display width* in cells. Omit to skip\n * truncation. Paths outside `$HOME` are returned verbatim modulo\n * truncation. The ellipsis (`…`) counts as one cell.\n *\n * `home` overrides `os.homedir()` for tests; production callers leave\n * it undefined and pay the cheap one-syscall lookup per call.\n */\nexport function compactPath(path: string, maxWidth?: number, home?: string): string {\n const h = home ?? resolveHome()\n let display = path\n if (h) {\n if (path === h)\n display = '~'\n else if (path.startsWith(`${h}/`))\n display = `~${path.slice(h.length)}`\n }\n if (maxWidth !== undefined && maxWidth > 1 && display.length > maxWidth) {\n // Left-truncate: keep the rightmost `maxWidth - 1` chars and\n // prepend `…`. The right side of a path is the distinctive\n // bit (repo name, leaf folder); the left side is the boilerplate\n // (home, common parents) we're happy to drop.\n return `…${display.slice(display.length - maxWidth + 1)}`\n }\n return display\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/tools/read-file-targets.ts
|
|
2
|
+
/**
|
|
3
|
+
* Normalized read targets of a `read_file` input, in call order. Entries
|
|
4
|
+
* without a usable string path are dropped (the tool reports them itself).
|
|
5
|
+
* Single-`path` calls carry their top-level `offset` / `limit`.
|
|
6
|
+
*/
|
|
7
|
+
function readFileTargets(input) {
|
|
8
|
+
if (!input) return [];
|
|
9
|
+
if (Array.isArray(input.paths)) {
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const entry of input.paths) if (typeof entry === "string") {
|
|
12
|
+
if (entry.length > 0) out.push({ path: entry });
|
|
13
|
+
} else if (entry && typeof entry === "object" && typeof entry.path === "string") {
|
|
14
|
+
const e = entry;
|
|
15
|
+
if (e.path.length > 0) out.push({
|
|
16
|
+
path: e.path,
|
|
17
|
+
offset: e.offset,
|
|
18
|
+
limit: e.limit
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
if (typeof input.path === "string" && input.path.length > 0) return [{
|
|
24
|
+
path: input.path,
|
|
25
|
+
offset: input.offset,
|
|
26
|
+
limit: input.limit
|
|
27
|
+
}];
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
/** Distinct file paths a `read_file` input reads, in first-seen order. */
|
|
31
|
+
function readFileInputPaths(input) {
|
|
32
|
+
return [...new Set(readFileTargets(input).map((t) => t.path))];
|
|
33
|
+
}
|
|
34
|
+
/** Header line for one section of a batched read. */
|
|
35
|
+
function readFileBatchHeader(path) {
|
|
36
|
+
return `==> ${path} <==`;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/format-bytes.ts
|
|
40
|
+
/** Byte counts for people: `512 B`, `1.5 KB`, `2.0 MB`. */
|
|
41
|
+
function formatBytes(bytes) {
|
|
42
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
43
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
44
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
45
|
+
}
|
|
46
|
+
/** {@link formatBytes} with whole kilobytes (`2 KB`), for short notices and attachment labels. */
|
|
47
|
+
function humanBytes(bytes) {
|
|
48
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
49
|
+
if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
|
|
50
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { readFileTargets as a, readFileInputPaths as i, humanBytes as n, readFileBatchHeader as r, formatBytes as t };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=format-bytes-C7tfc3Zm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-bytes-C7tfc3Zm.js","names":[],"sources":["../src/tools/read-file-targets.ts","../src/format-bytes.ts"],"sourcesContent":["/**\n * Input-shape helpers for `read_file`, dependency-free so loop-level\n * consumers (stale-read elision, compaction, task notifications, read-state\n * hydration) can resolve which files a call read without importing the tool.\n *\n * A call reads either one `path` or a batch of `paths`; batch entries are a\n * bare path string or `{ path, offset?, limit? }`, and the same path may\n * repeat to read several ranges.\n */\n\nexport interface ReadFileTarget {\n path: string\n offset?: unknown\n limit?: unknown\n}\n\n/** Max entries in one `paths` batch. */\nexport const READ_FILE_MAX_BATCH = 20\n\n/**\n * Normalized read targets of a `read_file` input, in call order. Entries\n * without a usable string path are dropped (the tool reports them itself).\n * Single-`path` calls carry their top-level `offset` / `limit`.\n */\nexport function readFileTargets(input: Record<string, unknown> | undefined): ReadFileTarget[] {\n if (!input)\n return []\n if (Array.isArray(input.paths)) {\n const out: ReadFileTarget[] = []\n for (const entry of input.paths) {\n if (typeof entry === 'string') {\n if (entry.length > 0)\n out.push({ path: entry })\n }\n else if (entry && typeof entry === 'object' && typeof (entry as { path?: unknown }).path === 'string') {\n const e = entry as { path: string, offset?: unknown, limit?: unknown }\n if (e.path.length > 0)\n out.push({ path: e.path, offset: e.offset, limit: e.limit })\n }\n }\n return out\n }\n if (typeof input.path === 'string' && input.path.length > 0)\n return [{ path: input.path, offset: input.offset, limit: input.limit }]\n return []\n}\n\n/** Distinct file paths a `read_file` input reads, in first-seen order. */\nexport function readFileInputPaths(input: Record<string, unknown> | undefined): string[] {\n return [...new Set(readFileTargets(input).map(t => t.path))]\n}\n\n/** Header line for one section of a batched read. */\nexport function readFileBatchHeader(path: string): string {\n return `==> ${path} <==`\n}\n","/** Byte counts for people: `512 B`, `1.5 KB`, `2.0 MB`. */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024)\n return `${bytes} B`\n if (bytes < 1024 * 1024)\n return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/** {@link formatBytes} with whole kilobytes (`2 KB`), for short notices and attachment labels. */\nexport function humanBytes(bytes: number): string {\n if (bytes < 1024)\n return `${bytes} B`\n if (bytes < 1024 * 1024)\n return `${Math.round(bytes / 1024)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,gBAAgB,OAA8D;CAC5F,IAAI,CAAC,OACH,OAAO,CAAC;CACV,IAAI,MAAM,QAAQ,MAAM,KAAK,GAAG;EAC9B,MAAM,MAAwB,CAAC;EAC/B,KAAK,MAAM,SAAS,MAAM,OACxB,IAAI,OAAO,UAAU,UACf;OAAA,MAAM,SAAS,GACjB,IAAI,KAAK,EAAE,MAAM,MAAM,CAAC;EAAA,OAEvB,IAAI,SAAS,OAAO,UAAU,YAAY,OAAQ,MAA6B,SAAS,UAAU;GACrG,MAAM,IAAI;GACV,IAAI,EAAE,KAAK,SAAS,GAClB,IAAI,KAAK;IAAE,MAAM,EAAE;IAAM,QAAQ,EAAE;IAAQ,OAAO,EAAE;GAAM,CAAC;EAC/D;EAEF,OAAO;CACT;CACA,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,GACxD,OAAO,CAAC;EAAE,MAAM,MAAM;EAAM,QAAQ,MAAM;EAAQ,OAAO,MAAM;CAAM,CAAC;CACxE,OAAO,CAAC;AACV;;AAGA,SAAgB,mBAAmB,OAAsD;CACvF,OAAO,CAAC,GAAG,IAAI,IAAI,gBAAgB,KAAK,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC;AAC7D;;AAGA,SAAgB,oBAAoB,MAAsB;CACxD,OAAO,OAAO,KAAK;AACrB;;;;ACtDA,SAAgB,YAAY,OAAuB;CACjD,IAAI,QAAQ,MACV,OAAO,GAAG,MAAM;CAClB,IAAI,QAAQ,SACV,OAAO,IAAI,QAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE;CACtC,OAAO,IAAI,QAAS,QAAA,CAAc,QAAQ,CAAC,EAAE;AAC/C;;AAGA,SAAgB,WAAW,OAAuB;CAChD,IAAI,QAAQ,MACV,OAAO,GAAG,MAAM;CAClB,IAAI,QAAQ,SACV,OAAO,GAAG,KAAK,MAAM,QAAQ,IAAI,EAAE;CACrC,OAAO,IAAI,QAAS,QAAA,CAAc,QAAQ,CAAC,EAAE;AAC/C"}
|