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,266 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
const mockLoadApprovedCustomElementScript = jest.fn(async () =>
|
|
4
|
+
document.createElement("script"),
|
|
5
|
+
);
|
|
6
|
+
const mockGetApprovedCustomElementScriptDescriptors = jest.fn(() => [
|
|
7
|
+
{
|
|
8
|
+
id: "feed-scroller",
|
|
9
|
+
src: "/pages/main/assets/feed-scroller.js",
|
|
10
|
+
global: true,
|
|
11
|
+
hasInit: true,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "x-hook-component",
|
|
15
|
+
src: "/pages/main/assets/x-hook-component.js",
|
|
16
|
+
hasInit: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "x-postpress-code",
|
|
20
|
+
src: "/pages/main/assets/x-postpress-code.js",
|
|
21
|
+
hasInit: true,
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
jest.unstable_mockModule("../../security/custom-element-security.js", () => ({
|
|
26
|
+
getApprovedCustomElementScriptDescriptors:
|
|
27
|
+
mockGetApprovedCustomElementScriptDescriptors,
|
|
28
|
+
loadApprovedCustomElementScript: mockLoadApprovedCustomElementScript,
|
|
29
|
+
isApprovedScript: jest.fn((_src: string) => true),
|
|
30
|
+
isAllowedCustomElement: jest.fn(() => true),
|
|
31
|
+
getIframeCsp: jest.fn(() => ""),
|
|
32
|
+
getIframeSandboxPolicy: jest.fn(() => "allow-scripts"),
|
|
33
|
+
hasApprovedCustomElement: jest.fn(() => false),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
jest.unstable_mockModule("../../core/effect.js", () => ({
|
|
37
|
+
effect: jest.fn(() => () => undefined),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.unstable_mockModule("../../content/markdown.js", () => ({
|
|
41
|
+
renderMarkdown: jest.fn(
|
|
42
|
+
async (value: string) =>
|
|
43
|
+
`<article>${value.replace(/<script[\s\S]*?<\/script>/gi, "")}</article>`,
|
|
44
|
+
),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
jest.unstable_mockModule("../../security/trusted-types.js", () => ({
|
|
48
|
+
sanitizeToTrustedHtml: jest.fn((html: string) => html),
|
|
49
|
+
sanitizeSrcdocHtml: jest.fn((html: string) => html),
|
|
50
|
+
setSanitizedHtml: jest.fn((element: Element, html: string) => {
|
|
51
|
+
element.innerHTML = html;
|
|
52
|
+
return html;
|
|
53
|
+
}),
|
|
54
|
+
setTrustedSrcdoc: jest.fn((iframe: HTMLIFrameElement, html: string) => {
|
|
55
|
+
iframe.srcdoc = html;
|
|
56
|
+
return html;
|
|
57
|
+
}),
|
|
58
|
+
toTrustedScriptUrl: jest.fn((url: string) => url),
|
|
59
|
+
toTrustedHtmlPresanitized: jest.fn((html: string) => html),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
let mockActivePinnedPage: any = null;
|
|
63
|
+
jest.unstable_mockModule("../../stores/orchestrator.js", () => ({
|
|
64
|
+
orchestratorStore: {
|
|
65
|
+
whenInitialized: Promise.resolve(),
|
|
66
|
+
whenReady: Promise.resolve(),
|
|
67
|
+
pages: [],
|
|
68
|
+
groups: [],
|
|
69
|
+
activeGroupId: "group-1",
|
|
70
|
+
isPageAllowed: jest.fn(() => true),
|
|
71
|
+
get activePinnedPage() {
|
|
72
|
+
return mockActivePinnedPage;
|
|
73
|
+
},
|
|
74
|
+
setActivePinnedPage: jest.fn(async (_db: any, val: any) => {
|
|
75
|
+
mockActivePinnedPage = val;
|
|
76
|
+
}),
|
|
77
|
+
_activePinnedPage: {
|
|
78
|
+
set: jest.fn((val: any) => {
|
|
79
|
+
mockActivePinnedPage = val;
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
jest.unstable_mockModule("../../storage/read-group-file.js", () => ({
|
|
86
|
+
readGroupFile: jest.fn(),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
jest.unstable_mockModule("../../storage/static-main-site.js", () => ({
|
|
90
|
+
getStaticMainManifest: jest.fn(async () => ({ pages: [] })),
|
|
91
|
+
getStaticPageContent: jest.fn(async () => null),
|
|
92
|
+
seedStaticMainSite: jest.fn(),
|
|
93
|
+
getSiteConfigSeoSettings: jest.fn(() => ({ siteTitle: "ShadowClaw" })),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
jest.unstable_mockModule("../../db/db.js", () => ({
|
|
97
|
+
getDb: jest.fn(async () => ({})),
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
jest.unstable_mockModule("../../db/get-config.js", () => ({
|
|
101
|
+
getConfig: jest.fn(async () => "0"),
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
const { ShadowClawPages } = await import("./shadow-claw-pages.js");
|
|
105
|
+
const { readGroupFile } = await import("../../storage/read-group-file.js");
|
|
106
|
+
const { setSanitizedHtml } = await import("../../security/trusted-types.js");
|
|
107
|
+
|
|
108
|
+
describe("ShadowClawPages - Script Dependencies Loading", () => {
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
jest.clearAllMocks();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("loads global scripts and frontmatter-declared script dependencies", async () => {
|
|
114
|
+
const component = new ShadowClawPages();
|
|
115
|
+
component.db = {} as any;
|
|
116
|
+
component.selectedPage = { groupId: "group-1", path: "posts/dynamic.md" };
|
|
117
|
+
|
|
118
|
+
const postContent = [
|
|
119
|
+
"---",
|
|
120
|
+
'title: "The Web Is Dynamic"',
|
|
121
|
+
"dependencies:",
|
|
122
|
+
' - "x-hook-component"',
|
|
123
|
+
"---",
|
|
124
|
+
"# The Web Is Dynamic",
|
|
125
|
+
"<x-hook-component></x-hook-component>",
|
|
126
|
+
].join("\n");
|
|
127
|
+
|
|
128
|
+
(
|
|
129
|
+
readGroupFile as jest.MockedFunction<typeof readGroupFile>
|
|
130
|
+
).mockResolvedValueOnce(postContent);
|
|
131
|
+
|
|
132
|
+
await component.renderSelectedPage();
|
|
133
|
+
|
|
134
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
135
|
+
"/pages/main/assets/feed-scroller.js",
|
|
136
|
+
);
|
|
137
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
138
|
+
"/pages/main/assets/x-hook-component.js",
|
|
139
|
+
);
|
|
140
|
+
expect(mockLoadApprovedCustomElementScript).not.toHaveBeenCalledWith(
|
|
141
|
+
"/pages/main/assets/x-postpress-code.js",
|
|
142
|
+
);
|
|
143
|
+
expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("fails gracefully when an unresolvable dependency is in frontmatter without breaking rendering", async () => {
|
|
147
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
148
|
+
const component = new ShadowClawPages();
|
|
149
|
+
component.db = {} as any;
|
|
150
|
+
component.selectedPage = { groupId: "group-1", path: "posts/unknown.md" };
|
|
151
|
+
|
|
152
|
+
const postContent = [
|
|
153
|
+
"---",
|
|
154
|
+
'title: "Unknown Script Page"',
|
|
155
|
+
"dependencies:",
|
|
156
|
+
' - "unknown-script-id"',
|
|
157
|
+
"---",
|
|
158
|
+
"# Unknown Script Page",
|
|
159
|
+
"<p>Some content</p>",
|
|
160
|
+
].join("\n");
|
|
161
|
+
|
|
162
|
+
(
|
|
163
|
+
readGroupFile as jest.MockedFunction<typeof readGroupFile>
|
|
164
|
+
).mockResolvedValueOnce(postContent);
|
|
165
|
+
|
|
166
|
+
await component.renderSelectedPage();
|
|
167
|
+
|
|
168
|
+
// Global script should still load
|
|
169
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
170
|
+
"/pages/main/assets/feed-scroller.js",
|
|
171
|
+
);
|
|
172
|
+
// Page rendering should still succeed
|
|
173
|
+
expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
|
|
174
|
+
|
|
175
|
+
warnSpy.mockRestore();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("fails gracefully when loadApprovedCustomElementScript throws without breaking rendering", async () => {
|
|
179
|
+
const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
180
|
+
mockLoadApprovedCustomElementScript.mockRejectedValueOnce(
|
|
181
|
+
new Error("Network connection failed"),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const component = new ShadowClawPages();
|
|
185
|
+
component.db = {} as any;
|
|
186
|
+
component.selectedPage = { groupId: "group-1", path: "posts/faulty.md" };
|
|
187
|
+
|
|
188
|
+
const postContent = [
|
|
189
|
+
"---",
|
|
190
|
+
'title: "Faulty Script Page"',
|
|
191
|
+
"dependencies:",
|
|
192
|
+
' - "x-hook-component"',
|
|
193
|
+
"---",
|
|
194
|
+
"# Faulty Script Page",
|
|
195
|
+
].join("\n");
|
|
196
|
+
|
|
197
|
+
(
|
|
198
|
+
readGroupFile as jest.MockedFunction<typeof readGroupFile>
|
|
199
|
+
).mockResolvedValueOnce(postContent);
|
|
200
|
+
|
|
201
|
+
// Should not throw
|
|
202
|
+
await expect(component.renderSelectedPage()).resolves.toBeUndefined();
|
|
203
|
+
expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
|
|
204
|
+
|
|
205
|
+
warnSpy.mockRestore();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("extracts and loads inline script tags from raw markdown content before DOMPurify strips them", async () => {
|
|
209
|
+
const component = new ShadowClawPages();
|
|
210
|
+
component.db = {} as any;
|
|
211
|
+
component.selectedPage = { groupId: "group-1", path: "posts/inline.md" };
|
|
212
|
+
|
|
213
|
+
const postContent = [
|
|
214
|
+
"---",
|
|
215
|
+
'title: "Inline Script Page"',
|
|
216
|
+
"---",
|
|
217
|
+
"# Inline Script Page",
|
|
218
|
+
'<script type="module" src="/pages/main/assets/x-postpress-code.js"></script>',
|
|
219
|
+
].join("\n");
|
|
220
|
+
|
|
221
|
+
(
|
|
222
|
+
readGroupFile as jest.MockedFunction<typeof readGroupFile>
|
|
223
|
+
).mockResolvedValueOnce(postContent);
|
|
224
|
+
|
|
225
|
+
await component.renderSelectedPage();
|
|
226
|
+
|
|
227
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
228
|
+
"/pages/main/assets/x-postpress-code.js",
|
|
229
|
+
);
|
|
230
|
+
expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("resolves and loads scripts by filename stem when existing config does not specify an id", async () => {
|
|
234
|
+
mockGetApprovedCustomElementScriptDescriptors.mockReturnValueOnce([
|
|
235
|
+
{ src: "/pages/main/assets/feed-scroller.js", global: true },
|
|
236
|
+
{ src: "/pages/main/assets/x-hook-component.js" },
|
|
237
|
+
] as any);
|
|
238
|
+
|
|
239
|
+
const component = new ShadowClawPages();
|
|
240
|
+
component.db = {} as any;
|
|
241
|
+
component.selectedPage = { groupId: "group-1", path: "posts/legacy.md" };
|
|
242
|
+
|
|
243
|
+
const postContent = [
|
|
244
|
+
"---",
|
|
245
|
+
'title: "Legacy Post"',
|
|
246
|
+
"dependencies:",
|
|
247
|
+
' - "x-hook-component"',
|
|
248
|
+
"---",
|
|
249
|
+
"# Legacy Post",
|
|
250
|
+
].join("\n");
|
|
251
|
+
|
|
252
|
+
(
|
|
253
|
+
readGroupFile as jest.MockedFunction<typeof readGroupFile>
|
|
254
|
+
).mockResolvedValueOnce(postContent);
|
|
255
|
+
|
|
256
|
+
await component.renderSelectedPage();
|
|
257
|
+
|
|
258
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
259
|
+
"/pages/main/assets/feed-scroller.js",
|
|
260
|
+
);
|
|
261
|
+
expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
|
|
262
|
+
"/pages/main/assets/x-hook-component.js",
|
|
263
|
+
);
|
|
264
|
+
expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -3,6 +3,7 @@ import { Signal } from "signal-polyfill";
|
|
|
3
3
|
import { effect } from "../../core/effect.js";
|
|
4
4
|
|
|
5
5
|
import { splitFrontmatter } from "../../utils/frontmatter.js";
|
|
6
|
+
import { resolvePageScriptDependencies } from "../../utils/resolve-page-script-dependencies.js";
|
|
6
7
|
import { formatSeoTitle } from "../../utils/seo.js";
|
|
7
8
|
import { getSiteConfigSeoSettings } from "../../storage/static-main-site.js";
|
|
8
9
|
import { CONFIG_KEYS } from "../../config/config.js";
|
|
@@ -1524,16 +1525,16 @@ export class ShadowClawPages extends ShadowClawElement {
|
|
|
1524
1525
|
return;
|
|
1525
1526
|
}
|
|
1526
1527
|
|
|
1527
|
-
// Extract approved script srcs from the raw
|
|
1528
|
+
// Extract approved script srcs from the raw content *before* DOMPurify
|
|
1528
1529
|
// strips <script> tags. DOMPurify removes scripts during sanitization,
|
|
1529
1530
|
// so we must collect them here from the raw string, not from the DOM.
|
|
1530
|
-
const
|
|
1531
|
+
const rawScriptSrcs: string[] = [];
|
|
1531
1532
|
const scriptSrcRegex = /<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi;
|
|
1532
1533
|
let scriptMatch: RegExpExecArray | null;
|
|
1533
|
-
while ((scriptMatch = scriptSrcRegex.exec(
|
|
1534
|
+
while ((scriptMatch = scriptSrcRegex.exec(content)) !== null) {
|
|
1534
1535
|
const src = scriptMatch[1];
|
|
1535
1536
|
if (src && isApprovedScript(src)) {
|
|
1536
|
-
|
|
1537
|
+
rawScriptSrcs.push(src);
|
|
1537
1538
|
}
|
|
1538
1539
|
}
|
|
1539
1540
|
|
|
@@ -1543,22 +1544,45 @@ export class ShadowClawPages extends ShadowClawElement {
|
|
|
1543
1544
|
// connectedCallback fires correctly and slotchange is captured on
|
|
1544
1545
|
// first connection rather than after a late upgrade.
|
|
1545
1546
|
const scriptDescriptors = getApprovedCustomElementScriptDescriptors();
|
|
1547
|
+
const combinedFrontmatter = {
|
|
1548
|
+
...(parsedFrontmatter?.data || {}),
|
|
1549
|
+
scripts: [
|
|
1550
|
+
...(Array.isArray(parsedFrontmatter?.data?.scripts)
|
|
1551
|
+
? parsedFrontmatter.data.scripts
|
|
1552
|
+
: parsedFrontmatter?.data?.scripts
|
|
1553
|
+
? [parsedFrontmatter.data.scripts]
|
|
1554
|
+
: []),
|
|
1555
|
+
...rawScriptSrcs,
|
|
1556
|
+
],
|
|
1557
|
+
};
|
|
1558
|
+
const resolvedScripts = resolvePageScriptDependencies(
|
|
1559
|
+
scriptDescriptors,
|
|
1560
|
+
combinedFrontmatter,
|
|
1561
|
+
content,
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1546
1564
|
await Promise.all(
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1565
|
+
resolvedScripts.map(async (desc) => {
|
|
1566
|
+
try {
|
|
1567
|
+
await loadApprovedCustomElementScript(desc.src);
|
|
1568
|
+
if (desc.hasInit) {
|
|
1569
|
+
try {
|
|
1570
|
+
const mod = await import(/* @vite-ignore */ desc.src);
|
|
1571
|
+
if (typeof mod?.init === "function") {
|
|
1572
|
+
await mod.init();
|
|
1573
|
+
}
|
|
1574
|
+
} catch (e) {
|
|
1575
|
+
console.error(
|
|
1576
|
+
"[ShadowClaw] Error executing init for " + desc.src,
|
|
1577
|
+
e,
|
|
1578
|
+
);
|
|
1555
1579
|
}
|
|
1556
|
-
} catch (e) {
|
|
1557
|
-
console.error(
|
|
1558
|
-
"[ShadowClaw] Error executing init for " + src,
|
|
1559
|
-
e,
|
|
1560
|
-
);
|
|
1561
1580
|
}
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
console.warn(
|
|
1583
|
+
`[ShadowClaw] Error loading custom element script ${desc.src}:`,
|
|
1584
|
+
err,
|
|
1585
|
+
);
|
|
1562
1586
|
}
|
|
1563
1587
|
}),
|
|
1564
1588
|
);
|
|
@@ -25,8 +25,10 @@ const mockApplyWebMcpMode = jest.fn<(mode: WebMcpMode) => void>();
|
|
|
25
25
|
|
|
26
26
|
let lastActivatedListener: ((event: any) => void) | null = null;
|
|
27
27
|
let lastCancelListener: ((event: any) => void) | null = null;
|
|
28
|
+
let lastChangeListener: ((event: any) => void) | null = null;
|
|
28
29
|
const mockUnsubscribeActivated = jest.fn<() => void>();
|
|
29
30
|
const mockUnsubscribeCancel = jest.fn<() => void>();
|
|
31
|
+
const mockUnsubscribeChange = jest.fn<() => void>();
|
|
30
32
|
const mockOnWebMcpToolActivated = jest.fn((cb: any) => {
|
|
31
33
|
lastActivatedListener = cb;
|
|
32
34
|
return mockUnsubscribeActivated;
|
|
@@ -35,6 +37,10 @@ const mockOnWebMcpToolCancel = jest.fn((cb: any) => {
|
|
|
35
37
|
lastCancelListener = cb;
|
|
36
38
|
return mockUnsubscribeCancel;
|
|
37
39
|
});
|
|
40
|
+
const mockOnWebMcpToolChange = jest.fn((cb: any) => {
|
|
41
|
+
lastChangeListener = cb;
|
|
42
|
+
return mockUnsubscribeChange;
|
|
43
|
+
});
|
|
38
44
|
|
|
39
45
|
jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
|
|
40
46
|
registerWebMcpTools: mockRegisterWebMcpTools,
|
|
@@ -42,6 +48,7 @@ jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
|
|
|
42
48
|
setWebMcpMode: mockApplyWebMcpMode,
|
|
43
49
|
onWebMcpToolActivated: mockOnWebMcpToolActivated,
|
|
44
50
|
onWebMcpToolCancel: mockOnWebMcpToolCancel,
|
|
51
|
+
onWebMcpToolChange: mockOnWebMcpToolChange,
|
|
45
52
|
}));
|
|
46
53
|
|
|
47
54
|
const mockHandleWorkerMessage = jest
|
|
@@ -234,6 +241,7 @@ describe("syncWebMcpRegistration", () => {
|
|
|
234
241
|
|
|
235
242
|
expect(mockOnWebMcpToolActivated).toHaveBeenCalled();
|
|
236
243
|
expect(mockOnWebMcpToolCancel).toHaveBeenCalled();
|
|
244
|
+
expect(mockOnWebMcpToolChange).toHaveBeenCalled();
|
|
237
245
|
|
|
238
246
|
// Trigger activated listener
|
|
239
247
|
lastActivatedListener?.({ toolName: "read_file" });
|
|
@@ -248,9 +256,17 @@ describe("syncWebMcpRegistration", () => {
|
|
|
248
256
|
"webmcp-tool-cancel",
|
|
249
257
|
{ toolName: "read_file" },
|
|
250
258
|
);
|
|
259
|
+
|
|
260
|
+
// Trigger change listener
|
|
261
|
+
const dummyEvt = new Event("toolchange");
|
|
262
|
+
lastChangeListener?.(dummyEvt);
|
|
263
|
+
expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
|
|
264
|
+
"webmcp-tool-change",
|
|
265
|
+
{ event: dummyEvt },
|
|
266
|
+
);
|
|
251
267
|
});
|
|
252
268
|
|
|
253
|
-
it("should unsubscribe from toolactivated and
|
|
269
|
+
it("should unsubscribe from toolactivated, toolcancel, and toolchange when webMcpEffectCleanup is executed", async () => {
|
|
254
270
|
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
255
271
|
await mockOrchestrator.webMcpRegistrationLock;
|
|
256
272
|
|
|
@@ -261,6 +277,7 @@ describe("syncWebMcpRegistration", () => {
|
|
|
261
277
|
|
|
262
278
|
expect(mockUnsubscribeActivated).toHaveBeenCalled();
|
|
263
279
|
expect(mockUnsubscribeCancel).toHaveBeenCalled();
|
|
280
|
+
expect(mockUnsubscribeChange).toHaveBeenCalled();
|
|
264
281
|
});
|
|
265
282
|
|
|
266
283
|
it("should setup effect and register tools filtered by group toolTags", async () => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
unregisterWebMcpTools,
|
|
8
8
|
onWebMcpToolActivated,
|
|
9
9
|
onWebMcpToolCancel,
|
|
10
|
+
onWebMcpToolChange,
|
|
10
11
|
} from "../../../subsystems/mcp/webmcp.js";
|
|
11
12
|
|
|
12
13
|
import { effect } from "../../effect.js";
|
|
@@ -53,6 +54,10 @@ export function syncWebMcpRegistration(
|
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
const unsubscribeChange = onWebMcpToolChange((event) => {
|
|
58
|
+
orchestrator.events?.emit("webmcp-tool-change", { event });
|
|
59
|
+
});
|
|
60
|
+
|
|
56
61
|
// Register WebMCP tools and re-register when tool config changes.
|
|
57
62
|
// This effect runs once immediately to perform the initial registration.
|
|
58
63
|
// We intentionally do NOT call isWebMcpSupported() here — that accesses
|
|
@@ -132,6 +137,9 @@ export function syncWebMcpRegistration(
|
|
|
132
137
|
try {
|
|
133
138
|
unsubscribeCancel();
|
|
134
139
|
} catch {}
|
|
140
|
+
try {
|
|
141
|
+
unsubscribeChange();
|
|
142
|
+
} catch {}
|
|
135
143
|
if (typeof disposeEffect === "function") {
|
|
136
144
|
disposeEffect();
|
|
137
145
|
}
|
|
@@ -861,14 +861,19 @@ describe("initControlPlane", () => {
|
|
|
861
861
|
});
|
|
862
862
|
|
|
863
863
|
try {
|
|
864
|
-
// 1. Client has WebMCP tools registered, but
|
|
864
|
+
// 1. Client has WebMCP tools registered, but read_file is not among them
|
|
865
865
|
await expect(
|
|
866
|
-
executeClientControlCommand("invoke-tool", { toolName: "
|
|
866
|
+
executeClientControlCommand("invoke-tool", { toolName: "read_file" }),
|
|
867
867
|
).rejects.toThrow(
|
|
868
|
-
"Tool '
|
|
868
|
+
"Tool 'read_file' is not enabled or registered on this client.",
|
|
869
869
|
);
|
|
870
870
|
|
|
871
|
-
// 2.
|
|
871
|
+
// 2. Tool is not enabled on this client
|
|
872
|
+
await expect(
|
|
873
|
+
executeClientControlCommand("invoke-tool", { toolName: "ask_user" }),
|
|
874
|
+
).rejects.toThrow("Tool 'ask_user' is not enabled on this client.");
|
|
875
|
+
|
|
876
|
+
// 3. Conversation group has restricted toolTags
|
|
872
877
|
const { orchestratorStore } =
|
|
873
878
|
await import("../../stores/orchestrator.js");
|
|
874
879
|
const groupsSpy = jest
|
|
@@ -465,6 +465,58 @@ describe("custom-element-security", () => {
|
|
|
465
465
|
siteConfigScript.remove();
|
|
466
466
|
}
|
|
467
467
|
});
|
|
468
|
+
|
|
469
|
+
it("preserves id, name, global, and autoload in script descriptors and approves by id", () => {
|
|
470
|
+
const siteConfigScript = document.createElement("script");
|
|
471
|
+
siteConfigScript.id = "shadow-claw-site-config";
|
|
472
|
+
siteConfigScript.type = "application/json";
|
|
473
|
+
siteConfigScript.textContent = JSON.stringify({
|
|
474
|
+
customElements: {
|
|
475
|
+
allowedDomains: ["kherrick.github.io"],
|
|
476
|
+
scripts: [
|
|
477
|
+
{
|
|
478
|
+
id: "feed-scroller",
|
|
479
|
+
src: "/pages/main/assets/feed-scroller.js",
|
|
480
|
+
global: true,
|
|
481
|
+
hasInit: true,
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "x-hook-component",
|
|
485
|
+
src: "/pages/main/assets/x-hook-component.js",
|
|
486
|
+
hasInit: true,
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
document.head.appendChild(siteConfigScript);
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
installCustomElementsRegistryGuard();
|
|
495
|
+
|
|
496
|
+
const descriptors = getApprovedCustomElementScriptDescriptors();
|
|
497
|
+
expect(descriptors).toEqual([
|
|
498
|
+
{
|
|
499
|
+
id: "feed-scroller",
|
|
500
|
+
src: "/pages/main/assets/feed-scroller.js",
|
|
501
|
+
global: true,
|
|
502
|
+
autoload: true,
|
|
503
|
+
hasInit: true,
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "x-hook-component",
|
|
507
|
+
src: "/pages/main/assets/x-hook-component.js",
|
|
508
|
+
hasInit: true,
|
|
509
|
+
},
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
expect(isApprovedScript("feed-scroller")).toBe(true);
|
|
513
|
+
expect(isApprovedScript("x-hook-component")).toBe(true);
|
|
514
|
+
expect(isApprovedScript("unknown-id")).toBe(false);
|
|
515
|
+
} finally {
|
|
516
|
+
uninstallCustomElementsRegistryGuard();
|
|
517
|
+
siteConfigScript.remove();
|
|
518
|
+
}
|
|
519
|
+
});
|
|
468
520
|
});
|
|
469
521
|
|
|
470
522
|
describe("hasApprovedCustomElement", () => {
|
|
@@ -564,6 +616,19 @@ describe("custom-element-security", () => {
|
|
|
564
616
|
).toBe(true);
|
|
565
617
|
});
|
|
566
618
|
|
|
619
|
+
it("approves script by stem or filename when config has no id", () => {
|
|
620
|
+
setApprovedCustomElementScripts([
|
|
621
|
+
"/pages/main/assets/x-hook-component.js",
|
|
622
|
+
{ src: "/pages/main/assets/x-postpress-code.js" },
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
expect(isApprovedScript("x-hook-component")).toBe(true);
|
|
626
|
+
expect(isApprovedScript("x-hook-component.js")).toBe(true);
|
|
627
|
+
expect(isApprovedScript("x-postpress-code")).toBe(true);
|
|
628
|
+
expect(isApprovedScript("x-postpress-code.js")).toBe(true);
|
|
629
|
+
expect(isApprovedScript("missing-widget")).toBe(false);
|
|
630
|
+
});
|
|
631
|
+
|
|
567
632
|
it("rejects unapproved script paths", () => {
|
|
568
633
|
setApprovedCustomElementScripts([
|
|
569
634
|
"/pages/main/assets/x-hook-component.js",
|
|
@@ -206,6 +206,13 @@ setEmbeddedConfigHook(configureCustomElementSecurity);
|
|
|
206
206
|
|
|
207
207
|
export interface CustomElementScriptDescriptor {
|
|
208
208
|
src: string;
|
|
209
|
+
id?: string;
|
|
210
|
+
name?: string;
|
|
211
|
+
global?: boolean;
|
|
212
|
+
autoload?: boolean;
|
|
213
|
+
type?: string;
|
|
214
|
+
async?: boolean;
|
|
215
|
+
defer?: boolean;
|
|
209
216
|
hasInit: boolean;
|
|
210
217
|
}
|
|
211
218
|
|
|
@@ -215,15 +222,50 @@ let activeApprovedScripts: CustomElementScriptDescriptor[] = [];
|
|
|
215
222
|
* Set approved custom element scripts in memory.
|
|
216
223
|
*/
|
|
217
224
|
export function setApprovedCustomElementScripts(
|
|
218
|
-
scripts: Array<string |
|
|
225
|
+
scripts: Array<string | Partial<CustomElementScriptDescriptor> | any>,
|
|
219
226
|
): void {
|
|
220
227
|
if (Array.isArray(scripts)) {
|
|
221
228
|
activeApprovedScripts = scripts
|
|
222
229
|
.map((s: any) => {
|
|
223
230
|
const src = typeof s === "string" ? s : s?.src;
|
|
231
|
+
const id =
|
|
232
|
+
typeof s === "object" && s !== null && typeof s.id === "string"
|
|
233
|
+
? s.id.trim()
|
|
234
|
+
: undefined;
|
|
235
|
+
const name =
|
|
236
|
+
typeof s === "object" && s !== null && typeof s.name === "string"
|
|
237
|
+
? s.name.trim()
|
|
238
|
+
: undefined;
|
|
239
|
+
const global =
|
|
240
|
+
typeof s === "object" && s !== null
|
|
241
|
+
? Boolean(s.global || s.autoload)
|
|
242
|
+
: false;
|
|
243
|
+
const type =
|
|
244
|
+
typeof s === "object" && s !== null && typeof s.type === "string"
|
|
245
|
+
? s.type
|
|
246
|
+
: undefined;
|
|
247
|
+
const async =
|
|
248
|
+
typeof s === "object" && s !== null && typeof s.async === "boolean"
|
|
249
|
+
? s.async
|
|
250
|
+
: undefined;
|
|
251
|
+
const defer =
|
|
252
|
+
typeof s === "object" && s !== null && typeof s.defer === "boolean"
|
|
253
|
+
? s.defer
|
|
254
|
+
: undefined;
|
|
224
255
|
const hasInit =
|
|
225
256
|
typeof s === "object" && s !== null ? Boolean(s.hasInit) : false;
|
|
226
|
-
|
|
257
|
+
|
|
258
|
+
const descriptor: CustomElementScriptDescriptor = {
|
|
259
|
+
src,
|
|
260
|
+
...(id ? { id } : {}),
|
|
261
|
+
...(name ? { name } : {}),
|
|
262
|
+
...(global ? { global: true, autoload: true } : {}),
|
|
263
|
+
...(type ? { type } : {}),
|
|
264
|
+
...(async ? { async } : {}),
|
|
265
|
+
...(defer ? { defer } : {}),
|
|
266
|
+
hasInit,
|
|
267
|
+
};
|
|
268
|
+
return descriptor;
|
|
227
269
|
})
|
|
228
270
|
.filter(
|
|
229
271
|
(entry): entry is CustomElementScriptDescriptor =>
|
|
@@ -292,11 +334,32 @@ export function isApprovedScript(src: string): boolean {
|
|
|
292
334
|
return false;
|
|
293
335
|
}
|
|
294
336
|
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
337
|
+
const descriptors = getApprovedCustomElementScriptDescriptors();
|
|
338
|
+
if (descriptors.length === 0) {
|
|
297
339
|
return false;
|
|
298
340
|
}
|
|
299
341
|
|
|
342
|
+
const trimmedLower = trimmed.toLowerCase();
|
|
343
|
+
const matchByIdOrName = descriptors.some(
|
|
344
|
+
(d) =>
|
|
345
|
+
(d.id && d.id.toLowerCase() === trimmedLower) ||
|
|
346
|
+
(d.name && d.name.toLowerCase() === trimmedLower),
|
|
347
|
+
);
|
|
348
|
+
if (matchByIdOrName) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const matchByStemOrFilename = descriptors.some((d) => {
|
|
353
|
+
const base = (d.src.split("/").pop() || "").toLowerCase();
|
|
354
|
+
const stem = base.replace(/\.(js|mjs|cjs|ts)$/i, "");
|
|
355
|
+
return base === trimmedLower || stem === trimmedLower;
|
|
356
|
+
});
|
|
357
|
+
if (matchByStemOrFilename) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const approved = getApprovedCustomElementScripts();
|
|
362
|
+
|
|
300
363
|
const clean = trimmed.split("?")[0].split("#")[0].trim();
|
|
301
364
|
const stripPrefix = (s: string) =>
|
|
302
365
|
s
|
package/src/stores/tools.test.ts
CHANGED
|
@@ -237,9 +237,11 @@ describe("ToolsStore — declarative tools management", () => {
|
|
|
237
237
|
store = new ToolsStore();
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
-
it("defaults to
|
|
240
|
+
it("defaults to having declarative tools not enabled when not explicitly configured", () => {
|
|
241
241
|
expect(store.declarativeToolNamesEnabled).toBeNull();
|
|
242
|
-
expect(store.isDeclarativeToolEnabled("generate_random_number")).toBe(
|
|
242
|
+
expect(store.isDeclarativeToolEnabled("generate_random_number")).toBe(
|
|
243
|
+
false,
|
|
244
|
+
);
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
it("toggles declarative tools and persists enabled state", async () => {
|
|
@@ -298,7 +300,7 @@ describe("ToolsStore — declarative tools management", () => {
|
|
|
298
300
|
const refreshed = await store.refreshDeclarativeTools(db, "br:main");
|
|
299
301
|
expect(refreshed).toHaveLength(1);
|
|
300
302
|
expect(store.declarativeTools).toHaveLength(1);
|
|
301
|
-
expect(store.enabledDeclarativeTools).toHaveLength(
|
|
303
|
+
expect(store.enabledDeclarativeTools).toHaveLength(0);
|
|
302
304
|
});
|
|
303
305
|
|
|
304
306
|
describe("Remote Site Integration", () => {
|