shadow-claw 1.54.0 → 1.55.0
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/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/README.md +1 -1
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-DO2t9bUI.js → chunk--IuVyWfT.js} +24 -23
- package/dist/cli/{chunk-2ZYd9gYC.js → chunk--JpdI-Ht.js} +1 -1
- package/dist/cli/{chunk-m4Ineemy.js → chunk-4O8yrQuN.js} +1 -1
- package/dist/cli/{chunk-BiH9IXnQ.js → chunk-7S9Ywlfy.js} +1 -1
- package/dist/cli/{chunk-BmMOdYvk.js → chunk-B0SqqDSK.js} +1 -1
- package/dist/cli/chunk-BNaWaEm-.js +1 -0
- package/dist/cli/chunk-BXCsxO9e.js +1 -0
- package/dist/cli/chunk-Bq37qDoZ.js +18 -0
- package/dist/cli/{chunk-C1htf-nO.js → chunk-BrEeCKIo.js} +2 -2
- package/dist/cli/{chunk-BI1SOTM1.js → chunk-Bu3ylrhT.js} +8 -8
- package/dist/cli/{chunk-3smiGQoQ.js → chunk-C1v_s0Kb.js} +1 -1
- package/dist/cli/{chunk-DGeysxz3.js → chunk-C44G533y.js} +1 -1
- package/dist/cli/{chunk-9KiBalKu.js → chunk-C7hJLmsL.js} +1 -1
- package/dist/cli/{chunk-BFv9XSNd.js → chunk-CAaQWd6t.js} +1 -1
- package/dist/cli/chunk-CIkgn5Fh.js +1 -0
- package/dist/cli/chunk-CWEIN2kc.js +1 -0
- package/dist/cli/{chunk-BDeDZ-0L.js → chunk-CYg1lO1a.js} +1 -1
- package/dist/cli/{chunk-DXLmEQ3i.js → chunk-Ce09lwXT.js} +1 -1
- package/dist/cli/{chunk-XsfHeN4m.js → chunk-CpdqRSO-.js} +1 -1
- package/dist/cli/chunk-DB4x5L-B.js +1 -0
- package/dist/cli/{chunk-DaNyMqrk.js → chunk-DBvObsBQ.js} +1 -1
- package/dist/cli/chunk-DGPpt7Ka.js +1 -0
- package/dist/cli/chunk-DGagHluH.js +1 -0
- package/dist/cli/chunk-DUIs23D9.js +1 -0
- package/dist/cli/{chunk-CyvfQV16.js → chunk-DaurYOqg.js} +1 -1
- package/dist/cli/{chunk-DSFrcSyA.js → chunk-DfwRL0Ue.js} +1 -1
- package/dist/cli/{chunk-M2tZrzY5.js → chunk-DudYQk0p.js} +1 -1
- package/dist/cli/chunk-DvKo50Tp.js +2 -0
- package/dist/cli/{chunk-H_Mw5ypp.js → chunk-Dvrqte5Q.js} +2 -2
- package/dist/cli/{chunk-BtNO-m9T.js → chunk-DzhSExU0.js} +1 -1
- package/dist/cli/{chunk-IOMO88kZ.js → chunk-ENKlt3i9.js} +1 -1
- package/dist/cli/{chunk-Bspgzwsm.js → chunk-FWRwAX75.js} +8 -8
- package/dist/cli/chunk-Lf-Z-h48.js +32 -0
- package/dist/cli/{chunk-BucXnZ4Z.js → chunk-VRfkSLrD.js} +1 -1
- package/dist/cli/chunk-uaTxD024.js +1 -0
- package/dist/cli/cli.js +1 -1
- package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
- package/dist/headless-agent.js +2 -2
- package/dist/lib/components/index.js +25 -25
- package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw/utils/get-route.d.ts +3 -0
- package/dist/lib/components/shadow-claw/utils/sync-slotted-sidebar-active-links.d.ts +1 -1
- package/dist/lib/components/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-page-header.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{configure-peer-js-D5jDCtLx.js → configure-peer-js-C9554Slv.js} +2 -2
- package/dist/lib/{configure-peer-js-D5jDCtLx.js.map → configure-peer-js-C9554Slv.js.map} +1 -1
- package/dist/lib/{file-viewer-B4CL4e0i.js → file-viewer-DBc5dxNy.js} +2 -2
- package/dist/lib/{file-viewer-B4CL4e0i.js.map → file-viewer-DBc5dxNy.js.map} +1 -1
- package/dist/lib/index.js +28 -27
- package/dist/lib/{init-control-plane-l2uFnhGV.js → init-control-plane-D-41Fm5q.js} +12 -12
- package/dist/lib/{init-control-plane-l2uFnhGV.js.map → init-control-plane-D-41Fm5q.js.map} +1 -1
- package/dist/lib/{markdown-0_p_zfxL.js → markdown-KnDWzWJJ.js} +2 -2
- package/dist/lib/{markdown-0_p_zfxL.js.map → markdown-KnDWzWJJ.js.map} +1 -1
- package/dist/lib/{orchestrator-D6wm8Sec.js → orchestrator-BEZcqgR5.js} +9 -9
- package/dist/lib/{orchestrator-D6wm8Sec.js.map → orchestrator-BEZcqgR5.js.map} +1 -1
- package/dist/lib/{orchestrator-Dh-hnIpK.js → orchestrator-Cda13e5U.js} +4 -4
- package/dist/lib/{orchestrator-Dh-hnIpK.js.map → orchestrator-Cda13e5U.js.map} +1 -1
- package/dist/lib/{peerjs-BWZnsD0B.js → peerjs-DYkUTcee.js} +5 -5
- package/dist/lib/{peerjs-BWZnsD0B.js.map → peerjs-DYkUTcee.js.map} +1 -1
- package/dist/lib/{push-client-MybDfOdi.js → push-client-CQtCFDaD.js} +3 -3
- package/dist/lib/{push-client-MybDfOdi.js.map → push-client-CQtCFDaD.js.map} +1 -1
- package/dist/lib/security/custom-element-security.d.ts +8 -4
- package/dist/lib/{shadow-claw-PdOpgTxH.js → shadow-claw-DYe9u6Tp.js} +142 -33
- package/dist/lib/shadow-claw-DYe9u6Tp.js.map +1 -0
- package/dist/lib/{shadow-claw-accounts-3bR76EOb.js → shadow-claw-accounts-D2URoHys.js} +2 -2
- package/dist/lib/{shadow-claw-accounts-3bR76EOb.js.map → shadow-claw-accounts-D2URoHys.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-ChdZ1iB8.js → shadow-claw-channel-config-buXoz2xv.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-ChdZ1iB8.js.map → shadow-claw-channel-config-buXoz2xv.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-CK9sc7h_.js → shadow-claw-channels-5yVJ1gLk.js} +3 -3
- package/dist/lib/{shadow-claw-channels-CK9sc7h_.js.map → shadow-claw-channels-5yVJ1gLk.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-B6uuZnJ7.js → shadow-claw-chat-CVc-7ycM.js} +7 -7
- package/dist/lib/{shadow-claw-chat-B6uuZnJ7.js.map → shadow-claw-chat-CVc-7ycM.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-BpRcl312.js → shadow-claw-control-plane-B5vsF91B.js} +6 -6
- package/dist/lib/{shadow-claw-control-plane-BpRcl312.js.map → shadow-claw-control-plane-B5vsF91B.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-Dc_Xk2mB.js → shadow-claw-conversations-PGK5zqn3.js} +54 -12
- package/dist/lib/{shadow-claw-conversations-Dc_Xk2mB.js.map → shadow-claw-conversations-PGK5zqn3.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-C4x1PtB2.js → shadow-claw-file-viewer-DSqn0hgx.js} +6 -6
- package/dist/lib/{shadow-claw-file-viewer-C4x1PtB2.js.map → shadow-claw-file-viewer-DSqn0hgx.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-DhnHbXKf.js → shadow-claw-files-vOa9ZWbr.js} +6 -6
- package/dist/lib/{shadow-claw-files-DhnHbXKf.js.map → shadow-claw-files-vOa9ZWbr.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-BeQ1ElUd.js → shadow-claw-git-D54Z85Bj.js} +4 -4
- package/dist/lib/{shadow-claw-git-BeQ1ElUd.js.map → shadow-claw-git-D54Z85Bj.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-Bbdolk3w.js → shadow-claw-integrations-BTubMH9d.js} +2 -2
- package/dist/lib/{shadow-claw-integrations-Bbdolk3w.js.map → shadow-claw-integrations-BTubMH9d.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-DPda4L3C.js → shadow-claw-llm-8koGTNYs.js} +3 -3
- package/dist/lib/{shadow-claw-llm-DPda4L3C.js.map → shadow-claw-llm-8koGTNYs.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-Dn9c30WI.js → shadow-claw-mcp-remote-C2jwBsQt.js} +2 -2
- package/dist/lib/{shadow-claw-mcp-remote-Dn9c30WI.js.map → shadow-claw-mcp-remote-C2jwBsQt.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-zqNuyn3-.js → shadow-claw-networking-BV-rna16.js} +2 -2
- package/dist/lib/{shadow-claw-networking-zqNuyn3-.js.map → shadow-claw-networking-BV-rna16.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-DFgdo33v.js → shadow-claw-notifications-Dku7VBLk.js} +2 -2
- package/dist/lib/{shadow-claw-notifications-DFgdo33v.js.map → shadow-claw-notifications-Dku7VBLk.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-B7LFGQRf.js → shadow-claw-page-header-VPjYWfsT.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-B7LFGQRf.js.map → shadow-claw-page-header-VPjYWfsT.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-DLspFqdK.js → shadow-claw-pages-DXrM6hgZ.js} +29 -19
- package/dist/lib/shadow-claw-pages-DXrM6hgZ.js.map +1 -0
- package/dist/lib/{shadow-claw-peerjs-D8xWwuWR.js → shadow-claw-peerjs-D3xonez5.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-D8xWwuWR.js.map → shadow-claw-peerjs-D3xonez5.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-NbvzXASD.js → shadow-claw-settings-DMTx6OS9.js} +5 -5
- package/dist/lib/{shadow-claw-settings-NbvzXASD.js.map → shadow-claw-settings-DMTx6OS9.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-B_NAoZmd.js → shadow-claw-storage-BJLnukxa.js} +3 -3
- package/dist/lib/{shadow-claw-storage-B_NAoZmd.js.map → shadow-claw-storage-BJLnukxa.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-BaQJXhYJ.js → shadow-claw-task-server-DcMj20hD.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-BaQJXhYJ.js.map → shadow-claw-task-server-DcMj20hD.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-CAZua0-j.js → shadow-claw-tasks-BQBe4lVi.js} +7 -7
- package/dist/lib/{shadow-claw-tasks-CAZua0-j.js.map → shadow-claw-tasks-BQBe4lVi.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-B2YJhkJL.js → shadow-claw-terminal-DPEzTxXv.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-B2YJhkJL.js.map → shadow-claw-terminal-DPEzTxXv.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-CkZWxxTm.js → shadow-claw-tools-BCnU8FaK.js} +6 -6
- package/dist/lib/{shadow-claw-tools-CkZWxxTm.js.map → shadow-claw-tools-BCnU8FaK.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-BL9cgrP9.js → shadow-claw-webvm-CnW-hoJ8.js} +2 -2
- package/dist/lib/{shadow-claw-webvm-BL9cgrP9.js.map → shadow-claw-webvm-CnW-hoJ8.js.map} +1 -1
- package/dist/lib/subsystems/mcp/webmcp.d.ts +15 -2
- package/dist/lib/{sync-web-mcp-registration-dW2MVCj4.js → sync-web-mcp-registration-DwpGjVdo.js} +10 -4
- package/dist/lib/sync-web-mcp-registration-DwpGjVdo.js.map +1 -0
- package/dist/lib/{tools-D7whby3H.js → tools-CP6G0HF7.js} +5 -6
- package/dist/lib/tools-CP6G0HF7.js.map +1 -0
- package/dist/lib/{trusted-types-d0cdkut_.js → trusted-types-0cu5Dm6M.js} +29 -4
- package/dist/lib/{trusted-types-d0cdkut_.js.map → trusted-types-0cu5Dm6M.js.map} +1 -1
- package/dist/lib/utils/index.d.ts +1 -0
- package/dist/lib/utils/index.js +4 -2
- package/dist/lib/utils/index.js.map +1 -1
- package/dist/lib/utils/resolve-page-script-dependencies.d.ts +19 -0
- package/dist/lib/utils/resolve-page-script-dependencies.js +98 -0
- package/dist/lib/utils/resolve-page-script-dependencies.js.map +1 -0
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/webmcp-Bf9rT7uu.js +2001 -0
- package/dist/lib/webmcp-Bf9rT7uu.js.map +1 -0
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/AGENTS.md +3 -3
- package/dist/public/README.md +1 -1
- package/dist/public/agent.worker.js +2 -2
- package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
- package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
- package/dist/public/components/shadow-claw-conversations/shadow-claw-conversations.css +1 -1
- package/dist/public/components/shadow-claw-conversations/shadow-claw-conversations.html +1 -1
- package/dist/public/{configure-peer-js-WLRM7Qa7.js → configure-peer-js-OYl3V8sW.js} +1 -1
- package/dist/public/{custom-element-security-Cl991z-x.js → custom-element-security-BR4V40j4.js} +1 -1
- package/dist/public/docs/example/article/index.html +3 -3
- package/dist/public/docs/example/article.html +3 -3
- package/dist/public/docs/publishing/index.html +3 -3
- package/dist/public/docs/publishing.html +3 -3
- package/dist/public/docs/skill-creator/index.html +3 -3
- package/dist/public/docs/skill-creator.html +3 -3
- package/dist/public/docs/subsystems/custom-element-security.md +19 -5
- package/dist/public/docs/subsystems/pages.md +10 -2
- package/dist/public/docs/subsystems/tools.md +1 -2
- package/dist/public/docs/subsystems/webmcp.md +41 -6
- package/dist/public/{download-group-file-fFjoIIp4.js → download-group-file-BNmK8wac.js} +1 -1
- package/dist/public/e2e/README.md +2 -2
- package/dist/public/{e2e-bridge-CiSfBRT4.js → e2e-bridge-4phD1JUQ.js} +1 -1
- package/dist/public/{file-viewer-DFC3kfeO.js → file-viewer-Dbh3K9nL.js} +1 -1
- package/dist/public/index.html +3 -3
- package/dist/public/index.js +1 -1
- package/dist/public/{init-control-plane-BEbcwmmv.js → init-control-plane-DGOY_RNS.js} +1 -1
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +3 -3
- package/dist/public/main/memory/index.html +3 -3
- package/dist/public/main/memory.html +3 -3
- package/dist/public/main.html +3 -3
- package/dist/public/{markdown-BeglYzw9.js → markdown-BPZYpfGm.js} +1 -1
- package/dist/public/{orchestrator-BlvEG3PO.js → orchestrator-OoUK7CF9.js} +1 -1
- package/dist/public/{orchestrator-DTNrntd4.js → orchestrator-a9047bGu.js} +1 -1
- package/dist/public/{peerjs-CnBdK94R.js → peerjs-Cbn310Tz.js} +1 -1
- package/dist/public/{push-client-DOHbdCyj.js → push-client-Dy4LGJy9.js} +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/shadow-claw-B-HWC1uN.js +1180 -0
- package/dist/public/{shadow-claw-accounts-C6FXVw2K.js → shadow-claw-accounts-Di-anCZ1.js} +1 -1
- package/dist/public/{shadow-claw-channels-tr_r60Kq.js → shadow-claw-channels-RVwCE8pE.js} +1 -1
- package/dist/public/{shadow-claw-chat-BAAPhAOs.js → shadow-claw-chat-DI1q8UnX.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-C_5VIx8E.js → shadow-claw-control-plane-KXEdLOGl.js} +2 -2
- package/dist/public/{shadow-claw-conversations-DGYZdDa3.js → shadow-claw-conversations-BCBGtS30.js} +40 -6
- package/dist/public/{shadow-claw-file-viewer-5j2-Z3fN.js → shadow-claw-file-viewer-CW3t6j-w.js} +1 -1
- package/dist/public/{shadow-claw-files-DKF4Yy0m.js → shadow-claw-files-JQ-o0ImX.js} +1 -1
- package/dist/public/{shadow-claw-git-CtUTJXKT.js → shadow-claw-git-D2M8VixW.js} +2 -2
- package/dist/public/{shadow-claw-integrations-CuBy9D3z.js → shadow-claw-integrations-Cgw6v9_1.js} +1 -1
- package/dist/public/{shadow-claw-llm-BIfNNjGL.js → shadow-claw-llm-74ieXp7i.js} +1 -1
- package/dist/public/{shadow-claw-mcp-remote-C17FXvrb.js → shadow-claw-mcp-remote-BX0BkSmy.js} +1 -1
- package/dist/public/{shadow-claw-networking-CjxQ1CLI.js → shadow-claw-networking-DNX8vaIQ.js} +1 -1
- package/dist/public/{shadow-claw-notifications-Brn2O3Ps.js → shadow-claw-notifications-BLTaGAny.js} +1 -1
- package/dist/public/{shadow-claw-page-header-Bn1P7Cxb.js → shadow-claw-page-header-CjFOdo9v.js} +1 -1
- package/dist/public/shadow-claw-pages-DOQqMm0_.js +645 -0
- package/dist/public/{shadow-claw-settings-CfDm0hVA.js → shadow-claw-settings-DFtl0Fy8.js} +2 -2
- package/dist/public/{shadow-claw-storage-DOOesRzw.js → shadow-claw-storage-gQGExZRq.js} +1 -1
- package/dist/public/{shadow-claw-task-server-H36iDDp2.js → shadow-claw-task-server-Ca45uY7S.js} +1 -1
- package/dist/public/{shadow-claw-tasks-4JsjSGkN.js → shadow-claw-tasks-CCTaaSdi.js} +1 -1
- package/dist/public/{shadow-claw-terminal-CTRTcYz-.js → shadow-claw-terminal-BfgulLkl.js} +1 -1
- package/dist/public/{shadow-claw-tools-Dm1LxNf4.js → shadow-claw-tools-V8NA4n18.js} +1 -1
- package/dist/public/{shadow-claw-webvm-JzuPe9Nr.js → shadow-claw-webvm-CxxI2bnC.js} +1 -1
- package/dist/public/sync-web-mcp-registration-CkCA8UFm.js +1 -0
- package/dist/public/{tools-FiTtOlIt.js → tools-CwQq5tJH.js} +2 -2
- package/dist/public/webmcp-CmtWOAFm.js +6 -0
- package/dist/server.js +2 -2
- package/package.json +3 -3
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +16 -0
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +62 -1
- package/src/cli/prerender/pretty-paths/prerender-pretty-paths.test.ts +277 -0
- package/src/cli/prerender/pretty-paths/prerender-pretty-paths.ts +28 -1
- package/src/cli/site-config/custom-elements/build-custom-element-script-tags.ts +13 -1
- package/src/components/shadow-claw/shadow-claw.css +12 -0
- package/src/components/shadow-claw/shadow-claw.html +12 -2
- package/src/components/shadow-claw/utils/bind-event-listeners.test.ts +18 -0
- package/src/components/shadow-claw/utils/bind-event-listeners.ts +14 -0
- package/src/components/shadow-claw/utils/get-route.test.ts +35 -0
- package/src/components/shadow-claw/utils/get-route.ts +18 -0
- package/src/components/shadow-claw/utils/sync-slotted-sidebar-active-links.test.ts +152 -1
- package/src/components/shadow-claw/utils/sync-slotted-sidebar-active-links.ts +139 -8
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.css +11 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.html +27 -4
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.ts +32 -4
- package/src/components/shadow-claw-pages/shadow-claw-pages-scripts.test.ts +266 -0
- package/src/components/shadow-claw-pages/shadow-claw-pages.ts +41 -17
- package/src/core/orchestrator/utils/sync-web-mcp-registration.test.ts +18 -1
- package/src/core/orchestrator/utils/sync-web-mcp-registration.ts +8 -0
- package/src/core/utils/init-control-plane.test.ts +9 -4
- package/src/security/custom-element-security.test.ts +65 -0
- package/src/security/custom-element-security.ts +67 -4
- package/src/stores/tools.test.ts +5 -3
- package/src/stores/tools.ts +5 -10
- package/src/subsystems/mcp/webmcp.test.ts +84 -13
- package/src/subsystems/mcp/webmcp.ts +73 -10
- package/src/utils/index.ts +1 -0
- package/src/utils/resolve-page-script-dependencies.test.ts +245 -0
- package/src/utils/resolve-page-script-dependencies.ts +198 -0
- package/dist/cli/chunk-B1hufJeO.js +0 -1
- package/dist/cli/chunk-BZpn8npn.js +0 -1
- package/dist/cli/chunk-Bw5aOTFr.js +0 -1
- package/dist/cli/chunk-C8nH01Gw.js +0 -32
- package/dist/cli/chunk-CK1ZoyoE.js +0 -17
- package/dist/cli/chunk-CVkhDr-7.js +0 -1
- package/dist/cli/chunk-Cum07Vlh.js +0 -1
- package/dist/cli/chunk-D3UhDDmV.js +0 -2
- package/dist/cli/chunk-D8GI6qj6.js +0 -1
- package/dist/cli/chunk-DEH-FvmP.js +0 -1
- package/dist/cli/chunk-DksvJ_kf.js +0 -1
- package/dist/cli/chunk-RGb9wM4R.js +0 -1
- package/dist/lib/shadow-claw-PdOpgTxH.js.map +0 -1
- package/dist/lib/shadow-claw-pages-DLspFqdK.js.map +0 -1
- package/dist/lib/sync-web-mcp-registration-dW2MVCj4.js.map +0 -1
- package/dist/lib/tools-D7whby3H.js.map +0 -1
- package/dist/lib/webmcp-CYTNZfkn.js +0 -1687
- package/dist/lib/webmcp-CYTNZfkn.js.map +0 -1
- package/dist/public/shadow-claw-CnNX-WCh.js +0 -1158
- package/dist/public/shadow-claw-pages-BwOOqRWW.js +0 -645
- package/dist/public/sync-web-mcp-registration-BnHVUjat.js +0 -1
- package/dist/public/webmcp-_TBqXlVW.js +0 -1
|
@@ -0,0 +1,2001 @@
|
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-DwyhSnWe.js";
|
|
2
|
+
import { o as DEFAULT_GROUP_ID } from "./config-Dalol4GN.js";
|
|
3
|
+
import { t as TOOL_DEFINITIONS } from "./tools-tRG7Gu0x.js";
|
|
4
|
+
import "./tools-Bj8UJ_6L.js";
|
|
5
|
+
|
|
6
|
+
//#region node_modules/@mcp-b/webmcp-polyfill/dist/index.js
|
|
7
|
+
const annotationNames = [
|
|
8
|
+
"consequentialHint",
|
|
9
|
+
"debugging",
|
|
10
|
+
"readOnlyHint",
|
|
11
|
+
"untrustedContentHint"
|
|
12
|
+
];
|
|
13
|
+
const protocol = "webmcp-polyfill";
|
|
14
|
+
const deadline = 500;
|
|
15
|
+
const NativeDOMException$1 = globalThis.DOMException;
|
|
16
|
+
var FrameBridge = class {
|
|
17
|
+
#document;
|
|
18
|
+
#window;
|
|
19
|
+
#handlers;
|
|
20
|
+
#knownPeers = /* @__PURE__ */ new Set();
|
|
21
|
+
#unanswered = /* @__PURE__ */ new Set();
|
|
22
|
+
#started = Promise.withResolvers();
|
|
23
|
+
#handshakes = /* @__PURE__ */ new Map();
|
|
24
|
+
#sessions = /* @__PURE__ */ new Set();
|
|
25
|
+
#monitor;
|
|
26
|
+
#hidden = false;
|
|
27
|
+
constructor(view, handlers) {
|
|
28
|
+
this.#window = view;
|
|
29
|
+
this.#document = view.document;
|
|
30
|
+
this.#handlers = handlers;
|
|
31
|
+
this.#window.addEventListener("message", (event) => this.#receive(event), true);
|
|
32
|
+
this.#window.addEventListener("pagehide", () => {
|
|
33
|
+
this.#hidden = true;
|
|
34
|
+
for (const session of this.#sessions) session.end();
|
|
35
|
+
});
|
|
36
|
+
this.#window.addEventListener("pageshow", (event) => {
|
|
37
|
+
if (event.persisted) {
|
|
38
|
+
this.#hidden = false;
|
|
39
|
+
this.#announce();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
for (const frame of this.#announce()) this.#unanswered.add(frame);
|
|
43
|
+
if (this.#unanswered.size === 0) this.#started.resolve();
|
|
44
|
+
else setTimeout(() => {
|
|
45
|
+
this.#unanswered.clear();
|
|
46
|
+
this.#started.resolve();
|
|
47
|
+
}, deadline);
|
|
48
|
+
}
|
|
49
|
+
async allowed() {
|
|
50
|
+
if (this.#hidden) return false;
|
|
51
|
+
return this.#documentAllowed(this.#document);
|
|
52
|
+
}
|
|
53
|
+
async getTools(fromOrigins) {
|
|
54
|
+
await this.#started.promise;
|
|
55
|
+
const ownOrigin = this.#window.origin;
|
|
56
|
+
const origins = [ownOrigin, ...fromOrigins];
|
|
57
|
+
return (await Promise.all(this.#participants().map(async (peer) => {
|
|
58
|
+
if (peer === this.#window) return this.#handlers.getTools(ownOrigin).map((metadata) => toRegisteredTool(metadata, this.#window, ownOrigin));
|
|
59
|
+
try {
|
|
60
|
+
const reply = await this.#request(peer, origins, { kind: "getTools" });
|
|
61
|
+
if (!Array.isArray(reply.value)) throw new TypeError("Expected a tool list from the frame");
|
|
62
|
+
return reply.value.map((value) => toRegisteredTool(readToolMetadata(value), peer, reply.origin));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!(error instanceof NativeDOMException$1 && error.name === "UnknownError")) console.warn("WebMCP: could not read tools from a frame.", error);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}))).flat();
|
|
68
|
+
}
|
|
69
|
+
async execute(target, expectedOrigin, name, serializedInput, signal) {
|
|
70
|
+
const reply = await this.#request(target, [expectedOrigin], {
|
|
71
|
+
kind: "execute",
|
|
72
|
+
name,
|
|
73
|
+
input: serializedInput
|
|
74
|
+
}, signal);
|
|
75
|
+
if (typeof reply.value !== "string") throw executionError();
|
|
76
|
+
return reply.value;
|
|
77
|
+
}
|
|
78
|
+
async notify(exposedTo) {
|
|
79
|
+
await this.#started.promise;
|
|
80
|
+
const origins = [this.#window.origin, ...exposedTo];
|
|
81
|
+
for (const peer of this.#participants()) {
|
|
82
|
+
if (peer === this.#window) {
|
|
83
|
+
await this.#handlers.changed();
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
await this.#request(peer, origins, { kind: "changed" });
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
#request(target, origins, request, signal) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
if (this.#hidden || !this.#related(target)) {
|
|
95
|
+
reject(executionError());
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const id = crypto.randomUUID();
|
|
99
|
+
const { port1, port2 } = new MessageChannel();
|
|
100
|
+
let peerOrigin;
|
|
101
|
+
const close = () => {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
stopWatching();
|
|
104
|
+
signal?.removeEventListener("abort", abort);
|
|
105
|
+
this.#handshakes.delete(id);
|
|
106
|
+
port1.close();
|
|
107
|
+
};
|
|
108
|
+
const fail = (error = executionError()) => {
|
|
109
|
+
port1.postMessage({ kind: "cancel" });
|
|
110
|
+
close();
|
|
111
|
+
reject(error);
|
|
112
|
+
};
|
|
113
|
+
const abort = () => fail(signal?.reason);
|
|
114
|
+
const expire = () => {
|
|
115
|
+
if (peerOrigin === void 0) this.#forget(target);
|
|
116
|
+
fail();
|
|
117
|
+
};
|
|
118
|
+
const connected = (event) => {
|
|
119
|
+
if (event.source !== target) return;
|
|
120
|
+
this.#handshakes.delete(id);
|
|
121
|
+
if (origins && !origins.includes(event.origin)) {
|
|
122
|
+
fail();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
peerOrigin = event.origin;
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
if (request.kind !== "execute" && request.kind !== "changed") timer = setTimeout(expire, deadline);
|
|
128
|
+
port1.postMessage(request);
|
|
129
|
+
};
|
|
130
|
+
let timer = setTimeout(expire, deadline);
|
|
131
|
+
const stopWatching = this.#watch(target, fail);
|
|
132
|
+
port1.onmessage = (event) => {
|
|
133
|
+
const reply = record(event.data);
|
|
134
|
+
if (reply?.kind === "error") {
|
|
135
|
+
fail();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (reply?.kind !== "result" || peerOrigin === void 0) return;
|
|
139
|
+
close();
|
|
140
|
+
resolve({
|
|
141
|
+
value: reply.value,
|
|
142
|
+
origin: peerOrigin
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
port1.onmessageerror = () => fail();
|
|
146
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
147
|
+
this.#handshakes.set(id, connected);
|
|
148
|
+
try {
|
|
149
|
+
target.postMessage(`${protocol}:connect:${id}`, "*", [port2]);
|
|
150
|
+
} catch {
|
|
151
|
+
port2.close();
|
|
152
|
+
fail();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
#receive(event) {
|
|
157
|
+
const message = readWindowMessage(event.data);
|
|
158
|
+
if (!message) return;
|
|
159
|
+
event.stopImmediatePropagation();
|
|
160
|
+
const source = event.source;
|
|
161
|
+
if (!event.isTrusted || !source || !isWindow(source) || !this.#related(source) || event.origin === "null" || this.#hidden) return;
|
|
162
|
+
if (message.kind === "announce") {
|
|
163
|
+
const isNew = !this.#knownPeers.has(source);
|
|
164
|
+
this.#learn(source);
|
|
165
|
+
source.postMessage(`${protocol}:present`, event.origin);
|
|
166
|
+
if (!isNew || this.#window !== this.#window.top) return;
|
|
167
|
+
for (const peer of this.#knownPeers) {
|
|
168
|
+
if (peer === source) continue;
|
|
169
|
+
peer.postMessage(`${protocol}:refresh`, "*");
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (message.kind === "present") {
|
|
174
|
+
this.#learn(source);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (message.kind === "refresh") {
|
|
178
|
+
if (source === this.#window.top) this.#announce();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (message.kind === "ready") {
|
|
182
|
+
this.#handshakes.get(message.id)?.(event);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const [port] = event.ports;
|
|
186
|
+
if (message.kind !== "connect" || !message.id || !port || event.ports.length !== 1) return;
|
|
187
|
+
this.#learn(source);
|
|
188
|
+
this.#accept(source, event.origin, message.id, port);
|
|
189
|
+
}
|
|
190
|
+
#accept(source, origin, id, port) {
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
let requested = false;
|
|
193
|
+
const close = () => {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
stopWatching();
|
|
196
|
+
port.close();
|
|
197
|
+
};
|
|
198
|
+
const cancel = () => {
|
|
199
|
+
controller.abort();
|
|
200
|
+
close();
|
|
201
|
+
};
|
|
202
|
+
const fail = () => {
|
|
203
|
+
port.postMessage({ kind: "error" });
|
|
204
|
+
cancel();
|
|
205
|
+
};
|
|
206
|
+
const timer = setTimeout(fail, deadline);
|
|
207
|
+
const stopWatching = this.#watch(source, fail);
|
|
208
|
+
port.onmessageerror = fail;
|
|
209
|
+
port.onmessage = async (event) => {
|
|
210
|
+
const request = record(event.data);
|
|
211
|
+
if (request?.kind === "cancel") {
|
|
212
|
+
cancel();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (requested) return;
|
|
216
|
+
requested = true;
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
try {
|
|
219
|
+
const value = await this.#respond(source, origin, request, controller.signal);
|
|
220
|
+
port.postMessage({
|
|
221
|
+
kind: "result",
|
|
222
|
+
value
|
|
223
|
+
});
|
|
224
|
+
close();
|
|
225
|
+
} catch {
|
|
226
|
+
fail();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
source.postMessage(`${protocol}:ready:${id}`, origin);
|
|
230
|
+
}
|
|
231
|
+
async #respond(source, origin, request, signal) {
|
|
232
|
+
if (!request || !this.#related(source)) throw executionError();
|
|
233
|
+
if (request.kind === "permission") {
|
|
234
|
+
const { childIndex, childOrigin } = request;
|
|
235
|
+
if (typeof childIndex !== "number" || !Number.isInteger(childIndex) || childIndex < 0 || typeof childOrigin !== "string") throw executionError();
|
|
236
|
+
return this.#childPermission(childIndex, childOrigin);
|
|
237
|
+
}
|
|
238
|
+
if (!await this.allowed() || !await this.#sourceAllowed(source, origin)) throw executionError();
|
|
239
|
+
if (!this.#related(source)) throw executionError();
|
|
240
|
+
signal.throwIfAborted();
|
|
241
|
+
if (request.kind === "getTools") return this.#handlers.getTools(origin);
|
|
242
|
+
if (request.kind === "execute" && typeof request.name === "string" && typeof request.input === "string") return this.#handlers.execute(origin, request.name, request.input, signal);
|
|
243
|
+
if (request.kind === "changed") {
|
|
244
|
+
await this.#handlers.changed();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
throw executionError();
|
|
248
|
+
}
|
|
249
|
+
async #childPermission(childIndex, childOrigin) {
|
|
250
|
+
const child = this.#window[childIndex];
|
|
251
|
+
return child !== void 0 && await this.allowed() && childAllowed(this.#document, child, childOrigin);
|
|
252
|
+
}
|
|
253
|
+
async #documentAllowed(owner) {
|
|
254
|
+
const view = activeWindow(owner);
|
|
255
|
+
if (!view) return false;
|
|
256
|
+
const policy = readToolsPolicy(owner);
|
|
257
|
+
if (policy !== void 0) return policy;
|
|
258
|
+
if (view === view.parent) return true;
|
|
259
|
+
const parentOrigin = view.location.ancestorOrigins?.[0];
|
|
260
|
+
const parentDocument = readDocument(view.parent, parentOrigin === void 0 || parentOrigin === view.origin);
|
|
261
|
+
if (!parentDocument) return this.#parentPermission(view, view.origin);
|
|
262
|
+
return childAllowed(parentDocument, view, view.origin) && await this.#documentAllowed(parentDocument);
|
|
263
|
+
}
|
|
264
|
+
async #sourceAllowed(source, origin) {
|
|
265
|
+
const sourceDocument = readDocument(source, origin === this.#window.origin);
|
|
266
|
+
return sourceDocument ? this.#documentAllowed(sourceDocument) : this.#parentPermission(source, origin);
|
|
267
|
+
}
|
|
268
|
+
async #parentPermission(child, origin) {
|
|
269
|
+
const parent = child.parent;
|
|
270
|
+
if (parent === child) return true;
|
|
271
|
+
let childIndex = -1;
|
|
272
|
+
for (let index = 0; index < parent.length; index++) if (parent[index] === child) {
|
|
273
|
+
childIndex = index;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
if (childIndex < 0) return false;
|
|
277
|
+
if (parent === this.#window) return await this.allowed() && childAllowed(this.#document, child, origin);
|
|
278
|
+
await this.#started.promise;
|
|
279
|
+
if (!this.#knownPeers.has(parent)) return false;
|
|
280
|
+
try {
|
|
281
|
+
const reply = await this.#request(parent, void 0, {
|
|
282
|
+
kind: "permission",
|
|
283
|
+
childIndex,
|
|
284
|
+
childOrigin: origin
|
|
285
|
+
});
|
|
286
|
+
return reply.value === true && await this.#sourceAllowed(parent, reply.origin);
|
|
287
|
+
} catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
#related(peer) {
|
|
292
|
+
try {
|
|
293
|
+
return !peer.closed && peer !== this.#window && peer.top === this.#window.top;
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
#tree() {
|
|
299
|
+
if (this.#window.closed) return [];
|
|
300
|
+
for (const peer of this.#knownPeers) if (!this.#related(peer)) this.#knownPeers.delete(peer);
|
|
301
|
+
const candidates = [this.#window, ...this.#knownPeers];
|
|
302
|
+
for (let ancestor = this.#window.parent; ancestor !== ancestor.parent; ancestor = ancestor.parent) candidates.push(ancestor);
|
|
303
|
+
const tree = /* @__PURE__ */ new Set();
|
|
304
|
+
const pending = [this.#window.top];
|
|
305
|
+
while (pending.length) {
|
|
306
|
+
const frame = pending.pop();
|
|
307
|
+
if (tree.has(frame)) continue;
|
|
308
|
+
tree.add(frame);
|
|
309
|
+
const children = [];
|
|
310
|
+
for (let index = 0; index < frame.length; index++) {
|
|
311
|
+
const child = frame[index];
|
|
312
|
+
if (child) children.push(child);
|
|
313
|
+
}
|
|
314
|
+
for (const candidate of candidates) if (candidate !== frame && candidate.parent === frame && !children.includes(candidate)) children.push(candidate);
|
|
315
|
+
pending.push(...children.reverse());
|
|
316
|
+
}
|
|
317
|
+
return Array.from(tree).filter((frame) => frame === this.#window || this.#related(frame));
|
|
318
|
+
}
|
|
319
|
+
#participants() {
|
|
320
|
+
return this.#tree().filter((frame) => frame === this.#window || this.#knownPeers.has(frame));
|
|
321
|
+
}
|
|
322
|
+
#announce() {
|
|
323
|
+
const others = this.#tree().filter((frame) => frame !== this.#window);
|
|
324
|
+
for (const frame of others) frame.postMessage(`${protocol}:announce`, "*");
|
|
325
|
+
return others;
|
|
326
|
+
}
|
|
327
|
+
#learn(peer) {
|
|
328
|
+
this.#knownPeers.add(peer);
|
|
329
|
+
this.#unanswered.delete(peer);
|
|
330
|
+
if (this.#unanswered.size === 0) this.#started.resolve();
|
|
331
|
+
}
|
|
332
|
+
#forget(peer) {
|
|
333
|
+
this.#knownPeers.delete(peer);
|
|
334
|
+
if (this.#related(peer)) peer.postMessage(`${protocol}:announce`, "*");
|
|
335
|
+
}
|
|
336
|
+
#watch(peer, end) {
|
|
337
|
+
const session = {
|
|
338
|
+
peer,
|
|
339
|
+
end
|
|
340
|
+
};
|
|
341
|
+
this.#sessions.add(session);
|
|
342
|
+
if (this.#monitor === void 0) this.#monitor = this.#window.setInterval(() => {
|
|
343
|
+
for (const open of this.#sessions) if (!this.#related(open.peer)) open.end();
|
|
344
|
+
}, 100);
|
|
345
|
+
return () => {
|
|
346
|
+
this.#sessions.delete(session);
|
|
347
|
+
if (this.#sessions.size === 0) {
|
|
348
|
+
this.#window.clearInterval(this.#monitor);
|
|
349
|
+
this.#monitor = void 0;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function childAllowed(owner, child, origin) {
|
|
355
|
+
const ownerOrigin = owner.defaultView?.origin;
|
|
356
|
+
const frame = findFrame(owner, child, origin === ownerOrigin);
|
|
357
|
+
if (!frame) return false;
|
|
358
|
+
const directive = (frame.getAttribute("allow") ?? "").split(";").map((part) => part.trim().split(/\s+/u)).find(([name]) => name === "tools");
|
|
359
|
+
if (!directive) return origin === ownerOrigin;
|
|
360
|
+
const sources = directive.length === 1 ? ["'src'"] : directive.slice(1);
|
|
361
|
+
if (sources.includes("'none'")) return false;
|
|
362
|
+
const src = frame.hasAttribute("srcdoc") ? null : frame.getAttribute("src");
|
|
363
|
+
const srcURL = src ? URL.parse(src, owner.baseURI) : null;
|
|
364
|
+
const sourceOrigin = !src || srcURL?.protocol === "about:" ? ownerOrigin : srcURL?.origin;
|
|
365
|
+
return sources.some((source) => source === "*" || source === "'self'" && origin === ownerOrigin || source === "'src'" && origin === sourceOrigin || source === origin);
|
|
366
|
+
}
|
|
367
|
+
function readDocument(view, sameOrigin) {
|
|
368
|
+
if (!sameOrigin) return;
|
|
369
|
+
try {
|
|
370
|
+
return view.document;
|
|
371
|
+
} catch {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function findFrame(owner, child, sameOrigin) {
|
|
376
|
+
if (sameOrigin) try {
|
|
377
|
+
const frame = child.frameElement;
|
|
378
|
+
if (frame?.ownerDocument === owner) return frame;
|
|
379
|
+
} catch {}
|
|
380
|
+
return searchFrames(owner, child);
|
|
381
|
+
}
|
|
382
|
+
function searchFrames(root, child) {
|
|
383
|
+
const frames = root.querySelectorAll("iframe, frame");
|
|
384
|
+
for (const frame of frames) if (frame.contentWindow === child) return frame;
|
|
385
|
+
for (const element of root.querySelectorAll("*")) {
|
|
386
|
+
if (!element.shadowRoot) continue;
|
|
387
|
+
const frame = searchFrames(element.shadowRoot, child);
|
|
388
|
+
if (frame) return frame;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function activeWindow(owner) {
|
|
392
|
+
const view = owner.defaultView;
|
|
393
|
+
return view && !view.closed && view.document === owner ? view : void 0;
|
|
394
|
+
}
|
|
395
|
+
function readToolsPolicy(owner) {
|
|
396
|
+
const source = owner;
|
|
397
|
+
const policy = record(source.permissionsPolicy ?? source.featurePolicy);
|
|
398
|
+
if (typeof policy?.features !== "function" || typeof policy.allowsFeature !== "function") return;
|
|
399
|
+
const features = policy.features();
|
|
400
|
+
return Array.isArray(features) && features.includes("tools") ? Boolean(policy.allowsFeature("tools")) : void 0;
|
|
401
|
+
}
|
|
402
|
+
function toRegisteredTool(metadata, owner, origin) {
|
|
403
|
+
const inputSchema = metadata.serializedSchema === void 0 ? void 0 : JSON.parse(metadata.serializedSchema);
|
|
404
|
+
return {
|
|
405
|
+
...metadata.annotations !== void 0 && { annotations: { ...metadata.annotations } },
|
|
406
|
+
description: metadata.description,
|
|
407
|
+
...inputSchema !== void 0 && { inputSchema },
|
|
408
|
+
name: metadata.name,
|
|
409
|
+
origin,
|
|
410
|
+
title: metadata.title,
|
|
411
|
+
window: owner
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function readToolMetadata(value) {
|
|
415
|
+
const tool = record(value);
|
|
416
|
+
if (!tool || typeof tool.name !== "string" || typeof tool.title !== "string" || typeof tool.description !== "string" || tool.serializedSchema !== void 0 && typeof tool.serializedSchema !== "string") throw new TypeError("Invalid tool metadata from the frame");
|
|
417
|
+
const metadata = {
|
|
418
|
+
name: tool.name,
|
|
419
|
+
title: tool.title,
|
|
420
|
+
description: tool.description,
|
|
421
|
+
annotations: void 0,
|
|
422
|
+
serializedSchema: tool.serializedSchema
|
|
423
|
+
};
|
|
424
|
+
if (tool.annotations === void 0) return metadata;
|
|
425
|
+
const hints = record(tool.annotations);
|
|
426
|
+
if (!hints) throw new TypeError("Invalid tool annotations from the frame");
|
|
427
|
+
const annotations = {};
|
|
428
|
+
for (const name of annotationNames) {
|
|
429
|
+
const hint = hints[name];
|
|
430
|
+
if (hint === void 0) continue;
|
|
431
|
+
if (typeof hint !== "boolean") throw new TypeError(`Invalid ${name} annotation from the frame`);
|
|
432
|
+
annotations[name] = hint;
|
|
433
|
+
}
|
|
434
|
+
metadata.annotations = annotations;
|
|
435
|
+
return metadata;
|
|
436
|
+
}
|
|
437
|
+
function readWindowMessage(value) {
|
|
438
|
+
if (typeof value !== "string") return;
|
|
439
|
+
const [namespace, kind = "", id = "", extra] = value.split(":");
|
|
440
|
+
return namespace === protocol && extra === void 0 ? {
|
|
441
|
+
kind,
|
|
442
|
+
id
|
|
443
|
+
} : void 0;
|
|
444
|
+
}
|
|
445
|
+
function record(value) {
|
|
446
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
447
|
+
return value;
|
|
448
|
+
}
|
|
449
|
+
function isWindow(value) {
|
|
450
|
+
try {
|
|
451
|
+
return "postMessage" in value && "window" in value;
|
|
452
|
+
} catch {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function executionError() {
|
|
457
|
+
return new NativeDOMException$1("Tool execution failed", "UnknownError");
|
|
458
|
+
}
|
|
459
|
+
const agentInvokedEvents = /* @__PURE__ */ new WeakSet();
|
|
460
|
+
const agentResponses = /* @__PURE__ */ new WeakMap();
|
|
461
|
+
const activeSubmissions = /* @__PURE__ */ new WeakMap();
|
|
462
|
+
function isAgentInvokedSubmitEvent(event) {
|
|
463
|
+
return event.isTrusted && (agentInvokedEvents.has(event) || event.eventPhase !== Event.NONE && event.target instanceof HTMLFormElement && activeSubmissions.has(event.target));
|
|
464
|
+
}
|
|
465
|
+
function respondWithAgentSubmitEvent(event, agentResponse) {
|
|
466
|
+
if (!isAgentInvokedSubmitEvent(event)) throw new DOMException("respondWith() is only available during an agent-invoked submit event", "InvalidStateError");
|
|
467
|
+
if (!event.defaultPrevented) throw new DOMException("respondWith() requires preventDefault() during an agent-invoked submit event", "InvalidStateError");
|
|
468
|
+
if (event.eventPhase === Event.NONE) throw new DOMException("respondWith() is only available while the submit event is being dispatched", "InvalidStateError");
|
|
469
|
+
agentInvokedEvents.add(event);
|
|
470
|
+
const response = Promise.resolve(agentResponse);
|
|
471
|
+
agentResponses.set(event, response);
|
|
472
|
+
if (event.target instanceof HTMLFormElement) activeSubmissions.get(event.target)?.respond(event);
|
|
473
|
+
}
|
|
474
|
+
const TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
475
|
+
"email",
|
|
476
|
+
"password",
|
|
477
|
+
"search",
|
|
478
|
+
"tel",
|
|
479
|
+
"text",
|
|
480
|
+
"url"
|
|
481
|
+
]);
|
|
482
|
+
const READONLY_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
483
|
+
...TEXT_INPUT_TYPES,
|
|
484
|
+
"date",
|
|
485
|
+
"datetime-local",
|
|
486
|
+
"month",
|
|
487
|
+
"number",
|
|
488
|
+
"time",
|
|
489
|
+
"week"
|
|
490
|
+
]);
|
|
491
|
+
function isControl(element) {
|
|
492
|
+
return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement;
|
|
493
|
+
}
|
|
494
|
+
function getFormControls(form) {
|
|
495
|
+
return Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, "elements")?.get?.call(form);
|
|
496
|
+
}
|
|
497
|
+
function getFormAttribute(form, name) {
|
|
498
|
+
return Element.prototype.getAttribute.call(form, name);
|
|
499
|
+
}
|
|
500
|
+
function formHasAttribute(form, name) {
|
|
501
|
+
return Element.prototype.hasAttribute.call(form, name);
|
|
502
|
+
}
|
|
503
|
+
function isConnected(node) {
|
|
504
|
+
return Object.getOwnPropertyDescriptor(Node.prototype, "isConnected")?.get?.call(node);
|
|
505
|
+
}
|
|
506
|
+
function getOpenShadowRoot(element) {
|
|
507
|
+
return Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoot")?.get?.call(element);
|
|
508
|
+
}
|
|
509
|
+
function checkFormValidity(form) {
|
|
510
|
+
return HTMLFormElement.prototype.checkValidity.call(form);
|
|
511
|
+
}
|
|
512
|
+
function requestFormSubmit(form, submitter) {
|
|
513
|
+
HTMLFormElement.prototype.requestSubmit.call(form, submitter);
|
|
514
|
+
}
|
|
515
|
+
function getControls(form) {
|
|
516
|
+
return [...getFormControls(form)].filter((element) => isControl(element) && !element.matches(":disabled") && !("readOnly" in element && element.readOnly && (element instanceof HTMLTextAreaElement || READONLY_INPUT_TYPES.has(element.type))));
|
|
517
|
+
}
|
|
518
|
+
function controlGroups(form) {
|
|
519
|
+
const groups = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const control of getControls(form)) {
|
|
521
|
+
const name = control.name.trim();
|
|
522
|
+
const controls = groups.get(name);
|
|
523
|
+
if (controls) controls.push(control);
|
|
524
|
+
else groups.set(name, [control]);
|
|
525
|
+
}
|
|
526
|
+
return groups;
|
|
527
|
+
}
|
|
528
|
+
function labelText(control) {
|
|
529
|
+
return [...control.labels ?? []].map((label) => {
|
|
530
|
+
const copy = label.cloneNode(true);
|
|
531
|
+
if (!(copy instanceof HTMLElement)) return "";
|
|
532
|
+
copy.querySelectorAll("button, input, meter, output, progress, select, textarea").forEach((element) => element.remove());
|
|
533
|
+
return copy.textContent?.trim() ?? "";
|
|
534
|
+
}).filter(Boolean).join("; ");
|
|
535
|
+
}
|
|
536
|
+
function commonFieldset(form, controls) {
|
|
537
|
+
for (let element = controls[0]?.parentElement; element && element !== form; element = element.parentElement) if (element instanceof HTMLFieldSetElement && controls.every((control) => element.contains(control))) return element;
|
|
538
|
+
}
|
|
539
|
+
function parameterDescription(form, controls) {
|
|
540
|
+
if (controls.length === 1) {
|
|
541
|
+
const control = controls[0];
|
|
542
|
+
if (!control) return void 0;
|
|
543
|
+
return control.getAttribute("toolparamdescription") || labelText(control) || control.getAttribute("aria-description") || void 0;
|
|
544
|
+
}
|
|
545
|
+
return commonFieldset(form, controls)?.getAttribute("toolparamdescription") || void 0;
|
|
546
|
+
}
|
|
547
|
+
function withDescription(schema, form, controls, extra) {
|
|
548
|
+
const description = parameterDescription(form, controls);
|
|
549
|
+
const combined = description && extra ? `${description} (${extra})` : description || extra;
|
|
550
|
+
return combined ? {
|
|
551
|
+
...schema,
|
|
552
|
+
description: combined
|
|
553
|
+
} : schema;
|
|
554
|
+
}
|
|
555
|
+
function validNumberAttribute(input, name) {
|
|
556
|
+
const raw = input.getAttribute(name);
|
|
557
|
+
if (raw === null || raw.trim() === "") return void 0;
|
|
558
|
+
const value = Number(raw);
|
|
559
|
+
return Number.isFinite(value) ? value : void 0;
|
|
560
|
+
}
|
|
561
|
+
function validStep(input, fallback) {
|
|
562
|
+
const step = Number(input.getAttribute("step"));
|
|
563
|
+
return Number.isFinite(step) && step > 0 ? step : fallback;
|
|
564
|
+
}
|
|
565
|
+
function isStepBaseMultiple(stepBase, step) {
|
|
566
|
+
const quotient = stepBase / step;
|
|
567
|
+
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * 16;
|
|
568
|
+
}
|
|
569
|
+
function withPattern(schema, input) {
|
|
570
|
+
const pattern = input.getAttribute("pattern");
|
|
571
|
+
if (pattern === null) return schema;
|
|
572
|
+
try {
|
|
573
|
+
new RegExp(pattern, "v");
|
|
574
|
+
} catch {
|
|
575
|
+
return schema;
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
...schema,
|
|
579
|
+
pattern
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function numberSchema(input) {
|
|
583
|
+
const schema = { type: "number" };
|
|
584
|
+
const minimum = validNumberAttribute(input, "min");
|
|
585
|
+
const maximum = validNumberAttribute(input, "max");
|
|
586
|
+
if (minimum !== void 0) schema.minimum = minimum;
|
|
587
|
+
if (maximum !== void 0) schema.maximum = maximum;
|
|
588
|
+
if (input.getAttribute("step") !== "any") {
|
|
589
|
+
const step = validStep(input, 1);
|
|
590
|
+
const rawValue = Number(input.getAttribute("value"));
|
|
591
|
+
if (isStepBaseMultiple(minimum ?? (Number.isFinite(rawValue) ? rawValue : 0), step)) schema.multipleOf = step;
|
|
592
|
+
}
|
|
593
|
+
return schema;
|
|
594
|
+
}
|
|
595
|
+
function temporalFormat(input, datePrefix) {
|
|
596
|
+
const step = validStep(input, 60);
|
|
597
|
+
if (step < 1) return `${datePrefix}(:[0-5][0-9](\\.[0-9]{1,3})?)?$`;
|
|
598
|
+
if (step < 60) return `${datePrefix}(:[0-5][0-9])?$`;
|
|
599
|
+
return `${datePrefix}$`;
|
|
600
|
+
}
|
|
601
|
+
function optionSchemas(options) {
|
|
602
|
+
return {
|
|
603
|
+
anyOf: options.map((option) => ({
|
|
604
|
+
type: "string",
|
|
605
|
+
const: option.value,
|
|
606
|
+
title: option.textContent ?? ""
|
|
607
|
+
})),
|
|
608
|
+
enum: options.map((option) => option.value)
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function groupChoiceSchemas(controls) {
|
|
612
|
+
return {
|
|
613
|
+
anyOf: controls.map((control) => {
|
|
614
|
+
const title = labelText(control);
|
|
615
|
+
const schema = {
|
|
616
|
+
type: "string",
|
|
617
|
+
const: control.value
|
|
618
|
+
};
|
|
619
|
+
if (title) schema.title = title;
|
|
620
|
+
return schema;
|
|
621
|
+
}),
|
|
622
|
+
enum: controls.map((control) => control.value)
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function isInput(control, type) {
|
|
626
|
+
return control instanceof HTMLInputElement && control.type === type;
|
|
627
|
+
}
|
|
628
|
+
function inputSchema(input) {
|
|
629
|
+
if (TEXT_INPUT_TYPES.has(input.type)) return withPattern({ type: "string" }, input);
|
|
630
|
+
switch (input.type) {
|
|
631
|
+
case "hidden": return input.getAttribute("toolparamdescription") ? { type: "string" } : void 0;
|
|
632
|
+
case "number": return withPattern(numberSchema(input), input);
|
|
633
|
+
case "range": {
|
|
634
|
+
const schema = numberSchema(input);
|
|
635
|
+
schema.minimum ??= 0;
|
|
636
|
+
schema.maximum ??= 100;
|
|
637
|
+
return schema;
|
|
638
|
+
}
|
|
639
|
+
case "checkbox": return { type: "boolean" };
|
|
640
|
+
case "date": return {
|
|
641
|
+
type: "string",
|
|
642
|
+
format: "date"
|
|
643
|
+
};
|
|
644
|
+
case "month": return {
|
|
645
|
+
type: "string",
|
|
646
|
+
format: "^[0-9]{4}-(0[1-9]|1[0-2])$"
|
|
647
|
+
};
|
|
648
|
+
case "week": return {
|
|
649
|
+
type: "string",
|
|
650
|
+
format: "^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$"
|
|
651
|
+
};
|
|
652
|
+
case "time": return {
|
|
653
|
+
type: "string",
|
|
654
|
+
format: temporalFormat(input, "^([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
655
|
+
};
|
|
656
|
+
case "datetime-local": return {
|
|
657
|
+
type: "string",
|
|
658
|
+
format: temporalFormat(input, "^[0-9]{4}-(0[1-9]|1[0-2])-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
659
|
+
};
|
|
660
|
+
case "color": return {
|
|
661
|
+
type: "string",
|
|
662
|
+
format: "^#[0-9a-zA-Z]{6}$"
|
|
663
|
+
};
|
|
664
|
+
default: return;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function parameterSchema(form, controls) {
|
|
668
|
+
const [first] = controls;
|
|
669
|
+
if (!first) return void 0;
|
|
670
|
+
let schema;
|
|
671
|
+
if (controls.every((control) => isInput(control, "radio"))) schema = {
|
|
672
|
+
type: "string",
|
|
673
|
+
...groupChoiceSchemas(controls)
|
|
674
|
+
};
|
|
675
|
+
else if (controls.length > 1) {
|
|
676
|
+
if (controls.every((control) => isInput(control, "checkbox"))) schema = {
|
|
677
|
+
type: "array",
|
|
678
|
+
items: {
|
|
679
|
+
type: "string",
|
|
680
|
+
...groupChoiceSchemas(controls)
|
|
681
|
+
},
|
|
682
|
+
uniqueItems: true
|
|
683
|
+
};
|
|
684
|
+
} else if (first instanceof HTMLTextAreaElement) schema = { type: "string" };
|
|
685
|
+
else if (first instanceof HTMLSelectElement) {
|
|
686
|
+
const choices = optionSchemas([...first.options]);
|
|
687
|
+
schema = first.multiple ? {
|
|
688
|
+
type: "array",
|
|
689
|
+
items: {
|
|
690
|
+
type: "string",
|
|
691
|
+
...choices
|
|
692
|
+
},
|
|
693
|
+
uniqueItems: true
|
|
694
|
+
} : {
|
|
695
|
+
type: "string",
|
|
696
|
+
...choices
|
|
697
|
+
};
|
|
698
|
+
} else schema = inputSchema(first);
|
|
699
|
+
const dateHint = first.type === "date" ? "Dates MUST be provided in 'YYYY-MM-DD' format." : void 0;
|
|
700
|
+
return schema && withDescription(schema, form, controls, dateHint);
|
|
701
|
+
}
|
|
702
|
+
function synthesizeSchema(form) {
|
|
703
|
+
const properties = {};
|
|
704
|
+
const required = [];
|
|
705
|
+
for (const [name, controls] of controlGroups(form)) {
|
|
706
|
+
if (!name) continue;
|
|
707
|
+
const schema = parameterSchema(form, controls);
|
|
708
|
+
if (!schema) continue;
|
|
709
|
+
Object.defineProperty(properties, name, {
|
|
710
|
+
configurable: true,
|
|
711
|
+
enumerable: true,
|
|
712
|
+
value: schema,
|
|
713
|
+
writable: true
|
|
714
|
+
});
|
|
715
|
+
if (controls.some((control) => control.required)) required.push(name);
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
type: "object",
|
|
719
|
+
properties,
|
|
720
|
+
required
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function toolDefinition(form) {
|
|
724
|
+
return {
|
|
725
|
+
name: getFormAttribute(form, "toolname") ?? "",
|
|
726
|
+
title: getFormAttribute(form, "tooltitle") ?? "",
|
|
727
|
+
description: getFormAttribute(form, "tooldescription") ?? "",
|
|
728
|
+
inputSchema: synthesizeSchema(form),
|
|
729
|
+
autosubmit: formHasAttribute(form, "toolautosubmit")
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function toFormString(value) {
|
|
733
|
+
if (typeof value === "string" || typeof value === "boolean") return String(value);
|
|
734
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
735
|
+
}
|
|
736
|
+
function toFormBoolean(value) {
|
|
737
|
+
if (typeof value === "boolean") return value;
|
|
738
|
+
if (typeof value === "number" && Number.isInteger(value)) return value !== 0;
|
|
739
|
+
if (typeof value !== "string") return void 0;
|
|
740
|
+
if (value === "1" || value.toLowerCase() === "true") return true;
|
|
741
|
+
if (value === "0" || value.toLowerCase() === "false") return false;
|
|
742
|
+
}
|
|
743
|
+
function hasUniqueAllowedValues(value, allowed) {
|
|
744
|
+
if (!Array.isArray(value)) return false;
|
|
745
|
+
const remaining = new Set(allowed);
|
|
746
|
+
for (const item of value) {
|
|
747
|
+
const string = toFormString(item);
|
|
748
|
+
if (string === void 0 || !remaining.delete(string)) return false;
|
|
749
|
+
}
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
function inputAcceptsValue(input, value) {
|
|
753
|
+
if (value === "") return input.type !== "number" && input.type !== "range";
|
|
754
|
+
const probe = input.ownerDocument.createElement("input");
|
|
755
|
+
probe.type = input.type;
|
|
756
|
+
probe.value = value;
|
|
757
|
+
return probe.value !== "";
|
|
758
|
+
}
|
|
759
|
+
function isFormParameterValue(form, controls, value) {
|
|
760
|
+
const [first] = controls;
|
|
761
|
+
if (!first || !parameterSchema(form, controls)) return false;
|
|
762
|
+
const string = toFormString(value);
|
|
763
|
+
if (controls.every((control) => isInput(control, "radio"))) return controls.some((control) => control.value === string);
|
|
764
|
+
if (controls.length > 1) return hasUniqueAllowedValues(value, new Set(controls.map((control) => control.value)));
|
|
765
|
+
if (first instanceof HTMLSelectElement) {
|
|
766
|
+
const allowed = new Set([...first.options].map((option) => option.value));
|
|
767
|
+
if (first.multiple) return hasUniqueAllowedValues(value, allowed);
|
|
768
|
+
return string !== void 0 && allowed.has(string);
|
|
769
|
+
}
|
|
770
|
+
if (first instanceof HTMLTextAreaElement) return string !== void 0;
|
|
771
|
+
if (first.type === "checkbox") return toFormBoolean(value) !== void 0;
|
|
772
|
+
return string !== void 0 && inputAcceptsValue(first, string);
|
|
773
|
+
}
|
|
774
|
+
function dispatchInputAndChange(control) {
|
|
775
|
+
control.dispatchEvent(new Event("input", {
|
|
776
|
+
bubbles: true,
|
|
777
|
+
composed: true
|
|
778
|
+
}));
|
|
779
|
+
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
780
|
+
}
|
|
781
|
+
function setValue(control, value) {
|
|
782
|
+
if (control.value === value) return;
|
|
783
|
+
const prototype = control instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
784
|
+
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(control, value);
|
|
785
|
+
dispatchInputAndChange(control);
|
|
786
|
+
}
|
|
787
|
+
function setChecked(control, checked) {
|
|
788
|
+
if (control.checked === checked) return;
|
|
789
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set?.call(control, checked);
|
|
790
|
+
dispatchInputAndChange(control);
|
|
791
|
+
}
|
|
792
|
+
function fillParameter(controls, value) {
|
|
793
|
+
const [first] = controls;
|
|
794
|
+
const string = toFormString(value);
|
|
795
|
+
const values = new Set(Array.isArray(value) ? value.map(toFormString) : []);
|
|
796
|
+
if (controls.every((control) => isInput(control, "radio"))) {
|
|
797
|
+
const control = controls.find((candidate) => candidate.value === string);
|
|
798
|
+
if (control) setChecked(control, true);
|
|
799
|
+
} else if (controls.length > 1) {
|
|
800
|
+
for (const control of controls) if (control instanceof HTMLInputElement) setChecked(control, values.has(control.value));
|
|
801
|
+
} else if (first instanceof HTMLSelectElement) {
|
|
802
|
+
if (first.multiple) {
|
|
803
|
+
let changed = false;
|
|
804
|
+
for (const option of first.options) {
|
|
805
|
+
const next = values.has(option.value);
|
|
806
|
+
if (option.selected === next) continue;
|
|
807
|
+
option.selected = next;
|
|
808
|
+
changed = true;
|
|
809
|
+
}
|
|
810
|
+
if (changed) dispatchInputAndChange(first);
|
|
811
|
+
} else if (string !== void 0 && first.value !== string) {
|
|
812
|
+
first.value = string;
|
|
813
|
+
dispatchInputAndChange(first);
|
|
814
|
+
}
|
|
815
|
+
} else if (first && isInput(first, "checkbox")) {
|
|
816
|
+
const checked = toFormBoolean(value);
|
|
817
|
+
if (checked !== void 0) setChecked(first, checked);
|
|
818
|
+
} else if (first && string !== void 0) setValue(first, string);
|
|
819
|
+
}
|
|
820
|
+
function fillForm(form, input) {
|
|
821
|
+
if (Array.isArray(input)) throw executionError();
|
|
822
|
+
const groups = controlGroups(form);
|
|
823
|
+
const parameters = [];
|
|
824
|
+
for (const [name, value] of Object.entries(input)) {
|
|
825
|
+
const controls = groups.get(name);
|
|
826
|
+
if (!controls || !isFormParameterValue(form, controls, value)) throw executionError();
|
|
827
|
+
parameters.push({
|
|
828
|
+
controls,
|
|
829
|
+
value
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
for (const { controls, value } of parameters) fillParameter(controls, value);
|
|
833
|
+
}
|
|
834
|
+
function findSubmitter(form) {
|
|
835
|
+
return [...getFormControls(form)].find((element) => !element.matches(":disabled") && (element instanceof HTMLButtonElement && element.type === "submit" || element instanceof HTMLInputElement && ["image", "submit"].includes(element.type)));
|
|
836
|
+
}
|
|
837
|
+
function lifecycleEvent(type, toolName) {
|
|
838
|
+
const event = new Event(type);
|
|
839
|
+
Object.defineProperty(event, "toolName", {
|
|
840
|
+
enumerable: true,
|
|
841
|
+
value: toolName
|
|
842
|
+
});
|
|
843
|
+
return event;
|
|
844
|
+
}
|
|
845
|
+
function waitForSubmission(context, registration, definition, submitter, signal) {
|
|
846
|
+
const { form } = registration;
|
|
847
|
+
registration.cancelPending?.(executionError());
|
|
848
|
+
return new Promise((resolve, reject) => {
|
|
849
|
+
let settled = false;
|
|
850
|
+
const cleanup = () => {
|
|
851
|
+
EventTarget.prototype.removeEventListener.call(form, "invalid", onInvalid, true);
|
|
852
|
+
signal.removeEventListener("abort", onAbort);
|
|
853
|
+
activeSubmissions.delete(form);
|
|
854
|
+
if (registration.cancelPending === cancel) delete registration.cancelPending;
|
|
855
|
+
};
|
|
856
|
+
const finish = (callback) => {
|
|
857
|
+
if (settled) return;
|
|
858
|
+
settled = true;
|
|
859
|
+
cleanup();
|
|
860
|
+
callback();
|
|
861
|
+
};
|
|
862
|
+
const cancel = (reason) => finish(() => reject(reason));
|
|
863
|
+
const settleResponse = (response) => {
|
|
864
|
+
if (registration.cancelPending === cancel) delete registration.cancelPending;
|
|
865
|
+
response.then((value) => finish(() => resolve(value ?? null)), (cause) => finish(() => reject(cause)));
|
|
866
|
+
};
|
|
867
|
+
const onInvalid = (event) => {
|
|
868
|
+
if (!event.isTrusted) return;
|
|
869
|
+
queueMicrotask(() => {
|
|
870
|
+
if (!checkFormValidity(form)) cancel(executionError());
|
|
871
|
+
});
|
|
872
|
+
};
|
|
873
|
+
const onAbort = () => {
|
|
874
|
+
finish(() => {
|
|
875
|
+
context.dispatchEvent(lifecycleEvent("toolcancel", definition.name));
|
|
876
|
+
reject(signal.reason);
|
|
877
|
+
});
|
|
878
|
+
};
|
|
879
|
+
activeSubmissions.set(form, {
|
|
880
|
+
complete(event) {
|
|
881
|
+
setTimeout(() => {
|
|
882
|
+
const response = agentResponses.get(event);
|
|
883
|
+
if (response) settleResponse(response);
|
|
884
|
+
else if (event.defaultPrevented) cancel(executionError());
|
|
885
|
+
else finish(() => resolve(null));
|
|
886
|
+
});
|
|
887
|
+
},
|
|
888
|
+
direct() {
|
|
889
|
+
finish(() => resolve(null));
|
|
890
|
+
},
|
|
891
|
+
respond(event) {
|
|
892
|
+
queueMicrotask(() => {
|
|
893
|
+
const response = agentResponses.get(event);
|
|
894
|
+
if (response) settleResponse(response);
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
registration.cancelPending = cancel;
|
|
899
|
+
EventTarget.prototype.addEventListener.call(form, "invalid", onInvalid, true);
|
|
900
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
901
|
+
if (!definition.autosubmit) {
|
|
902
|
+
submitter?.focus();
|
|
903
|
+
context.dispatchEvent(lifecycleEvent("toolactivated", definition.name));
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
try {
|
|
907
|
+
requestFormSubmit(form, submitter);
|
|
908
|
+
context.dispatchEvent(lifecycleEvent("toolactivated", definition.name));
|
|
909
|
+
} catch (error) {
|
|
910
|
+
cancel(error);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
/** Adds declarative tools until the vendored upstream implements them. */
|
|
915
|
+
function installWebMCPDeclarativeExtensions(context) {
|
|
916
|
+
const prototype = SubmitEvent.prototype;
|
|
917
|
+
if ("agentInvoked" in prototype && "respondWith" in prototype) return;
|
|
918
|
+
if (!("agentInvoked" in prototype)) Object.defineProperty(prototype, "agentInvoked", {
|
|
919
|
+
configurable: true,
|
|
920
|
+
enumerable: true,
|
|
921
|
+
get() {
|
|
922
|
+
return isAgentInvokedSubmitEvent(this);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
if (!("respondWith" in prototype)) Object.defineProperty(prototype, "respondWith", {
|
|
926
|
+
configurable: true,
|
|
927
|
+
enumerable: true,
|
|
928
|
+
writable: true,
|
|
929
|
+
value(agentResponse) {
|
|
930
|
+
respondWithAgentSubmitEvent(this, agentResponse);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
934
|
+
const blockedDefinitions = /* @__PURE__ */ new Map();
|
|
935
|
+
const observers = /* @__PURE__ */ new Map();
|
|
936
|
+
const onSubmit = (event) => {
|
|
937
|
+
if (!(event instanceof SubmitEvent) || !event.isTrusted || !(event.target instanceof HTMLFormElement)) return;
|
|
938
|
+
const submission = activeSubmissions.get(event.target);
|
|
939
|
+
if (!submission) return;
|
|
940
|
+
agentInvokedEvents.add(event);
|
|
941
|
+
submission.complete(event);
|
|
942
|
+
};
|
|
943
|
+
const onReset = (event) => {
|
|
944
|
+
if (!event.isTrusted || !(event.target instanceof HTMLFormElement)) return;
|
|
945
|
+
const form = event.target;
|
|
946
|
+
setTimeout(() => {
|
|
947
|
+
if (event.defaultPrevented) return;
|
|
948
|
+
registrations.get(form)?.cancelPending?.(executionError());
|
|
949
|
+
});
|
|
950
|
+
};
|
|
951
|
+
function stopObservingRoot(root) {
|
|
952
|
+
observers.get(root)?.disconnect();
|
|
953
|
+
observers.delete(root);
|
|
954
|
+
root.removeEventListener("reset", onReset, true);
|
|
955
|
+
root.removeEventListener("submit", onSubmit, true);
|
|
956
|
+
}
|
|
957
|
+
function observeRoot(root) {
|
|
958
|
+
if (observers.has(root)) return;
|
|
959
|
+
const observer = new MutationObserver(sync);
|
|
960
|
+
observers.set(root, observer);
|
|
961
|
+
observer.observe(root, {
|
|
962
|
+
attributes: true,
|
|
963
|
+
characterData: true,
|
|
964
|
+
childList: true,
|
|
965
|
+
subtree: true
|
|
966
|
+
});
|
|
967
|
+
root.addEventListener("reset", onReset, true);
|
|
968
|
+
root.addEventListener("submit", onSubmit, true);
|
|
969
|
+
}
|
|
970
|
+
function sync() {
|
|
971
|
+
observeRoot(document);
|
|
972
|
+
for (const root of observers.keys()) if (root instanceof ShadowRoot && !root.host.isConnected) stopObservingRoot(root);
|
|
973
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
974
|
+
const selected = /* @__PURE__ */ new Map();
|
|
975
|
+
const selectedByName = /* @__PURE__ */ new Map();
|
|
976
|
+
for (const root of observers.keys()) for (const element of root.querySelectorAll("*")) {
|
|
977
|
+
const shadowRoot = getOpenShadowRoot(element);
|
|
978
|
+
if (shadowRoot) observeRoot(shadowRoot);
|
|
979
|
+
if (!(element instanceof HTMLFormElement) || !formHasAttribute(element, "toolname") || !formHasAttribute(element, "tooldescription") || !isConnected(element)) continue;
|
|
980
|
+
const form = element;
|
|
981
|
+
candidates.add(form);
|
|
982
|
+
const definition = toolDefinition(form);
|
|
983
|
+
const fingerprint = JSON.stringify(definition);
|
|
984
|
+
if (blockedDefinitions.get(form) === fingerprint) continue;
|
|
985
|
+
blockedDefinitions.delete(form);
|
|
986
|
+
const existingForm = selectedByName.get(definition.name);
|
|
987
|
+
if (existingForm) {
|
|
988
|
+
const holdsName = (candidate) => registrations.get(candidate)?.name === definition.name;
|
|
989
|
+
if (!holdsName(form) || holdsName(existingForm)) {
|
|
990
|
+
blockedDefinitions.set(form, fingerprint);
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const existingSelection = selected.get(existingForm);
|
|
994
|
+
if (existingSelection) blockedDefinitions.set(existingForm, existingSelection.fingerprint);
|
|
995
|
+
selected.delete(existingForm);
|
|
996
|
+
}
|
|
997
|
+
selectedByName.set(definition.name, form);
|
|
998
|
+
selected.set(form, {
|
|
999
|
+
definition,
|
|
1000
|
+
fingerprint
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
for (const form of blockedDefinitions.keys()) if (!candidates.has(form)) blockedDefinitions.delete(form);
|
|
1004
|
+
for (const [form, registration] of registrations) if (selected.get(form)?.fingerprint !== registration.fingerprint) {
|
|
1005
|
+
registration.cancelPending?.(executionError());
|
|
1006
|
+
registration.controller.abort();
|
|
1007
|
+
registrations.delete(form);
|
|
1008
|
+
}
|
|
1009
|
+
for (const [form, { definition, fingerprint }] of selected) {
|
|
1010
|
+
if (registrations.has(form)) continue;
|
|
1011
|
+
const controller = new AbortController();
|
|
1012
|
+
const registration = {
|
|
1013
|
+
controller,
|
|
1014
|
+
fingerprint,
|
|
1015
|
+
form,
|
|
1016
|
+
name: definition.name
|
|
1017
|
+
};
|
|
1018
|
+
registrations.set(form, registration);
|
|
1019
|
+
context.registerTool({
|
|
1020
|
+
name: definition.name,
|
|
1021
|
+
title: definition.title,
|
|
1022
|
+
description: definition.description,
|
|
1023
|
+
inputSchema: definition.inputSchema,
|
|
1024
|
+
execute(input, options) {
|
|
1025
|
+
const submitter = findSubmitter(form);
|
|
1026
|
+
if (!definition.autosubmit && !submitter) throw executionError();
|
|
1027
|
+
fillForm(form, input);
|
|
1028
|
+
return waitForSubmission(context, registration, definition, submitter, options.signal);
|
|
1029
|
+
}
|
|
1030
|
+
}, { signal: controller.signal }).catch((cause) => {
|
|
1031
|
+
controller.abort();
|
|
1032
|
+
if (cause instanceof DOMException && cause.name === "AbortError") return;
|
|
1033
|
+
console.error(`[webmcp] declarative form tool "${definition.name}" was not registered:`, cause);
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const attachShadowDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, "attachShadow");
|
|
1038
|
+
if (attachShadowDescriptor?.configurable) {
|
|
1039
|
+
const nativeAttachShadow = Element.prototype.attachShadow;
|
|
1040
|
+
const attachShadow = function(init) {
|
|
1041
|
+
const root = nativeAttachShadow.call(this, init);
|
|
1042
|
+
if (root.mode === "open" && Object.getOwnPropertyDescriptor(Node.prototype, "ownerDocument")?.get?.call(this) === document && isConnected(this)) observeRoot(root);
|
|
1043
|
+
return root;
|
|
1044
|
+
};
|
|
1045
|
+
Object.defineProperty(Element.prototype, "attachShadow", {
|
|
1046
|
+
...attachShadowDescriptor,
|
|
1047
|
+
value: attachShadow
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
const submitDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, "submit");
|
|
1051
|
+
if (submitDescriptor?.configurable) {
|
|
1052
|
+
const nativeSubmit = HTMLFormElement.prototype.submit;
|
|
1053
|
+
const submit = function() {
|
|
1054
|
+
nativeSubmit.call(this);
|
|
1055
|
+
activeSubmissions.get(this)?.direct();
|
|
1056
|
+
};
|
|
1057
|
+
Object.defineProperty(HTMLFormElement.prototype, "submit", {
|
|
1058
|
+
...submitDescriptor,
|
|
1059
|
+
value: submit
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
sync();
|
|
1063
|
+
}
|
|
1064
|
+
/*!
|
|
1065
|
+
* Copyright (c) 2026 WebMCP polyfill contributors
|
|
1066
|
+
* SPDX-License-Identifier: MIT
|
|
1067
|
+
*/
|
|
1068
|
+
const NativeDOMException = globalThis.DOMException;
|
|
1069
|
+
const getWindow = Object.getOwnPropertyDescriptor(globalThis, "window")?.get;
|
|
1070
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
1071
|
+
/**
|
|
1072
|
+
* Install WebMCP in the current window.
|
|
1073
|
+
*
|
|
1074
|
+
* Does nothing outside a secure browser context or when `document.modelContext`
|
|
1075
|
+
* already exists, including partial native implementations. Call before registering
|
|
1076
|
+
* tools in each frame; repeated calls preserve existing contexts and registrations.
|
|
1077
|
+
*
|
|
1078
|
+
* Installing joins cross-frame discovery: the window listens for the polyfill's messages and
|
|
1079
|
+
* announces itself to the other frames of its tree.
|
|
1080
|
+
*
|
|
1081
|
+
* @throws {TypeError} If the window or Document prototype prevents installation.
|
|
1082
|
+
* @example
|
|
1083
|
+
* import { installWebMCP } from "webmcp-polyfill";
|
|
1084
|
+
* installWebMCP();
|
|
1085
|
+
*
|
|
1086
|
+
* @see https://webmachinelearning.github.io/webmcp/#document-extension
|
|
1087
|
+
* @see https://github.com/webmachinelearning/webmcp-polyfill/blob/main/TESTING.md
|
|
1088
|
+
*/
|
|
1089
|
+
function installWebMCP$1() {
|
|
1090
|
+
if (typeof document === "undefined" || !globalThis.isSecureContext) return;
|
|
1091
|
+
if ("modelContext" in document) return;
|
|
1092
|
+
const documentPrototype = Document.prototype;
|
|
1093
|
+
const getDefaultView = Object.getOwnPropertyDescriptor(documentPrototype, "defaultView").get;
|
|
1094
|
+
const constructorDescriptor = Object.getOwnPropertyDescriptor(globalThis, "ModelContext");
|
|
1095
|
+
if (!Object.isExtensible(documentPrototype) || constructorDescriptor && !constructorDescriptor.configurable || !constructorDescriptor && !Object.isExtensible(globalThis)) throw new TypeError("Cannot install WebMCP on this realm");
|
|
1096
|
+
Object.defineProperty(globalThis, "ModelContext", {
|
|
1097
|
+
value: modelContextConstructor,
|
|
1098
|
+
configurable: true,
|
|
1099
|
+
writable: true
|
|
1100
|
+
});
|
|
1101
|
+
const { getModelContext } = { getModelContext() {
|
|
1102
|
+
getDefaultView.call(this);
|
|
1103
|
+
let context = contexts.get(this);
|
|
1104
|
+
if (!context) {
|
|
1105
|
+
context = new ModelContextPolyfill(this);
|
|
1106
|
+
contexts.set(this, context);
|
|
1107
|
+
}
|
|
1108
|
+
return context;
|
|
1109
|
+
} };
|
|
1110
|
+
Object.defineProperty(getModelContext, "name", { value: "get modelContext" });
|
|
1111
|
+
Object.defineProperty(documentPrototype, "modelContext", {
|
|
1112
|
+
configurable: true,
|
|
1113
|
+
enumerable: true,
|
|
1114
|
+
get: getModelContext
|
|
1115
|
+
});
|
|
1116
|
+
document.modelContext;
|
|
1117
|
+
}
|
|
1118
|
+
var ModelContextPolyfill = class extends EventTarget {
|
|
1119
|
+
#document;
|
|
1120
|
+
#tools = /* @__PURE__ */ new Map();
|
|
1121
|
+
#frames;
|
|
1122
|
+
#toolchangeHandler = null;
|
|
1123
|
+
#toolchangeListener = (event) => {
|
|
1124
|
+
const handler = this.#toolchangeHandler;
|
|
1125
|
+
if (typeof handler !== "function") return;
|
|
1126
|
+
if (Reflect.apply(handler, this, [event]) === false) Event.prototype.preventDefault.call(event);
|
|
1127
|
+
};
|
|
1128
|
+
constructor(owner) {
|
|
1129
|
+
super();
|
|
1130
|
+
this.#document = owner;
|
|
1131
|
+
const view = activeWindow(owner);
|
|
1132
|
+
if (!view) return;
|
|
1133
|
+
this.#frames = new FrameBridge(view, {
|
|
1134
|
+
getTools: (callerOrigin) => this.#exposedTools(callerOrigin),
|
|
1135
|
+
execute: (callerOrigin, name, serializedInput, signal) => {
|
|
1136
|
+
const ownerOrigin = requireActiveWindow(owner).origin;
|
|
1137
|
+
return this.#executeLocal(name, serializedInput, ownerOrigin, callerOrigin, signal);
|
|
1138
|
+
},
|
|
1139
|
+
changed: () => this.#queueToolChange()
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
get ontoolchange() {
|
|
1143
|
+
return this.#toolchangeHandler;
|
|
1144
|
+
}
|
|
1145
|
+
set ontoolchange(handler) {
|
|
1146
|
+
const nextHandler = isObject(handler) ? handler : null;
|
|
1147
|
+
if (!this.#toolchangeHandler && nextHandler) this.addEventListener("toolchange", this.#toolchangeListener);
|
|
1148
|
+
if (this.#toolchangeHandler && !nextHandler) this.removeEventListener("toolchange", this.#toolchangeListener);
|
|
1149
|
+
this.#toolchangeHandler = nextHandler;
|
|
1150
|
+
}
|
|
1151
|
+
async registerTool(tool, options = {}) {
|
|
1152
|
+
const ownerDocument = this.#document;
|
|
1153
|
+
const { name, title, description, annotations, inputSchema, execute } = readToolDefinition(tool);
|
|
1154
|
+
const settings = readDictionary(options);
|
|
1155
|
+
const exposedTo = readOriginSequence(settings.exposedTo);
|
|
1156
|
+
const registrationSignal = readAbortSignal(settings.signal);
|
|
1157
|
+
requireActiveWindow(ownerDocument);
|
|
1158
|
+
const requireUnusedName = () => {
|
|
1159
|
+
if (this.#tools.has(name)) throw new NativeDOMException(`A tool named ${name} is already registered`, "InvalidStateError");
|
|
1160
|
+
};
|
|
1161
|
+
requireUnusedName();
|
|
1162
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/u.test(name)) throw new NativeDOMException(`Tool names are 1 to 128 characters of ASCII alphanumerics, "_", "-" or ".": ${name}`, "InvalidStateError");
|
|
1163
|
+
if (!description) throw new NativeDOMException("A tool description cannot be empty", "InvalidStateError");
|
|
1164
|
+
const serializedSchema = inputSchema === void 0 ? void 0 : serializeJSON(inputSchema);
|
|
1165
|
+
registrationSignal?.throwIfAborted();
|
|
1166
|
+
const exposedOrigins = parseOrigins(exposedTo);
|
|
1167
|
+
const storedTool = {
|
|
1168
|
+
metadata: {
|
|
1169
|
+
name,
|
|
1170
|
+
title,
|
|
1171
|
+
description,
|
|
1172
|
+
annotations,
|
|
1173
|
+
serializedSchema
|
|
1174
|
+
},
|
|
1175
|
+
execute,
|
|
1176
|
+
exposedTo: exposedOrigins
|
|
1177
|
+
};
|
|
1178
|
+
const frames = await this.#requireFrames();
|
|
1179
|
+
registrationSignal?.throwIfAborted();
|
|
1180
|
+
requireUnusedName();
|
|
1181
|
+
return new Promise((resolve, reject) => {
|
|
1182
|
+
registrationSignal?.addEventListener("abort", () => {
|
|
1183
|
+
this.#tools.delete(name);
|
|
1184
|
+
frames.notify(exposedOrigins);
|
|
1185
|
+
reject(registrationSignal.reason);
|
|
1186
|
+
}, { once: true });
|
|
1187
|
+
this.#tools.set(name, storedTool);
|
|
1188
|
+
frames.notify(exposedOrigins).then(resolve, reject);
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
async getTools(options = {}) {
|
|
1192
|
+
const ownerDocument = this.#document;
|
|
1193
|
+
const fromOrigins = readOriginSequence(readDictionary(options).fromOrigins);
|
|
1194
|
+
requireActiveWindow(ownerDocument);
|
|
1195
|
+
const frames = await this.#requireFrames();
|
|
1196
|
+
const requestedOrigins = parseOrigins(fromOrigins);
|
|
1197
|
+
const tools = await frames.getTools(requestedOrigins);
|
|
1198
|
+
tools.sort((left, right) => {
|
|
1199
|
+
if (left.name === right.name) return 0;
|
|
1200
|
+
return left.name < right.name ? -1 : 1;
|
|
1201
|
+
});
|
|
1202
|
+
return new Promise((resolve) => {
|
|
1203
|
+
queueTask(() => resolve(tools));
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
async executeTool(tool, inputObject = {}, options = {}) {
|
|
1207
|
+
const ownerDocument = this.#document;
|
|
1208
|
+
const target = readExecutionTarget(tool);
|
|
1209
|
+
const callerSignal = readAbortSignal(readDictionary(options).signal);
|
|
1210
|
+
requireActiveWindow(ownerDocument);
|
|
1211
|
+
const expectedOrigin = URL.parse(target.origin)?.origin;
|
|
1212
|
+
if (!expectedOrigin || expectedOrigin === "null") throw new NativeDOMException("Invalid or opaque origin", "NotSupportedError");
|
|
1213
|
+
if (!isObject(inputObject)) throw new TypeError("inputObject must be an object");
|
|
1214
|
+
const serializedInput = serializeJSON(inputObject);
|
|
1215
|
+
callerSignal?.throwIfAborted();
|
|
1216
|
+
const frames = await this.#requireFrames();
|
|
1217
|
+
callerSignal?.throwIfAborted();
|
|
1218
|
+
const callerWindow = requireActiveWindow(ownerDocument);
|
|
1219
|
+
if (target.window !== callerWindow) return frames.execute(target.window, expectedOrigin, target.name, serializedInput, callerSignal);
|
|
1220
|
+
return this.#executeLocal(target.name, serializedInput, expectedOrigin, callerWindow.origin, callerSignal);
|
|
1221
|
+
}
|
|
1222
|
+
#executeLocal(name, serializedInput, expectedOrigin, callerOrigin, callerSignal) {
|
|
1223
|
+
return new Promise((resolve, reject) => {
|
|
1224
|
+
callerSignal?.throwIfAborted();
|
|
1225
|
+
const callbackController = new AbortController();
|
|
1226
|
+
let callbackFinished = false;
|
|
1227
|
+
const onCallerAbort = () => {
|
|
1228
|
+
reject(callerSignal.reason);
|
|
1229
|
+
queueTask(() => {
|
|
1230
|
+
if (!callbackFinished) callbackController.abort();
|
|
1231
|
+
});
|
|
1232
|
+
};
|
|
1233
|
+
const rejectExecution = () => {
|
|
1234
|
+
callbackFinished = true;
|
|
1235
|
+
queueTask(() => {
|
|
1236
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
1237
|
+
reject(executionError());
|
|
1238
|
+
});
|
|
1239
|
+
};
|
|
1240
|
+
const completeExecution = (value) => {
|
|
1241
|
+
callbackFinished = true;
|
|
1242
|
+
if (callerSignal?.aborted) return;
|
|
1243
|
+
try {
|
|
1244
|
+
const serializedResult = serializeJSON(value);
|
|
1245
|
+
queueTask(() => {
|
|
1246
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
1247
|
+
resolve(serializedResult);
|
|
1248
|
+
});
|
|
1249
|
+
} catch {
|
|
1250
|
+
rejectExecution();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
const dispatchTool = () => {
|
|
1254
|
+
if (callerSignal?.aborted) return;
|
|
1255
|
+
try {
|
|
1256
|
+
const ownerWindow = requireActiveWindow(this.#document);
|
|
1257
|
+
const storedTool = this.#tools.get(name);
|
|
1258
|
+
if (!storedTool || expectedOrigin !== ownerWindow.origin || !isExposedTo(storedTool, ownerWindow.origin, callerOrigin)) {
|
|
1259
|
+
rejectExecution();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const input = JSON.parse(serializedInput);
|
|
1263
|
+
if (!isObject(input)) {
|
|
1264
|
+
rejectExecution();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const execute = storedTool.execute;
|
|
1268
|
+
const callbackResult = execute(input, { signal: callbackController.signal });
|
|
1269
|
+
Promise.resolve(callbackResult).then(completeExecution, rejectExecution);
|
|
1270
|
+
} catch {
|
|
1271
|
+
rejectExecution();
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1275
|
+
queueTask(dispatchTool);
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
async #requireFrames() {
|
|
1279
|
+
const frames = this.#frames;
|
|
1280
|
+
if (!frames || !await frames.allowed()) throw new NativeDOMException("WebMCP is disabled by Permissions Policy", "NotAllowedError");
|
|
1281
|
+
requireActiveWindow(this.#document);
|
|
1282
|
+
return frames;
|
|
1283
|
+
}
|
|
1284
|
+
#exposedTools(callerOrigin) {
|
|
1285
|
+
const ownerOrigin = requireActiveWindow(this.#document).origin;
|
|
1286
|
+
const exposed = [];
|
|
1287
|
+
for (const storedTool of this.#tools.values()) {
|
|
1288
|
+
if (!isExposedTo(storedTool, ownerOrigin, callerOrigin)) continue;
|
|
1289
|
+
exposed.push(storedTool.metadata);
|
|
1290
|
+
}
|
|
1291
|
+
return exposed;
|
|
1292
|
+
}
|
|
1293
|
+
#queueToolChange() {
|
|
1294
|
+
return new Promise((resolve) => {
|
|
1295
|
+
queueTask(() => {
|
|
1296
|
+
this.dispatchEvent(new Event("toolchange"));
|
|
1297
|
+
resolve();
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
function isExposedTo(tool, ownerOrigin, callerOrigin) {
|
|
1303
|
+
return ownerOrigin === callerOrigin || tool.exposedTo.includes(callerOrigin);
|
|
1304
|
+
}
|
|
1305
|
+
const modelContextConstructor = function ModelContext() {
|
|
1306
|
+
throw new TypeError("Illegal constructor");
|
|
1307
|
+
};
|
|
1308
|
+
Object.defineProperty(modelContextConstructor, "name", { value: "ModelContext" });
|
|
1309
|
+
Object.defineProperty(modelContextConstructor, "prototype", {
|
|
1310
|
+
value: ModelContextPolyfill.prototype,
|
|
1311
|
+
writable: false
|
|
1312
|
+
});
|
|
1313
|
+
Object.setPrototypeOf(modelContextConstructor, EventTarget);
|
|
1314
|
+
Object.defineProperties(ModelContextPolyfill.prototype, {
|
|
1315
|
+
constructor: {
|
|
1316
|
+
value: modelContextConstructor,
|
|
1317
|
+
configurable: true,
|
|
1318
|
+
writable: true
|
|
1319
|
+
},
|
|
1320
|
+
[Symbol.toStringTag]: {
|
|
1321
|
+
value: "ModelContext",
|
|
1322
|
+
configurable: true
|
|
1323
|
+
},
|
|
1324
|
+
registerTool: { enumerable: true },
|
|
1325
|
+
getTools: { enumerable: true },
|
|
1326
|
+
executeTool: { enumerable: true },
|
|
1327
|
+
ontoolchange: { enumerable: true }
|
|
1328
|
+
});
|
|
1329
|
+
function readToolDefinition(value) {
|
|
1330
|
+
const descriptor = readDictionary(value);
|
|
1331
|
+
const annotations = readAnnotations(descriptor.annotations);
|
|
1332
|
+
const description = toDOMString(requireMember(descriptor.description, "description"));
|
|
1333
|
+
const callback = requireMember(descriptor.execute, "execute");
|
|
1334
|
+
if (typeof callback !== "function") throw new TypeError("execute must be a function");
|
|
1335
|
+
const execute = callback;
|
|
1336
|
+
const inputSchema = readInputSchema(descriptor.inputSchema);
|
|
1337
|
+
const name = toDOMString(requireMember(descriptor.name, "name"));
|
|
1338
|
+
const rawTitle = descriptor.title;
|
|
1339
|
+
return {
|
|
1340
|
+
name,
|
|
1341
|
+
title: rawTitle === void 0 ? "" : toUSVString(rawTitle),
|
|
1342
|
+
description,
|
|
1343
|
+
annotations,
|
|
1344
|
+
inputSchema,
|
|
1345
|
+
execute
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function readExecutionTarget(value) {
|
|
1349
|
+
const descriptor = readDictionary(value);
|
|
1350
|
+
readAnnotations(descriptor.annotations);
|
|
1351
|
+
toDOMString(requireMember(descriptor.description, "description"));
|
|
1352
|
+
readInputSchema(descriptor.inputSchema);
|
|
1353
|
+
const name = toDOMString(requireMember(descriptor.name, "name"));
|
|
1354
|
+
const origin = toUSVString(requireMember(descriptor.origin, "origin"));
|
|
1355
|
+
const title = descriptor.title;
|
|
1356
|
+
if (title !== void 0) toDOMString(title);
|
|
1357
|
+
const targetWindow = requireMember(descriptor.window, "window");
|
|
1358
|
+
if (!isObject(targetWindow)) throw new TypeError("window must be a Window");
|
|
1359
|
+
getWindow.call(targetWindow);
|
|
1360
|
+
return {
|
|
1361
|
+
name,
|
|
1362
|
+
origin,
|
|
1363
|
+
window: targetWindow
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function readInputSchema(value) {
|
|
1367
|
+
if (value !== void 0 && !isObject(value)) throw new TypeError("inputSchema must be an object");
|
|
1368
|
+
return value;
|
|
1369
|
+
}
|
|
1370
|
+
function isObject(value) {
|
|
1371
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1372
|
+
}
|
|
1373
|
+
function readDictionary(value) {
|
|
1374
|
+
if (value == null) return {};
|
|
1375
|
+
if (!isObject(value)) throw new TypeError("Expected a dictionary");
|
|
1376
|
+
return value;
|
|
1377
|
+
}
|
|
1378
|
+
function readAnnotations(value) {
|
|
1379
|
+
if (value === void 0) return;
|
|
1380
|
+
const dictionary = readDictionary(value);
|
|
1381
|
+
const annotations = {};
|
|
1382
|
+
for (const name of annotationNames) annotations[name] = Boolean(dictionary[name]);
|
|
1383
|
+
return annotations;
|
|
1384
|
+
}
|
|
1385
|
+
function toDOMString(value) {
|
|
1386
|
+
if (typeof value === "symbol") throw new TypeError("Cannot convert a Symbol to a string");
|
|
1387
|
+
return String(value);
|
|
1388
|
+
}
|
|
1389
|
+
function toUSVString(value) {
|
|
1390
|
+
return toDOMString(value).toWellFormed();
|
|
1391
|
+
}
|
|
1392
|
+
function requireMember(value, name) {
|
|
1393
|
+
if (value === void 0) throw new TypeError(`${name} is required`);
|
|
1394
|
+
return value;
|
|
1395
|
+
}
|
|
1396
|
+
function serializeJSON(value) {
|
|
1397
|
+
const result = JSON.stringify(value);
|
|
1398
|
+
if (result === void 0) throw new TypeError("Value is not JSON-serializable");
|
|
1399
|
+
return result;
|
|
1400
|
+
}
|
|
1401
|
+
function readAbortSignal(value) {
|
|
1402
|
+
if (value === void 0) return;
|
|
1403
|
+
return AbortSignal.any([value]);
|
|
1404
|
+
}
|
|
1405
|
+
function readOriginSequence(value) {
|
|
1406
|
+
if (value === void 0) return [];
|
|
1407
|
+
if (!isObject(value)) throw new TypeError("Origins must be a sequence");
|
|
1408
|
+
const getIterator = readDictionary(value)[Symbol.iterator];
|
|
1409
|
+
if (typeof getIterator !== "function") throw new TypeError("Origins must be a sequence");
|
|
1410
|
+
const iterable = { [Symbol.iterator]() {
|
|
1411
|
+
const iterator = Reflect.apply(getIterator, value, []);
|
|
1412
|
+
if (!isObject(iterator)) throw new TypeError("Iterator must be an object");
|
|
1413
|
+
const next = readDictionary(iterator).next;
|
|
1414
|
+
if (typeof next !== "function") throw new TypeError("Iterator next must be a function");
|
|
1415
|
+
return { next() {
|
|
1416
|
+
return Reflect.apply(next, iterator, []);
|
|
1417
|
+
} };
|
|
1418
|
+
} };
|
|
1419
|
+
return Array.from(iterable, toUSVString);
|
|
1420
|
+
}
|
|
1421
|
+
function parseOrigins(origins) {
|
|
1422
|
+
return origins.map((value) => {
|
|
1423
|
+
const origin = URL.parse(value)?.origin;
|
|
1424
|
+
if (origin === void 0) throw new NativeDOMException("Invalid origin", "SecurityError");
|
|
1425
|
+
const url = URL.parse(origin);
|
|
1426
|
+
if (!url || !isPotentiallyTrustworthy(url)) throw new NativeDOMException("Origin is not potentially trustworthy", "SecurityError");
|
|
1427
|
+
return origin;
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
function isPotentiallyTrustworthy({ protocol, hostname }) {
|
|
1431
|
+
if (["https:", "wss:"].includes(protocol)) return true;
|
|
1432
|
+
const isLoopback = hostname === "[::1]" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
|
|
1433
|
+
const isLocalhost = hostname === "localhost" || hostname === "localhost." || hostname.endsWith(".localhost") || hostname.endsWith(".localhost.");
|
|
1434
|
+
return ["http:", "ws:"].includes(protocol) && (isLoopback || isLocalhost);
|
|
1435
|
+
}
|
|
1436
|
+
function requireActiveWindow(owner) {
|
|
1437
|
+
const view = activeWindow(owner);
|
|
1438
|
+
if (!view) throw new NativeDOMException("The document is not fully active", "InvalidStateError");
|
|
1439
|
+
if (readToolsPolicy(owner) === false) throw new NativeDOMException("WebMCP is disabled by Permissions Policy", "NotAllowedError");
|
|
1440
|
+
return view;
|
|
1441
|
+
}
|
|
1442
|
+
let taskPort;
|
|
1443
|
+
const queuedTasks = [];
|
|
1444
|
+
function queueTask(callback) {
|
|
1445
|
+
if (!taskPort) {
|
|
1446
|
+
const channel = new MessageChannel();
|
|
1447
|
+
channel.port1.onmessage = () => queuedTasks.shift()?.();
|
|
1448
|
+
taskPort = channel.port2;
|
|
1449
|
+
}
|
|
1450
|
+
queuedTasks.push(callback);
|
|
1451
|
+
taskPort.postMessage(void 0);
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Installs upstream WebMCP and the temporary declarative tools layer.
|
|
1455
|
+
* Engines missing an API the vendored core calls are left untouched: upstream defines
|
|
1456
|
+
* the document getter before it constructs the context, so a failed construction would
|
|
1457
|
+
* leave `document.modelContext` throwing.
|
|
1458
|
+
*/
|
|
1459
|
+
function installWebMCP() {
|
|
1460
|
+
if (typeof document === "undefined" || !globalThis.isSecureContext) return;
|
|
1461
|
+
if (typeof Promise.withResolvers !== "function" || typeof AbortSignal.any !== "function" || typeof String.prototype.toWellFormed !== "function" || typeof URL.parse !== "function") return;
|
|
1462
|
+
installWebMCP$1();
|
|
1463
|
+
const context = document.modelContext;
|
|
1464
|
+
if (context) installWebMCPDeclarativeExtensions(context);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/subsystems/mcp/webmcp.ts
|
|
1469
|
+
var webmcp_exports = /* @__PURE__ */ __exportAll({
|
|
1470
|
+
CONSEQUENTIAL_TOOL_NAMES: () => CONSEQUENTIAL_TOOL_NAMES,
|
|
1471
|
+
READ_ONLY_TOOL_NAMES: () => READ_ONLY_TOOL_NAMES,
|
|
1472
|
+
addWebMcpEventListener: () => addWebMcpEventListener,
|
|
1473
|
+
executeWebMcpTool: () => executeWebMcpTool,
|
|
1474
|
+
extractAbortSignal: () => extractAbortSignal,
|
|
1475
|
+
getWebMcpEventTarget: () => getWebMcpEventTarget,
|
|
1476
|
+
getWebMcpMode: () => getWebMcpMode,
|
|
1477
|
+
getWebMcpTools: () => getWebMcpTools,
|
|
1478
|
+
onWebMcpToolActivated: () => onWebMcpToolActivated,
|
|
1479
|
+
onWebMcpToolCancel: () => onWebMcpToolCancel,
|
|
1480
|
+
onWebMcpToolChange: () => onWebMcpToolChange,
|
|
1481
|
+
parseWebMcpInputSchema: () => parseWebMcpInputSchema,
|
|
1482
|
+
registerWebMcpTools: () => registerWebMcpTools,
|
|
1483
|
+
resolveWebMcpToolAnnotations: () => resolveWebMcpToolAnnotations,
|
|
1484
|
+
setWebMcpMode: () => setWebMcpMode,
|
|
1485
|
+
unregisterWebMcpTools: () => unregisterWebMcpTools
|
|
1486
|
+
});
|
|
1487
|
+
const registeredToolControllers = /* @__PURE__ */ new Map();
|
|
1488
|
+
const registeredToolNames = /* @__PURE__ */ new Set();
|
|
1489
|
+
let currentMode = "polyfill";
|
|
1490
|
+
/**
|
|
1491
|
+
* Set the WebMCP mode (polyfill vs native).
|
|
1492
|
+
*
|
|
1493
|
+
* - `"polyfill"` (default): Uses `@mcp-b/webmcp-polyfill` for a pure-JS
|
|
1494
|
+
* `document.modelContext` that works in all browsers.
|
|
1495
|
+
* - `"native"`: Uses Chrome's native `document.modelContext` from
|
|
1496
|
+
* `chrome://flags/#enable-webmcp-testing`. Requires the flag to be
|
|
1497
|
+
* enabled. May crash in early Canary builds.
|
|
1498
|
+
*
|
|
1499
|
+
* Detection follows Chrome's recommended compat pattern:
|
|
1500
|
+
* `document.modelContext || navigator.modelContext`
|
|
1501
|
+
* `navigator.modelContext` was deprecated in Chrome 150 and removed in
|
|
1502
|
+
* Chrome 152.0.7943.0, but the fallback keeps older clients working.
|
|
1503
|
+
*/
|
|
1504
|
+
function setWebMcpMode(mode) {
|
|
1505
|
+
currentMode = mode;
|
|
1506
|
+
}
|
|
1507
|
+
function getWebMcpMode() {
|
|
1508
|
+
return currentMode;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Install the `@mcp-b/webmcp-polyfill` onto `document.modelContext`.
|
|
1512
|
+
*
|
|
1513
|
+
* In `@mcp-b/webmcp-polyfill` 6.0.0+, the entry point is `installWebMCP()`
|
|
1514
|
+
* (previously `initializeWebMCPPolyfill()` in v5).
|
|
1515
|
+
* The polyfill itself bails out when Chrome's native `document.modelContext`
|
|
1516
|
+
* is already present — it does NOT override the native API. We call it
|
|
1517
|
+
* unconditionally; if the polyfill is already installed it returns quickly.
|
|
1518
|
+
*/
|
|
1519
|
+
function ensurePolyfill() {
|
|
1520
|
+
try {
|
|
1521
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.isSecureContext === "undefined" && typeof location !== "undefined" && (location.protocol === "https:" || location.hostname === "localhost" || location.hostname === "127.0.0.1")) try {
|
|
1522
|
+
Object.defineProperty(globalThis, "isSecureContext", {
|
|
1523
|
+
value: true,
|
|
1524
|
+
configurable: true
|
|
1525
|
+
});
|
|
1526
|
+
} catch {}
|
|
1527
|
+
if (typeof installWebMCP === "function") installWebMCP();
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
console.warn("WebMCP polyfill initialization failed:", err);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Returns true when the `@mcp-b/webmcp-polyfill` v5.1.0+ security gate will
|
|
1534
|
+
* allow `registerTool()` to proceed.
|
|
1535
|
+
*
|
|
1536
|
+
* The polyfill's `validateOriginAgentCluster()` throws `DOMException("",
|
|
1537
|
+
* "SecurityError")` when `globalThis.originAgentCluster === false` (and the
|
|
1538
|
+
* protocol is not `file:`). Chrome automatically origin-isolates HTTPS pages so
|
|
1539
|
+
* this is never an issue there. Firefox ≥ 138 exposes `originAgentCluster` but
|
|
1540
|
+
* returns `false` unless the server sends `Origin-Agent-Cluster: ?1` — a
|
|
1541
|
+
* header that GitHub Pages does not set. This check lets us detect Firefox
|
|
1542
|
+
* (and any other browser with the same behaviour) early and skip registration
|
|
1543
|
+
* with a single descriptive warning instead of N per-tool `DOMException`s.
|
|
1544
|
+
*
|
|
1545
|
+
* Native mode bypasses the polyfill entirely and is unaffected.
|
|
1546
|
+
*/
|
|
1547
|
+
function isOriginAgentClusterCompatible() {
|
|
1548
|
+
if (typeof globalThis.originAgentCluster === "undefined") return true;
|
|
1549
|
+
if (globalThis.location?.protocol === "file:") return true;
|
|
1550
|
+
return globalThis.originAgentCluster !== false;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Access the WebMCP ModelContext API.
|
|
1554
|
+
*
|
|
1555
|
+
* In polyfill mode: installs the `@mcp-b/webmcp-polyfill` which surfaces
|
|
1556
|
+
* `document.modelContext`.
|
|
1557
|
+
* In native mode: follows Chrome's recommended feature-detection pattern:
|
|
1558
|
+
* `document.modelContext || navigator.modelContext`
|
|
1559
|
+
* `navigator.modelContext` was deprecated in Chrome 150 and removed in
|
|
1560
|
+
* Chrome 152.0.7943.0, but the fallback preserves compatibility with
|
|
1561
|
+
* earlier builds that may still be in the wild.
|
|
1562
|
+
*/
|
|
1563
|
+
function getModelContextApi() {
|
|
1564
|
+
if (typeof document === "undefined" && typeof navigator === "undefined") return null;
|
|
1565
|
+
if (currentMode === "polyfill") ensurePolyfill();
|
|
1566
|
+
try {
|
|
1567
|
+
const modelContext = typeof document !== "undefined" && typeof document.modelContext !== "undefined" ? Reflect.get(document, "modelContext") : typeof navigator !== "undefined" && typeof navigator.modelContext !== "undefined" ? Reflect.get(navigator, "modelContext") : void 0;
|
|
1568
|
+
if (!modelContext || typeof modelContext !== "object") return null;
|
|
1569
|
+
const api = modelContext;
|
|
1570
|
+
if (typeof api.registerTool !== "function") return null;
|
|
1571
|
+
return api;
|
|
1572
|
+
} catch (err) {
|
|
1573
|
+
console.warn("WebMCP modelContext access failed:", err);
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Safely parse or normalize WebMCP input schema.
|
|
1579
|
+
*
|
|
1580
|
+
* Starting in Chrome 154.0.8014.0 (PR #241), `RegisteredTool#inputSchema` returns
|
|
1581
|
+
* a JavaScript object (a deep copy of the schema provided at registration).
|
|
1582
|
+
* In Chrome < 154.0.8014.0, `RegisteredTool#inputSchema` returned a stringified JSON schema (DOMString).
|
|
1583
|
+
*
|
|
1584
|
+
* This function provides backwards-compatible schema parsing:
|
|
1585
|
+
* - If inputSchema is a string: attempts JSON.parse.
|
|
1586
|
+
* - If inputSchema is an object: returns the object.
|
|
1587
|
+
* - Fallbacks gracefully to an empty object schema if null, undefined, or invalid JSON.
|
|
1588
|
+
*/
|
|
1589
|
+
function parseWebMcpInputSchema(inputSchema) {
|
|
1590
|
+
if (typeof inputSchema === "string") {
|
|
1591
|
+
try {
|
|
1592
|
+
const parsed = JSON.parse(inputSchema);
|
|
1593
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
1594
|
+
} catch {}
|
|
1595
|
+
return {
|
|
1596
|
+
type: "object",
|
|
1597
|
+
properties: {}
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
if (inputSchema && typeof inputSchema === "object" && !Array.isArray(inputSchema)) return inputSchema;
|
|
1601
|
+
return {
|
|
1602
|
+
type: "object",
|
|
1603
|
+
properties: {}
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Safely extract AbortSignal from execute options/context.
|
|
1608
|
+
* Supports Chrome 153+ options object `{ signal }` (PR #247), direct AbortSignal instances,
|
|
1609
|
+
* or undefined for backward compatibility with Chrome < 153 and legacy callers.
|
|
1610
|
+
*/
|
|
1611
|
+
function extractAbortSignal(context) {
|
|
1612
|
+
if (!context) return;
|
|
1613
|
+
if (typeof AbortSignal !== "undefined" && context instanceof AbortSignal) return context;
|
|
1614
|
+
if (typeof context === "object" && "signal" in context && typeof AbortSignal !== "undefined" && context.signal instanceof AbortSignal) return context.signal;
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Retrieve tools registered on the WebMCP ModelContext API.
|
|
1618
|
+
*
|
|
1619
|
+
* Practicing graceful degradation:
|
|
1620
|
+
* - Feature-detects `getTools()` on `document.modelContext` / `navigator.modelContext`.
|
|
1621
|
+
* - If `getTools()` is absent (older browser builds or polyfills without getTools),
|
|
1622
|
+
* returns an empty array `[]` without throwing.
|
|
1623
|
+
* - Supports options dictionary with `fromOrigins` to discover cross-frame/cross-origin tools (Chrome 155+).
|
|
1624
|
+
* - Normalizes `RegisteredTool#inputSchema` so caller receives a JavaScript object
|
|
1625
|
+
* whether running on Chrome 154+ (native object) or Chrome < 154 (DOMString JSON).
|
|
1626
|
+
*/
|
|
1627
|
+
async function getWebMcpTools(options) {
|
|
1628
|
+
const modelContext = getModelContextApi();
|
|
1629
|
+
if (!modelContext || typeof modelContext.getTools !== "function") return [];
|
|
1630
|
+
try {
|
|
1631
|
+
const rawTools = await modelContext.getTools(options);
|
|
1632
|
+
if (!Array.isArray(rawTools)) return [];
|
|
1633
|
+
return rawTools.map((tool) => ({
|
|
1634
|
+
name: tool.name,
|
|
1635
|
+
...tool.title ? { title: tool.title } : {},
|
|
1636
|
+
...tool.description ? { description: tool.description } : {},
|
|
1637
|
+
inputSchema: parseWebMcpInputSchema(tool.inputSchema),
|
|
1638
|
+
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
1639
|
+
...tool.execute ? { execute: tool.execute } : {}
|
|
1640
|
+
}));
|
|
1641
|
+
} catch (err) {
|
|
1642
|
+
console.warn("Failed to retrieve WebMCP tools via getTools():", err);
|
|
1643
|
+
return [];
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Gracefully execute a WebMCP tool on a ModelContext instance with backwards compatibility.
|
|
1648
|
+
*
|
|
1649
|
+
* Starting in Chrome 155.0.8052.0 (PR #246, PR #251), `document.modelContext.executeTool()`
|
|
1650
|
+
* accepts an optional JavaScript object directly instead of a JSON DOMString.
|
|
1651
|
+
* In Chrome < 155 and earlier polyfills, `executeTool()` required a stringified JSON arguments string.
|
|
1652
|
+
*
|
|
1653
|
+
* This function practices graceful degradation:
|
|
1654
|
+
* 1. Prepares both an `inputObject` (for Chrome 155+) and an `inputStr` (for Chrome < 155 & polyfills).
|
|
1655
|
+
* 2. Attempts to execute `executeTool(tool, inputObject, options)` first.
|
|
1656
|
+
* 3. Catches errors starting with "Failed to parse input" (or TypeErrors from WebIDL DOMString checks)
|
|
1657
|
+
* and retries transparently with `JSON.stringify(inputObject)`.
|
|
1658
|
+
* 4. Re-throws any unrelated execution errors.
|
|
1659
|
+
*/
|
|
1660
|
+
async function executeWebMcpTool(modelContext, tool, input, options) {
|
|
1661
|
+
if (!modelContext || typeof modelContext.executeTool !== "function") throw new Error("ModelContext executeTool is not available");
|
|
1662
|
+
let inputObject;
|
|
1663
|
+
let inputStr;
|
|
1664
|
+
if (typeof input === "string") {
|
|
1665
|
+
inputStr = input;
|
|
1666
|
+
try {
|
|
1667
|
+
const parsed = JSON.parse(input);
|
|
1668
|
+
inputObject = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1669
|
+
} catch {
|
|
1670
|
+
inputObject = {};
|
|
1671
|
+
}
|
|
1672
|
+
} else if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
1673
|
+
inputObject = input;
|
|
1674
|
+
try {
|
|
1675
|
+
inputStr = JSON.stringify(input);
|
|
1676
|
+
} catch {
|
|
1677
|
+
inputStr = "{}";
|
|
1678
|
+
}
|
|
1679
|
+
} else {
|
|
1680
|
+
inputObject = {};
|
|
1681
|
+
inputStr = "{}";
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
return await modelContext.executeTool(tool, inputObject, options);
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
const isParseError = typeof err?.message === "string" && err.message.startsWith("Failed to parse input");
|
|
1687
|
+
const isTypeError = err instanceof TypeError;
|
|
1688
|
+
if (isParseError || isTypeError) return await modelContext.executeTool(tool, inputStr, options);
|
|
1689
|
+
throw err;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Resolves the appropriate EventTarget for WebMCP lifecycle events
|
|
1694
|
+
* (`toolactivated` and `toolcancel`) with backwards compatibility.
|
|
1695
|
+
*
|
|
1696
|
+
* Starting in Chrome 156.0.8076.0 (PR #245), `ontoolactivated` and `ontoolcancel`
|
|
1697
|
+
* event handler properties and lifecycle events moved from `window` to `document.modelContext`.
|
|
1698
|
+
* In Chrome < 156, these events were fired on `window`.
|
|
1699
|
+
*
|
|
1700
|
+
* This function practices robust feature detection and graceful degradation:
|
|
1701
|
+
* 1. Safely checks for existence of `document` and `window` to avoid ReferenceErrors
|
|
1702
|
+
* in non-DOM / headless / Node.js environments.
|
|
1703
|
+
* 2. Checks if `on${type}` is present in `document.modelContext` (or `navigator.modelContext`).
|
|
1704
|
+
* If present and it supports `addEventListener`, `modelContext` is returned.
|
|
1705
|
+
* 3. Checks if `on${type}` is present in `window`. If present and `window` supports
|
|
1706
|
+
* `addEventListener`, `window` is returned (legacy Chrome < 156 path).
|
|
1707
|
+
* 4. Fallback: If neither has `on${type}` (e.g. polyfill, synthetic mock, or uninitialized prototype),
|
|
1708
|
+
* prefers `modelContext` if it supports `addEventListener`, otherwise falls back to `window`.
|
|
1709
|
+
* 5. Returns `null` if no valid EventTarget is found.
|
|
1710
|
+
*/
|
|
1711
|
+
function getWebMcpEventTarget(type) {
|
|
1712
|
+
const doc = typeof document !== "undefined" ? document : void 0;
|
|
1713
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
1714
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
1715
|
+
const modelContext = doc?.modelContext ?? nav?.modelContext;
|
|
1716
|
+
const hasModelContextListener = modelContext && typeof modelContext.addEventListener === "function";
|
|
1717
|
+
const hasWindowListener = win && typeof win.addEventListener === "function";
|
|
1718
|
+
if (type) {
|
|
1719
|
+
const handlerKey = `on${type}`;
|
|
1720
|
+
if (modelContext && handlerKey in modelContext && hasModelContextListener) return modelContext;
|
|
1721
|
+
if (win && handlerKey in win && hasWindowListener) return win;
|
|
1722
|
+
}
|
|
1723
|
+
if (hasModelContextListener) return modelContext;
|
|
1724
|
+
if (hasWindowListener) return win;
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Safely registers a listener for WebMCP lifecycle events (`toolactivated`, `toolcancel`)
|
|
1729
|
+
* with graceful degradation, event payload normalization, and automatic unsubscription.
|
|
1730
|
+
*
|
|
1731
|
+
* Returns an idempotent unsubscribe cleanup function.
|
|
1732
|
+
*/
|
|
1733
|
+
function addWebMcpEventListener(type, listener, options) {
|
|
1734
|
+
const target = getWebMcpEventTarget(type);
|
|
1735
|
+
const opts = typeof options === "object" ? options : void 0;
|
|
1736
|
+
const listenBoth = opts?.listenBoth ?? false;
|
|
1737
|
+
const win = typeof window !== "undefined" && typeof window.addEventListener === "function" ? window : null;
|
|
1738
|
+
const doc = typeof document !== "undefined" ? document : null;
|
|
1739
|
+
const nav = typeof navigator !== "undefined" ? navigator : null;
|
|
1740
|
+
const modelContext = doc?.modelContext ?? nav?.modelContext;
|
|
1741
|
+
const hasMc = modelContext && typeof modelContext.addEventListener === "function";
|
|
1742
|
+
const secondaryTarget = listenBoth ? target === modelContext ? win !== target ? win : null : hasMc ? modelContext : null : null;
|
|
1743
|
+
if (!target && !secondaryTarget) return () => {};
|
|
1744
|
+
let recentToolNames = null;
|
|
1745
|
+
let clearTimer = null;
|
|
1746
|
+
const wrapHandler = (evt) => {
|
|
1747
|
+
let toolName = evt.toolName;
|
|
1748
|
+
if (typeof toolName !== "string" && "detail" in evt && typeof evt.detail === "object") toolName = evt.detail?.toolName;
|
|
1749
|
+
if (typeof toolName !== "string") toolName = "";
|
|
1750
|
+
if (!("toolName" in evt) || evt.toolName !== toolName) try {
|
|
1751
|
+
Object.defineProperty(evt, "toolName", {
|
|
1752
|
+
value: toolName,
|
|
1753
|
+
configurable: true,
|
|
1754
|
+
enumerable: true
|
|
1755
|
+
});
|
|
1756
|
+
} catch {}
|
|
1757
|
+
if (listenBoth && toolName) {
|
|
1758
|
+
if (!recentToolNames) recentToolNames = /* @__PURE__ */ new Set();
|
|
1759
|
+
if (recentToolNames.has(toolName)) return;
|
|
1760
|
+
recentToolNames.add(toolName);
|
|
1761
|
+
if (!clearTimer) clearTimer = setTimeout(() => {
|
|
1762
|
+
recentToolNames?.clear();
|
|
1763
|
+
clearTimer = null;
|
|
1764
|
+
}, 100);
|
|
1765
|
+
}
|
|
1766
|
+
listener(evt);
|
|
1767
|
+
};
|
|
1768
|
+
if (target) target.addEventListener(type, wrapHandler, options);
|
|
1769
|
+
if (secondaryTarget) secondaryTarget.addEventListener(type, wrapHandler, options);
|
|
1770
|
+
let unsubscribed = false;
|
|
1771
|
+
const unsubscribe = () => {
|
|
1772
|
+
if (unsubscribed) return;
|
|
1773
|
+
unsubscribed = true;
|
|
1774
|
+
if (clearTimer) {
|
|
1775
|
+
clearTimeout(clearTimer);
|
|
1776
|
+
clearTimer = null;
|
|
1777
|
+
}
|
|
1778
|
+
if (target) target.removeEventListener(type, wrapHandler, options);
|
|
1779
|
+
if (secondaryTarget) secondaryTarget.removeEventListener(type, wrapHandler, options);
|
|
1780
|
+
};
|
|
1781
|
+
if (opts?.signal) {
|
|
1782
|
+
if (opts.signal.aborted) {
|
|
1783
|
+
unsubscribe();
|
|
1784
|
+
return () => {};
|
|
1785
|
+
}
|
|
1786
|
+
opts.signal.addEventListener("abort", () => unsubscribe(), { once: true });
|
|
1787
|
+
}
|
|
1788
|
+
return unsubscribe;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Convenience helper to subscribe to tool execution start events (`toolactivated`).
|
|
1792
|
+
* Returns an unsubscribe cleanup function.
|
|
1793
|
+
*/
|
|
1794
|
+
function onWebMcpToolActivated(listener, options) {
|
|
1795
|
+
return addWebMcpEventListener("toolactivated", listener, options);
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Convenience helper to subscribe to tool execution cancel events (`toolcancel`).
|
|
1799
|
+
* Returns an unsubscribe cleanup function.
|
|
1800
|
+
*/
|
|
1801
|
+
function onWebMcpToolCancel(listener, options) {
|
|
1802
|
+
return addWebMcpEventListener("toolcancel", listener, options);
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Convenience helper to subscribe to tool registration change events (`toolchange`).
|
|
1806
|
+
* Dispatched on `document.modelContext` when tools are registered or unregistered.
|
|
1807
|
+
* Returns an unsubscribe cleanup function.
|
|
1808
|
+
*/
|
|
1809
|
+
function onWebMcpToolChange(listener, options) {
|
|
1810
|
+
const doc = typeof document !== "undefined" ? document : void 0;
|
|
1811
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
1812
|
+
const modelContext = doc?.modelContext ?? nav?.modelContext;
|
|
1813
|
+
if (!modelContext || typeof modelContext.addEventListener !== "function") return () => {};
|
|
1814
|
+
modelContext.addEventListener("toolchange", listener, options);
|
|
1815
|
+
return () => {
|
|
1816
|
+
try {
|
|
1817
|
+
modelContext.removeEventListener("toolchange", listener, options);
|
|
1818
|
+
} catch {}
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Consequential tools that perform high-stakes, irreversible, or external real-world actions
|
|
1823
|
+
* (such as deleting data, executing arbitrary system commands, or transmitting live communications).
|
|
1824
|
+
* Signals consuming agents to require explicit user confirmation prior to execution (Chrome 154.0.8017.0+).
|
|
1825
|
+
*/
|
|
1826
|
+
const CONSEQUENTIAL_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1827
|
+
"bash",
|
|
1828
|
+
"clear_chat",
|
|
1829
|
+
"delete_file",
|
|
1830
|
+
"delete_task",
|
|
1831
|
+
"email_send_message",
|
|
1832
|
+
"git_delete_branch",
|
|
1833
|
+
"git_delete_repo",
|
|
1834
|
+
"git_push",
|
|
1835
|
+
"git_reset"
|
|
1836
|
+
]);
|
|
1837
|
+
/**
|
|
1838
|
+
* Read-only tools that query information without mutating state.
|
|
1839
|
+
*/
|
|
1840
|
+
const READ_ONLY_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1841
|
+
"ask_user",
|
|
1842
|
+
"detect_language",
|
|
1843
|
+
"diff_files",
|
|
1844
|
+
"email_read_messages",
|
|
1845
|
+
"fetch_url",
|
|
1846
|
+
"get_current_time",
|
|
1847
|
+
"git_branches",
|
|
1848
|
+
"git_diff",
|
|
1849
|
+
"git_list_repos",
|
|
1850
|
+
"git_log",
|
|
1851
|
+
"git_read_file_at_ref",
|
|
1852
|
+
"git_show",
|
|
1853
|
+
"git_status",
|
|
1854
|
+
"list_components",
|
|
1855
|
+
"list_files",
|
|
1856
|
+
"list_room_members",
|
|
1857
|
+
"list_tasks",
|
|
1858
|
+
"list_tool_profiles",
|
|
1859
|
+
"proofread_text",
|
|
1860
|
+
"read_file",
|
|
1861
|
+
"remote_mcp_list_tools",
|
|
1862
|
+
"rewrite_text",
|
|
1863
|
+
"search_files",
|
|
1864
|
+
"summarize_text",
|
|
1865
|
+
"translate_text",
|
|
1866
|
+
"web_search",
|
|
1867
|
+
"write_text"
|
|
1868
|
+
]);
|
|
1869
|
+
/**
|
|
1870
|
+
* Resolve annotations for a WebMCP tool registration.
|
|
1871
|
+
* Allows custom annotations (from declarative or customized tool definitions)
|
|
1872
|
+
* to override defaults.
|
|
1873
|
+
*/
|
|
1874
|
+
function resolveWebMcpToolAnnotations(toolName, customAnnotations) {
|
|
1875
|
+
const isConsequential = CONSEQUENTIAL_TOOL_NAMES.has(toolName);
|
|
1876
|
+
return {
|
|
1877
|
+
readOnlyHint: !isConsequential && READ_ONLY_TOOL_NAMES.has(toolName),
|
|
1878
|
+
consequentialHint: isConsequential,
|
|
1879
|
+
untrustedContentHint: true,
|
|
1880
|
+
...typeof customAnnotations?.debugging === "boolean" ? { debugging: customAnnotations.debugging } : {},
|
|
1881
|
+
...customAnnotations || {}
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Register ShadowClaw tools with the WebMCP ModelContext API.
|
|
1886
|
+
*
|
|
1887
|
+
* Both the `@mcp-b/webmcp-polyfill` and Chrome's native API support
|
|
1888
|
+
* `registerTool(tool, { signal })`. We always use AbortController signals so
|
|
1889
|
+
* that unregistration works identically regardless of which implementation is
|
|
1890
|
+
* actually active.
|
|
1891
|
+
*
|
|
1892
|
+
* Key correctness detail: `ensurePolyfill()` is a no-op when
|
|
1893
|
+
* Chrome's native `document.modelContext` already exists — it does NOT
|
|
1894
|
+
* override the native API. In that situation we are talking to the native
|
|
1895
|
+
* Chrome API even in "polyfill" mode. The native Chrome API only supports
|
|
1896
|
+
* signal-based unregistration, so using AbortController signals everywhere
|
|
1897
|
+
* prevents "Duplicate tool name" errors when tools are re-configured.
|
|
1898
|
+
*/
|
|
1899
|
+
async function registerWebMcpTools(agentWorker, _emit, groupId = DEFAULT_GROUP_ID, tools) {
|
|
1900
|
+
const activeTools = Array.isArray(tools) ? tools : TOOL_DEFINITIONS;
|
|
1901
|
+
if (activeTools.length === 0) return true;
|
|
1902
|
+
if (currentMode === "polyfill" && !isOriginAgentClusterCompatible()) {
|
|
1903
|
+
console.warn("WebMCP polyfill registration skipped: this browser reports originAgentCluster = false. The @mcp-b/webmcp-polyfill v5.1.0+ requires an origin-keyed agent cluster (Chrome enforces this automatically for HTTPS; Firefox requires the server to send Origin-Agent-Cluster: ?1). WebMCP tools are unavailable in this browser session.");
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
const modelContext = getModelContextApi();
|
|
1907
|
+
if (!modelContext) return false;
|
|
1908
|
+
for (const def of activeTools) {
|
|
1909
|
+
if (registeredToolNames.has(def.name)) continue;
|
|
1910
|
+
const controller = new AbortController();
|
|
1911
|
+
registeredToolControllers.set(def.name, controller);
|
|
1912
|
+
registeredToolNames.add(def.name);
|
|
1913
|
+
try {
|
|
1914
|
+
const toolDef = {
|
|
1915
|
+
name: def.name,
|
|
1916
|
+
title: def.title || def.name,
|
|
1917
|
+
description: def.description,
|
|
1918
|
+
inputSchema: parseWebMcpInputSchema(def.input_schema),
|
|
1919
|
+
annotations: resolveWebMcpToolAnnotations(def.name, def.annotations),
|
|
1920
|
+
execute: (input, context) => {
|
|
1921
|
+
if (!agentWorker) throw new Error("Cannot execute tool: agent worker is not ready");
|
|
1922
|
+
const signal = extractAbortSignal(context);
|
|
1923
|
+
if (signal?.aborted) return Promise.reject(/* @__PURE__ */ new Error("Tool execution aborted"));
|
|
1924
|
+
return new Promise((resolve, reject) => {
|
|
1925
|
+
const callId = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
1926
|
+
let timeout = null;
|
|
1927
|
+
const cleanup = () => {
|
|
1928
|
+
if (timeout !== null) {
|
|
1929
|
+
clearTimeout(timeout);
|
|
1930
|
+
timeout = null;
|
|
1931
|
+
}
|
|
1932
|
+
agentWorker?.removeEventListener("message", handler);
|
|
1933
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
1934
|
+
};
|
|
1935
|
+
const onAbort = () => {
|
|
1936
|
+
cleanup();
|
|
1937
|
+
reject(/* @__PURE__ */ new Error("Tool execution aborted"));
|
|
1938
|
+
};
|
|
1939
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true });
|
|
1940
|
+
timeout = setTimeout(() => {
|
|
1941
|
+
cleanup();
|
|
1942
|
+
reject(/* @__PURE__ */ new Error("Timeout waiting for tool execution"));
|
|
1943
|
+
}, 6e5);
|
|
1944
|
+
const handler = (event) => {
|
|
1945
|
+
const data = event.data;
|
|
1946
|
+
if (data && data.type === "execute-tool-result" && data.callId === callId) {
|
|
1947
|
+
cleanup();
|
|
1948
|
+
if (data.error) reject(new Error(data.error));
|
|
1949
|
+
else resolve(data.result);
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
agentWorker.addEventListener("message", handler);
|
|
1953
|
+
agentWorker.postMessage({
|
|
1954
|
+
type: "execute-tool",
|
|
1955
|
+
callId,
|
|
1956
|
+
payload: {
|
|
1957
|
+
name: def.name,
|
|
1958
|
+
input: input || {},
|
|
1959
|
+
groupId
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
await modelContext.registerTool(toolDef, { signal: controller.signal });
|
|
1966
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
registeredToolControllers.delete(def.name);
|
|
1969
|
+
registeredToolNames.delete(def.name);
|
|
1970
|
+
if (err instanceof Error && (err.message.includes("already registered") || err.message.includes("Duplicate tool name"))) continue;
|
|
1971
|
+
if (err instanceof DOMException && err.name === "SecurityError") {
|
|
1972
|
+
console.warn("WebMCP tool registration aborted: browser security policy (SecurityError) prevented registration. Remaining tools will not be registered for this session.", err);
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
console.error(`Failed to register tool ${def.name}:`, err);
|
|
1976
|
+
throw err;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return true;
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Unregister previously registered ShadowClaw WebMCP tools.
|
|
1983
|
+
*
|
|
1984
|
+
* Aborts every AbortController associated with a registered tool. This works
|
|
1985
|
+
* for both the polyfill (which listens for the `abort` event on the signal it
|
|
1986
|
+
* was given) and Chrome's native API (which also uses signal-based lifecycle).
|
|
1987
|
+
* The deprecated `unregisterTool(name)` is intentionally NOT called here
|
|
1988
|
+
* because it is absent from the native Chrome API and causes silent failures
|
|
1989
|
+
* that prevent proper cleanup.
|
|
1990
|
+
*/
|
|
1991
|
+
function unregisterWebMcpTools() {
|
|
1992
|
+
for (const controller of registeredToolControllers.values()) try {
|
|
1993
|
+
controller.abort();
|
|
1994
|
+
} catch {}
|
|
1995
|
+
registeredToolControllers.clear();
|
|
1996
|
+
registeredToolNames.clear();
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
//#endregion
|
|
2000
|
+
export { registerWebMcpTools as a, webmcp_exports as c, onWebMcpToolChange as i, onWebMcpToolActivated as n, setWebMcpMode as o, onWebMcpToolCancel as r, unregisterWebMcpTools as s, getWebMcpMode as t };
|
|
2001
|
+
//# sourceMappingURL=webmcp-Bf9rT7uu.js.map
|