shadow-claw 1.40.1 → 1.41.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 -0
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
- package/dist/cli/chunk-3oLxlxPn.js +1 -0
- package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
- package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
- package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
- package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
- package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
- package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
- package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
- package/dist/cli/chunk-BjOeVLb42.js +1 -0
- package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
- package/dist/cli/chunk-BqWBd_jF2.js +1 -0
- package/dist/cli/{chunk-Bc9nRNyN.js → chunk-BvVUnHTq.js} +12 -9
- package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
- package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
- package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
- package/dist/cli/chunk-CCx8WOwz2.js +1 -0
- package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
- package/dist/cli/chunk-CUUERioR2.js +1 -0
- package/dist/cli/chunk-CXAobnJd.js +1 -0
- package/dist/cli/chunk-CY1X0pOF2.js +1 -0
- package/dist/cli/chunk-CZvvgLVv.js +1 -0
- package/dist/cli/chunk-CbcwbfJB.js +1 -0
- package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
- package/dist/cli/chunk-Cj9wl0qx.js +1 -0
- package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
- package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
- package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
- package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
- package/dist/cli/chunk-DR2XtWRo.js +1 -0
- package/dist/cli/{chunk-DID5VvOG.js → chunk-DTES-dE6.js} +3 -3
- package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
- package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
- package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
- package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
- package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
- package/dist/cli/chunk-UcNbHaRQ.js +1 -0
- package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
- package/dist/cli/chunk-bQQkVCw-.js +94 -0
- package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
- package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
- package/dist/cli/chunk-p0n_c-I2.js +1 -0
- package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
- package/dist/cli/cli.js +1 -1
- package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
- package/dist/electron/main.cjs +1 -1
- package/dist/headless-agent.js +1 -1
- package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
- package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
- package/dist/lib/components/index.js +26 -26
- 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/syncSlottedSidebarActiveLinks.d.ts +10 -0
- package/dist/lib/components/shadow-claw-a2ui.js +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/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
- package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
- package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
- package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
- package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
- package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
- package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
- package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
- package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
- package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
- package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
- package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
- package/dist/lib/index.js +27 -27
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
- package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
- package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
- package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
- package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
- package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
- package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
- package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
- package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
- package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
- package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
- package/dist/lib/{orchestrator-CMN43L0F.js → orchestrator-Dn1ZOz9B.js} +12 -12
- package/dist/lib/{orchestrator-CMN43L0F.js.map → orchestrator-Dn1ZOz9B.js.map} +1 -1
- package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
- package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
- package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
- package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
- package/dist/lib/{shadow-claw-4x3LqlII.js → shadow-claw-CtCj5Rn_.js} +121 -16
- package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
- package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js → shadow-claw-file-viewer-CAsn9NxH.js} +10 -10
- package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
- package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
- package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
- package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
- package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
- package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
- package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
- package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/namespacedStorage.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
- package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
- package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
- package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
- 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 +2 -1
- package/dist/public/README.md +1 -0
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/app-routes-C6-H2zPH.js +1 -0
- 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-pages/shadow-claw-pages.css +1 -1
- package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
- package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
- package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
- package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
- package/dist/public/docs/example/article/index.html +4 -4
- package/dist/public/docs/guides/publishing-to-github-pages.md +49 -6
- package/dist/public/docs/publishing/index.html +4 -4
- package/dist/public/docs/skill-creator/index.html +4 -4
- package/dist/public/docs/subsystems/pages.md +10 -3
- package/dist/public/docs/subsystems/webmcp.md +89 -1
- package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
- package/dist/public/e2e/README.md +2 -2
- package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
- package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
- package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
- package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
- package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
- package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
- package/dist/public/index.css +1 -1
- package/dist/public/index.html +4 -4
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
- package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +4 -4
- package/dist/public/main/memory/index.html +4 -4
- package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
- package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
- package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
- package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
- package/dist/public/{orchestrator-Dc_Q73La.js → orchestrator-CQcc7OFA.js} +2 -2
- package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
- package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Dabb2AEp.js → shadow-claw-WwfOu-tu.js} +33 -3
- package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
- package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
- package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
- package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-CDs1QGXk.js → shadow-claw-file-viewer-DYgZjZb9.js} +1 -1
- package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
- package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
- package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
- package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
- package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
- package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
- package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
- package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
- package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
- package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
- package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
- package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
- package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
- package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
- package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
- package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
- package/dist/public/theme-init.js +1 -1
- package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
- package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
- package/dist/server.js +188 -188
- package/package.json +1 -1
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +7 -1
- package/src/cli/prerender/utils/insert-before-closing-head.test.ts +12 -0
- package/src/cli/prerender/utils/insert-before-closing-head.ts +4 -6
- package/src/cli/site-config/apply.test.ts +90 -0
- package/src/cli/site-config/apply.ts +181 -13
- package/src/components/shadow-claw/shadow-claw.css +26 -0
- package/src/components/shadow-claw/shadow-claw.html +4 -0
- package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
- package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
- package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
- package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
- package/src/components/shadow-claw/utils/getRoute.ts +18 -0
- package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
- package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
- package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
- package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
- package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +52 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +28 -1
- package/src/server/middleware/static-files.test.ts +36 -0
- package/src/server/middleware/static-files.ts +14 -6
- package/src/storage/staticRouting.test.ts +36 -0
- package/src/storage/staticRouting.ts +31 -3
- package/src/subsystems/mcp/webmcp.test.ts +255 -0
- package/src/subsystems/mcp/webmcp.ts +253 -0
- package/dist/cli/chunk-00GAzVPl2.js +0 -1
- package/dist/cli/chunk-2hnFoNNC.js +0 -1
- package/dist/cli/chunk-BKQL4nzF2.js +0 -1
- package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
- package/dist/cli/chunk-BVCGo6uf2.js +0 -1
- package/dist/cli/chunk-BcTH_-FL.js +0 -1
- package/dist/cli/chunk-BmD_sPRt.js +0 -1
- package/dist/cli/chunk-BsxNqqer.js +0 -1
- package/dist/cli/chunk-CCcF5T-c.js +0 -1
- package/dist/cli/chunk-CFhvwUeL.js +0 -91
- package/dist/cli/chunk-CwWtvULn.js +0 -1
- package/dist/cli/chunk-Drf-hXIR2.js +0 -1
- package/dist/cli/chunk-Dt5HNYVi.js +0 -1
- package/dist/cli/chunk-DwZQi9Ax.js +0 -1
- package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
- package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
- package/dist/lib/shadow-claw-4x3LqlII.js.map +0 -1
- package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
- package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
- package/dist/public/app-routes-BMQ4HFaM.js +0 -1
- package/dist/public/syncWebMcpRegistration-4bT0YUyZ.js +0 -1
|
@@ -21,6 +21,16 @@ export async function navigateToRoute(
|
|
|
21
21
|
const targetPath = buildRoutePath(route);
|
|
22
22
|
const finalPath = applyBasePath(targetPath);
|
|
23
23
|
|
|
24
|
+
const currentPath =
|
|
25
|
+
typeof window !== "undefined" && window.location
|
|
26
|
+
? `${window.location.pathname}${window.location.search}${window.location.hash}`
|
|
27
|
+
: "";
|
|
28
|
+
|
|
29
|
+
if (currentPath && finalPath === currentPath) {
|
|
30
|
+
await applyRoute(shadow, shadowClaw, db, fStore, oStore, route);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
if (supportsNavigationApi()) {
|
|
25
35
|
const nav = (window as any).navigation;
|
|
26
36
|
|
|
@@ -34,12 +34,20 @@ export function applySidebarNavVisibility(
|
|
|
34
34
|
`.nav-item[data-page="${page}"]`,
|
|
35
35
|
) as HTMLElement | null;
|
|
36
36
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
if (navItem) {
|
|
38
|
+
navItem.hidden = hidden;
|
|
39
|
+
navItem.setAttribute("aria-hidden", String(hidden));
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (page === "chat") {
|
|
43
|
+
const convoEl = shadow.querySelector(
|
|
44
|
+
"shadow-claw-conversations",
|
|
45
|
+
) as HTMLElement | null;
|
|
46
|
+
if (convoEl) {
|
|
47
|
+
convoEl.hidden = hidden;
|
|
48
|
+
convoEl.setAttribute("aria-hidden", String(hidden));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/**
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@jest/globals";
|
|
2
|
+
import { syncSlottedSidebarActiveLinks } from "./syncSlottedSidebarActiveLinks.js";
|
|
3
|
+
|
|
4
|
+
describe("syncSlottedSidebarActiveLinks", () => {
|
|
5
|
+
let host: HTMLElement;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
host = document.createElement("div");
|
|
9
|
+
host.innerHTML = `
|
|
10
|
+
<div slot="sidebar" class="sidebar-custom-sections">
|
|
11
|
+
<a href="/" class="sidebar-link">Home</a>
|
|
12
|
+
<a href="/about" class="sidebar-link">About</a>
|
|
13
|
+
<a href="/portfolio" class="sidebar-link">Portfolio</a>
|
|
14
|
+
<a href="https://external.example.com" class="sidebar-link" target="_blank">External</a>
|
|
15
|
+
</div>
|
|
16
|
+
`;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("marks matching internal link as active and sets aria-current='page'", () => {
|
|
20
|
+
const url = new URL("https://example.com/about");
|
|
21
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
22
|
+
|
|
23
|
+
const homeLink = host.querySelector('a[href="/"]');
|
|
24
|
+
const aboutLink = host.querySelector('a[href="/about"]');
|
|
25
|
+
const portfolioLink = host.querySelector('a[href="/portfolio"]');
|
|
26
|
+
const externalLink = host.querySelector(
|
|
27
|
+
'a[href="https://external.example.com"]',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(aboutLink?.classList.contains("active")).toBe(true);
|
|
31
|
+
expect(aboutLink?.getAttribute("aria-current")).toBe("page");
|
|
32
|
+
|
|
33
|
+
expect(homeLink?.classList.contains("active")).toBe(false);
|
|
34
|
+
expect(homeLink?.hasAttribute("aria-current")).toBe(false);
|
|
35
|
+
|
|
36
|
+
expect(portfolioLink?.classList.contains("active")).toBe(false);
|
|
37
|
+
expect(externalLink?.classList.contains("active")).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles root path matching correctly with trailing slashes", () => {
|
|
41
|
+
const url = new URL("https://example.com/");
|
|
42
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
43
|
+
|
|
44
|
+
const homeLink = host.querySelector('a[href="/"]');
|
|
45
|
+
const aboutLink = host.querySelector('a[href="/about"]');
|
|
46
|
+
|
|
47
|
+
expect(homeLink?.classList.contains("active")).toBe(true);
|
|
48
|
+
expect(homeLink?.getAttribute("aria-current")).toBe("page");
|
|
49
|
+
expect(aboutLink?.classList.contains("active")).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("never marks external links as active", () => {
|
|
53
|
+
const url = new URL("https://example.com/");
|
|
54
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
55
|
+
|
|
56
|
+
const externalLink = host.querySelector(
|
|
57
|
+
'a[href="https://external.example.com"]',
|
|
58
|
+
);
|
|
59
|
+
expect(externalLink?.classList.contains("active")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("handles hash links properly without activating sub-items on base route", () => {
|
|
63
|
+
host.innerHTML = `
|
|
64
|
+
<div slot="sidebar">
|
|
65
|
+
<a href="/portfolio" class="sidebar-link">Portfolio</a>
|
|
66
|
+
<a href="/portfolio#web-apps" class="sidebar-link">Web Apps</a>
|
|
67
|
+
<a href="/portfolio#corporate-sites" class="sidebar-link">Corporate Sites</a>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
// 1. Browsed to base /portfolio with no hash
|
|
72
|
+
const baseUrl = new URL("https://example.com/portfolio");
|
|
73
|
+
syncSlottedSidebarActiveLinks(host, baseUrl);
|
|
74
|
+
|
|
75
|
+
const portfolioLink = host.querySelector('a[href="/portfolio"]');
|
|
76
|
+
const webAppsLink = host.querySelector('a[href="/portfolio#web-apps"]');
|
|
77
|
+
const corporateLink = host.querySelector(
|
|
78
|
+
'a[href="/portfolio#corporate-sites"]',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(portfolioLink?.classList.contains("active")).toBe(true);
|
|
82
|
+
expect(webAppsLink?.classList.contains("active")).toBe(false);
|
|
83
|
+
expect(corporateLink?.classList.contains("active")).toBe(false);
|
|
84
|
+
|
|
85
|
+
// 2. Browsed to /portfolio#web-apps with hash
|
|
86
|
+
const hashUrl = new URL("https://example.com/portfolio#web-apps");
|
|
87
|
+
syncSlottedSidebarActiveLinks(host, hashUrl);
|
|
88
|
+
|
|
89
|
+
expect(portfolioLink?.classList.contains("active")).toBe(false);
|
|
90
|
+
expect(webAppsLink?.classList.contains("active")).toBe(true);
|
|
91
|
+
expect(corporateLink?.classList.contains("active")).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync active state on slotted sidebar navigation links.
|
|
3
|
+
*/
|
|
4
|
+
export function syncSlottedSidebarActiveLinks(
|
|
5
|
+
host: HTMLElement | null,
|
|
6
|
+
currentUrl?: URL,
|
|
7
|
+
): void {
|
|
8
|
+
if (!host || typeof host.querySelectorAll !== "function") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const url =
|
|
13
|
+
currentUrl ||
|
|
14
|
+
(typeof window !== "undefined" && window.location?.href
|
|
15
|
+
? new URL(window.location.href)
|
|
16
|
+
: null);
|
|
17
|
+
if (!url) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const currentPath = url.pathname.replace(/\/+$/, "") || "/";
|
|
22
|
+
|
|
23
|
+
// Select all links slotted into sidebar slots or in custom sections
|
|
24
|
+
const slottedLinks = host.querySelectorAll<HTMLAnchorElement>(
|
|
25
|
+
'[slot="sidebar"] a[href], [slot="sidebar-nav"] a[href], [slot="sidebar-content"] a[href], [slot="sidebar-footer"] a[href], .sidebar-custom-sections a[href]',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
slottedLinks.forEach((link) => {
|
|
29
|
+
try {
|
|
30
|
+
const href = link.getAttribute("href") || "";
|
|
31
|
+
if (
|
|
32
|
+
!href ||
|
|
33
|
+
href.startsWith("#") ||
|
|
34
|
+
href.startsWith("javascript:") ||
|
|
35
|
+
href.startsWith("mailto:") ||
|
|
36
|
+
href.startsWith("tel:")
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const linkUrl = new URL(href, url.origin);
|
|
42
|
+
if (linkUrl.origin !== url.origin) {
|
|
43
|
+
link.classList.remove("active");
|
|
44
|
+
link.removeAttribute("aria-current");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const linkPath = linkUrl.pathname.replace(/\/+$/, "") || "/";
|
|
49
|
+
const isPathMatch = linkPath === currentPath;
|
|
50
|
+
const isMatch =
|
|
51
|
+
isPathMatch && (linkUrl.hash ? linkUrl.hash === url.hash : !url.hash);
|
|
52
|
+
|
|
53
|
+
if (isMatch) {
|
|
54
|
+
link.classList.add("active");
|
|
55
|
+
link.setAttribute("aria-current", "page");
|
|
56
|
+
} else {
|
|
57
|
+
link.classList.remove("active");
|
|
58
|
+
link.removeAttribute("aria-current");
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Ignore URL parsing errors
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sets up listeners so slotted sidebar active links stay in sync whenever
|
|
68
|
+
* the URL changes (popstate, Navigation API navigate, or iframe replaceState).
|
|
69
|
+
* Returns a cleanup function to remove the listeners.
|
|
70
|
+
*/
|
|
71
|
+
export function setupSlottedSidebarActiveLinks(
|
|
72
|
+
host: HTMLElement | null,
|
|
73
|
+
): () => void {
|
|
74
|
+
if (!host || typeof window === "undefined") {
|
|
75
|
+
return () => {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sync = () => syncSlottedSidebarActiveLinks(host);
|
|
79
|
+
|
|
80
|
+
// Initial sync
|
|
81
|
+
sync();
|
|
82
|
+
|
|
83
|
+
// Re-sync on back/forward navigation and replaceState-driven URL changes
|
|
84
|
+
window.addEventListener("popstate", sync, { passive: true });
|
|
85
|
+
|
|
86
|
+
// Re-sync on Navigation API navigate (Chrome 102+) — covers pushState/replaceState too
|
|
87
|
+
const nav = (window as unknown as { navigation?: EventTarget }).navigation;
|
|
88
|
+
if (nav && typeof nav.addEventListener === "function") {
|
|
89
|
+
nav.addEventListener("navigate", sync, {
|
|
90
|
+
passive: true,
|
|
91
|
+
} as EventListenerOptions);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
window.removeEventListener("popstate", sync);
|
|
96
|
+
if (nav && typeof nav.removeEventListener === "function") {
|
|
97
|
+
nav.removeEventListener("navigate", sync);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -186,6 +186,11 @@ article > h2,
|
|
|
186
186
|
.pages__rendered > h1,
|
|
187
187
|
.pages__rendered > h2 {
|
|
188
188
|
margin-top: 0;
|
|
189
|
+
margin-bottom: var(--shadow-claw-article-header-margin-bottom, 1.5rem);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.pages__rendered :is(h1, h2, h3, h4) {
|
|
193
|
+
clear: both;
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
.pages__iframe {
|
|
@@ -219,7 +224,7 @@ article > h2,
|
|
|
219
224
|
|
|
220
225
|
.pages__dropdown-container {
|
|
221
226
|
align-items: center;
|
|
222
|
-
display: flex;
|
|
227
|
+
display: var(--shadow-claw-pages-dropdown-container-display, flex);
|
|
223
228
|
gap: 0.5rem;
|
|
224
229
|
margin-bottom: 1rem;
|
|
225
230
|
position: relative;
|
|
@@ -487,6 +492,10 @@ article > h2,
|
|
|
487
492
|
}
|
|
488
493
|
}
|
|
489
494
|
|
|
495
|
+
shadow-claw-page-header {
|
|
496
|
+
display: var(--shadow-claw-page-header-display, block);
|
|
497
|
+
}
|
|
498
|
+
|
|
490
499
|
ul {
|
|
491
500
|
line-height: 1.5;
|
|
492
501
|
}
|
|
@@ -23,10 +23,25 @@ const mockRegisterWebMcpTools = jest
|
|
|
23
23
|
const mockUnregisterWebMcpTools = jest.fn<() => void>();
|
|
24
24
|
const mockApplyWebMcpMode = jest.fn<(mode: WebMcpMode) => void>();
|
|
25
25
|
|
|
26
|
+
let lastActivatedListener: ((event: any) => void) | null = null;
|
|
27
|
+
let lastCancelListener: ((event: any) => void) | null = null;
|
|
28
|
+
const mockUnsubscribeActivated = jest.fn<() => void>();
|
|
29
|
+
const mockUnsubscribeCancel = jest.fn<() => void>();
|
|
30
|
+
const mockOnWebMcpToolActivated = jest.fn((cb: any) => {
|
|
31
|
+
lastActivatedListener = cb;
|
|
32
|
+
return mockUnsubscribeActivated;
|
|
33
|
+
});
|
|
34
|
+
const mockOnWebMcpToolCancel = jest.fn((cb: any) => {
|
|
35
|
+
lastCancelListener = cb;
|
|
36
|
+
return mockUnsubscribeCancel;
|
|
37
|
+
});
|
|
38
|
+
|
|
26
39
|
jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
|
|
27
40
|
registerWebMcpTools: mockRegisterWebMcpTools,
|
|
28
41
|
unregisterWebMcpTools: mockUnregisterWebMcpTools,
|
|
29
42
|
setWebMcpMode: mockApplyWebMcpMode,
|
|
43
|
+
onWebMcpToolActivated: mockOnWebMcpToolActivated,
|
|
44
|
+
onWebMcpToolCancel: mockOnWebMcpToolCancel,
|
|
30
45
|
}));
|
|
31
46
|
|
|
32
47
|
const mockHandleWorkerMessage = jest
|
|
@@ -157,6 +172,7 @@ interface MockOrchestrator {
|
|
|
157
172
|
webMcpToolsEnabled: boolean;
|
|
158
173
|
webMcpRegistrationLock: Promise<void>;
|
|
159
174
|
agentWorker: Worker | null;
|
|
175
|
+
events?: { emit: jest.Mock };
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
describe("syncWebMcpRegistration", () => {
|
|
@@ -180,6 +196,7 @@ describe("syncWebMcpRegistration", () => {
|
|
|
180
196
|
webMcpToolsEnabled: true,
|
|
181
197
|
webMcpRegistrationLock: Promise.resolve(),
|
|
182
198
|
agentWorker: {} as unknown as Worker,
|
|
199
|
+
events: { emit: jest.fn() },
|
|
183
200
|
};
|
|
184
201
|
mockDiscoverSkills.mockResolvedValue({ skills: [] });
|
|
185
202
|
mockLoadDeclarativeTools.mockResolvedValue({
|
|
@@ -211,6 +228,41 @@ describe("syncWebMcpRegistration", () => {
|
|
|
211
228
|
expect(mockUnregisterWebMcpTools).toHaveBeenCalled();
|
|
212
229
|
});
|
|
213
230
|
|
|
231
|
+
it("should subscribe to toolactivated and toolcancel events and emit on orchestrator.events", async () => {
|
|
232
|
+
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
233
|
+
await mockOrchestrator.webMcpRegistrationLock;
|
|
234
|
+
|
|
235
|
+
expect(mockOnWebMcpToolActivated).toHaveBeenCalled();
|
|
236
|
+
expect(mockOnWebMcpToolCancel).toHaveBeenCalled();
|
|
237
|
+
|
|
238
|
+
// Trigger activated listener
|
|
239
|
+
lastActivatedListener?.({ toolName: "read_file" });
|
|
240
|
+
expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
|
|
241
|
+
"webmcp-tool-activated",
|
|
242
|
+
{ toolName: "read_file" },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Trigger cancel listener
|
|
246
|
+
lastCancelListener?.({ toolName: "read_file" });
|
|
247
|
+
expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
|
|
248
|
+
"webmcp-tool-cancel",
|
|
249
|
+
{ toolName: "read_file" },
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should unsubscribe from toolactivated and toolcancel when webMcpEffectCleanup is executed", async () => {
|
|
254
|
+
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
255
|
+
await mockOrchestrator.webMcpRegistrationLock;
|
|
256
|
+
|
|
257
|
+
const cleanup = mockOrchestrator.webMcpEffectCleanup;
|
|
258
|
+
expect(cleanup).toBeDefined();
|
|
259
|
+
|
|
260
|
+
cleanup!();
|
|
261
|
+
|
|
262
|
+
expect(mockUnsubscribeActivated).toHaveBeenCalled();
|
|
263
|
+
expect(mockUnsubscribeCancel).toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
214
266
|
it("should setup effect and register tools filtered by group toolTags", async () => {
|
|
215
267
|
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
216
268
|
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
setWebMcpMode as applyWebMcpMode,
|
|
6
6
|
registerWebMcpTools,
|
|
7
7
|
unregisterWebMcpTools,
|
|
8
|
+
onWebMcpToolActivated,
|
|
9
|
+
onWebMcpToolCancel,
|
|
8
10
|
} from "../../../subsystems/mcp/webmcp.js";
|
|
9
11
|
|
|
10
12
|
import { effect } from "../../effect.js";
|
|
@@ -38,6 +40,19 @@ export function syncWebMcpRegistration(
|
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// Subscribe to WebMCP lifecycle events (PR #245 / Chrome 156+ with backwards compatibility).
|
|
44
|
+
const unsubscribeActivated = onWebMcpToolActivated((event) => {
|
|
45
|
+
orchestrator.events?.emit("webmcp-tool-activated", {
|
|
46
|
+
toolName: event.toolName,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const unsubscribeCancel = onWebMcpToolCancel((event) => {
|
|
51
|
+
orchestrator.events?.emit("webmcp-tool-cancel", {
|
|
52
|
+
toolName: event.toolName,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
41
56
|
// Register WebMCP tools and re-register when tool config changes.
|
|
42
57
|
// This effect runs once immediately to perform the initial registration.
|
|
43
58
|
// We intentionally do NOT call isWebMcpSupported() here — that accesses
|
|
@@ -45,7 +60,7 @@ export function syncWebMcpRegistration(
|
|
|
45
60
|
// early-preview renderer. Instead, registerWebMcpTools handles feature detection
|
|
46
61
|
// internally and skips modelContext access entirely when 0 tools are
|
|
47
62
|
// passed.
|
|
48
|
-
|
|
63
|
+
const disposeEffect = effect(() => {
|
|
49
64
|
// Access signals to establish tracking.
|
|
50
65
|
const activeGroupId = orchestratorStore.activeGroupId;
|
|
51
66
|
const allTools = toolsStore.allTools;
|
|
@@ -109,6 +124,18 @@ export function syncWebMcpRegistration(
|
|
|
109
124
|
console.error("WebMCP registration failed:", err);
|
|
110
125
|
});
|
|
111
126
|
});
|
|
127
|
+
|
|
128
|
+
orchestrator.webMcpEffectCleanup = () => {
|
|
129
|
+
try {
|
|
130
|
+
unsubscribeActivated();
|
|
131
|
+
} catch {}
|
|
132
|
+
try {
|
|
133
|
+
unsubscribeCancel();
|
|
134
|
+
} catch {}
|
|
135
|
+
if (typeof disposeEffect === "function") {
|
|
136
|
+
disposeEffect();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
112
139
|
}
|
|
113
140
|
export async function setWebMcpMode(
|
|
114
141
|
_state: Pick<
|
|
@@ -175,6 +175,42 @@ describe("static-files-middleware", () => {
|
|
|
175
175
|
}
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
it("serves SPA shell for app routes even when route directory exists on disk", async () => {
|
|
179
|
+
await register();
|
|
180
|
+
const middleware = app.use.mock.calls[1][0];
|
|
181
|
+
|
|
182
|
+
for (const appPath of ["/files", "/files/", "/pages", "/pages/"]) {
|
|
183
|
+
const req = {
|
|
184
|
+
method: "GET",
|
|
185
|
+
headers: {
|
|
186
|
+
accept: "text/html",
|
|
187
|
+
"sec-fetch-dest": "document",
|
|
188
|
+
"sec-fetch-mode": "navigate",
|
|
189
|
+
},
|
|
190
|
+
originalUrl: appPath,
|
|
191
|
+
url: appPath,
|
|
192
|
+
};
|
|
193
|
+
const res = { setHeader: jest.fn(), sendFile: jest.fn() };
|
|
194
|
+
const next = jest.fn();
|
|
195
|
+
|
|
196
|
+
fsMock.stat.mockImplementation((_targetPath: string, cb: any) => {
|
|
197
|
+
cb(null, { isFile: () => false, isDirectory: () => true });
|
|
198
|
+
});
|
|
199
|
+
fsMock.access.mockImplementation(
|
|
200
|
+
(_targetPath: string, _mode: any, cb: any) => {
|
|
201
|
+
cb(new Error("ENOENT"));
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
middleware(req, res, next);
|
|
206
|
+
|
|
207
|
+
expect(res.sendFile).toHaveBeenCalledWith(
|
|
208
|
+
expect.stringContaining("/root/index.html"),
|
|
209
|
+
);
|
|
210
|
+
expect(next).not.toHaveBeenCalled();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
178
214
|
it("does not serve SPA shell for non-HTML missing requests", async () => {
|
|
179
215
|
await register();
|
|
180
216
|
const middleware = app.use.mock.calls[1][0];
|
|
@@ -112,15 +112,18 @@ export function registerStaticFilesMiddleware(app: Express, rootPath: string) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const urlPathname = new URL(req.url || "/", "http://localhost").pathname;
|
|
115
|
+
if (
|
|
116
|
+
isSpaShellRequest(req, requestPath) ||
|
|
117
|
+
isSpaShellRequest(req, urlPathname)
|
|
118
|
+
) {
|
|
119
|
+
res.sendFile(path.join(rootPath, "index.html"));
|
|
120
|
+
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
115
124
|
const filePath = path.join(rootPath, urlPathname);
|
|
116
125
|
fs.stat(filePath, (err, stats) => {
|
|
117
126
|
if (err || !stats) {
|
|
118
|
-
if (isSpaShellRequest(req, requestPath)) {
|
|
119
|
-
res.sendFile(path.join(rootPath, "index.html"));
|
|
120
|
-
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
127
|
// Check if request has a subpath prefix matching a root file (e.g. /shadow-claw/index.js -> /index.js)
|
|
125
128
|
const strippedSegmentPath = requestPath.replace(/^\/[^/]+/, "");
|
|
126
129
|
if (strippedSegmentPath) {
|
|
@@ -212,6 +215,11 @@ export function registerStaticFilesMiddleware(app: Express, rootPath: string) {
|
|
|
212
215
|
} else {
|
|
213
216
|
res.sendFile(indexPath);
|
|
214
217
|
}
|
|
218
|
+
} else if (
|
|
219
|
+
isSpaShellRequest(req, requestPath) ||
|
|
220
|
+
isSpaShellRequest(req, urlPathname)
|
|
221
|
+
) {
|
|
222
|
+
res.sendFile(path.join(rootPath, "index.html"));
|
|
215
223
|
} else {
|
|
216
224
|
next();
|
|
217
225
|
}
|
|
@@ -73,12 +73,29 @@ describe("staticRouting", () => {
|
|
|
73
73
|
"/pages/main/posts/2026-07-01_03-37-38.md": {
|
|
74
74
|
prettyPath: "/2026/06/30/on-developing-loops/",
|
|
75
75
|
},
|
|
76
|
+
"/pages/main/index.html": {
|
|
77
|
+
prettyPath: "/",
|
|
78
|
+
},
|
|
76
79
|
"/pages/main/about.md": {
|
|
77
80
|
prettyPath: "/about",
|
|
78
81
|
},
|
|
79
82
|
},
|
|
80
83
|
};
|
|
81
84
|
|
|
85
|
+
// Test root path "/"
|
|
86
|
+
expect(resolvePrettyPathToRoute("/", manifest)).toEqual({
|
|
87
|
+
page: "pages",
|
|
88
|
+
groupId: "br:main",
|
|
89
|
+
path: "index.html",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Test root index.html
|
|
93
|
+
expect(resolvePrettyPathToRoute("/index.html", manifest)).toEqual({
|
|
94
|
+
page: "pages",
|
|
95
|
+
groupId: "br:main",
|
|
96
|
+
path: "index.html",
|
|
97
|
+
});
|
|
98
|
+
|
|
82
99
|
// Test with explicit manifest
|
|
83
100
|
expect(
|
|
84
101
|
resolvePrettyPathToRoute("/2026/06/30/on-developing-loops/", manifest),
|
|
@@ -155,6 +172,25 @@ describe("staticRouting", () => {
|
|
|
155
172
|
manifest,
|
|
156
173
|
),
|
|
157
174
|
).toBeNull();
|
|
175
|
+
|
|
176
|
+
const manifestWithRoot: StaticRoutesManifest = {
|
|
177
|
+
routes: {
|
|
178
|
+
"/pages/main/index.html": {
|
|
179
|
+
prettyPath: "/",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
expect(
|
|
185
|
+
resolveRouteToPrettyPath(
|
|
186
|
+
{ page: "pages", groupId: "br:main" },
|
|
187
|
+
manifestWithRoot,
|
|
188
|
+
),
|
|
189
|
+
).toBe("/");
|
|
190
|
+
|
|
191
|
+
expect(resolveRouteToPrettyPath({ page: "pages" }, manifestWithRoot)).toBe(
|
|
192
|
+
"/",
|
|
193
|
+
);
|
|
158
194
|
});
|
|
159
195
|
|
|
160
196
|
it("fetches static routing manifest when not embedded in DOM", async () => {
|
|
@@ -214,9 +214,6 @@ export function resolvePrettyPathToRoute(
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const normalizedPath = normalizePrettyPathKey(pathname);
|
|
217
|
-
if (!normalizedPath) {
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
217
|
|
|
221
218
|
for (const [canonicalKey, routeDef] of Object.entries(
|
|
222
219
|
routesManifest.routes,
|
|
@@ -363,6 +360,29 @@ export function resolveRouteToPrettyPath(
|
|
|
363
360
|
candidateKeys.push(`pages/${normalizedGroupId}/${cleanPath}`);
|
|
364
361
|
candidateKeys.push(`/pages/${groupId}/${cleanPath}`);
|
|
365
362
|
}
|
|
363
|
+
} else if (page === "pages" && !path) {
|
|
364
|
+
if (normalizedGroupId === "main" || !normalizedGroupId) {
|
|
365
|
+
candidateKeys.push(
|
|
366
|
+
"/pages/main/index.html",
|
|
367
|
+
"pages/main/index.html",
|
|
368
|
+
"/pages/br:main/index.html",
|
|
369
|
+
"/pages/br-main/index.html",
|
|
370
|
+
"/main/index.html",
|
|
371
|
+
"main/index.html",
|
|
372
|
+
"/index.html",
|
|
373
|
+
"index.html",
|
|
374
|
+
"/pages/main",
|
|
375
|
+
"pages/main",
|
|
376
|
+
"/pages",
|
|
377
|
+
"pages",
|
|
378
|
+
);
|
|
379
|
+
} else if (normalizedGroupId) {
|
|
380
|
+
candidateKeys.push(
|
|
381
|
+
`/pages/${normalizedGroupId}/index.html`,
|
|
382
|
+
`pages/${normalizedGroupId}/index.html`,
|
|
383
|
+
`/pages/${groupId}/index.html`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
366
386
|
} else if (page === "files" && path) {
|
|
367
387
|
const cleanPath = trimSlashes(path);
|
|
368
388
|
if (normalizedGroupId) {
|
|
@@ -386,5 +406,13 @@ export function resolveRouteToPrettyPath(
|
|
|
386
406
|
}
|
|
387
407
|
}
|
|
388
408
|
|
|
409
|
+
if (page === "pages" && !path) {
|
|
410
|
+
for (const routeDef of Object.values(routesManifest.routes)) {
|
|
411
|
+
if (routeDef && routeDef.prettyPath === "/") {
|
|
412
|
+
return "/";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
389
417
|
return null;
|
|
390
418
|
}
|