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.
Files changed (319) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +1 -0
  4. package/dist/cli/build/build.js +1 -1
  5. package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
  6. package/dist/cli/chunk-3oLxlxPn.js +1 -0
  7. package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
  8. package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
  9. package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
  10. package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
  11. package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
  12. package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
  13. package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
  14. package/dist/cli/chunk-BjOeVLb42.js +1 -0
  15. package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
  16. package/dist/cli/chunk-BqWBd_jF2.js +1 -0
  17. package/dist/cli/{chunk-Bc9nRNyN.js → chunk-BvVUnHTq.js} +12 -9
  18. package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
  19. package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
  20. package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
  21. package/dist/cli/chunk-CCx8WOwz2.js +1 -0
  22. package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
  23. package/dist/cli/chunk-CUUERioR2.js +1 -0
  24. package/dist/cli/chunk-CXAobnJd.js +1 -0
  25. package/dist/cli/chunk-CY1X0pOF2.js +1 -0
  26. package/dist/cli/chunk-CZvvgLVv.js +1 -0
  27. package/dist/cli/chunk-CbcwbfJB.js +1 -0
  28. package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
  29. package/dist/cli/chunk-Cj9wl0qx.js +1 -0
  30. package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
  31. package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
  32. package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
  33. package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
  34. package/dist/cli/chunk-DR2XtWRo.js +1 -0
  35. package/dist/cli/{chunk-DID5VvOG.js → chunk-DTES-dE6.js} +3 -3
  36. package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
  37. package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
  38. package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
  39. package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
  40. package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
  41. package/dist/cli/chunk-UcNbHaRQ.js +1 -0
  42. package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
  43. package/dist/cli/chunk-bQQkVCw-.js +94 -0
  44. package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
  45. package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
  46. package/dist/cli/chunk-p0n_c-I2.js +1 -0
  47. package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
  48. package/dist/cli/cli.js +1 -1
  49. package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
  50. package/dist/electron/main.cjs +1 -1
  51. package/dist/headless-agent.js +1 -1
  52. package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
  53. package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
  54. package/dist/lib/components/index.js +26 -26
  55. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  56. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  57. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  58. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  59. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  60. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  61. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  62. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  63. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  64. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  65. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  66. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  67. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  68. package/dist/lib/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.d.ts +10 -0
  69. package/dist/lib/components/shadow-claw-a2ui.js +1 -1
  70. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  71. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  72. package/dist/lib/components/shadow-claw-channels.js +1 -1
  73. package/dist/lib/components/shadow-claw-chat.js +1 -1
  74. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  75. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  76. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  77. package/dist/lib/components/shadow-claw-files.js +1 -1
  78. package/dist/lib/components/shadow-claw-git.js +1 -1
  79. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  80. package/dist/lib/components/shadow-claw-llm.js +1 -1
  81. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  82. package/dist/lib/components/shadow-claw-networking.js +1 -1
  83. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  84. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  85. package/dist/lib/components/shadow-claw-pages.js +1 -1
  86. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  87. package/dist/lib/components/shadow-claw-settings.js +1 -1
  88. package/dist/lib/components/shadow-claw-storage.js +1 -1
  89. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  90. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  91. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  92. package/dist/lib/components/shadow-claw-tools.js +1 -1
  93. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  94. package/dist/lib/components/shadow-claw.js +1 -1
  95. package/dist/lib/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
  96. package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
  97. package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
  98. package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
  99. package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
  100. package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
  101. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
  102. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
  103. package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
  104. package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
  105. package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
  106. package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
  107. package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
  108. package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
  109. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
  110. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
  111. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
  112. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
  113. package/dist/lib/index.js +27 -27
  114. package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
  115. package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
  116. package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
  117. package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
  118. package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
  119. package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
  120. package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
  121. package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
  122. package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
  123. package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
  124. package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
  125. package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
  126. package/dist/lib/{orchestrator-CMN43L0F.js → orchestrator-Dn1ZOz9B.js} +12 -12
  127. package/dist/lib/{orchestrator-CMN43L0F.js.map → orchestrator-Dn1ZOz9B.js.map} +1 -1
  128. package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
  129. package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
  130. package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
  131. package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
  132. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
  133. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
  134. package/dist/lib/{shadow-claw-4x3LqlII.js → shadow-claw-CtCj5Rn_.js} +121 -16
  135. package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
  136. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
  137. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
  138. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
  139. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
  140. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
  141. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
  142. package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
  143. package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
  144. package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
  145. package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
  146. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
  147. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
  148. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
  149. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
  150. package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js → shadow-claw-file-viewer-CAsn9NxH.js} +10 -10
  151. package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
  152. package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
  153. package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
  154. package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
  155. package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
  156. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
  157. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
  158. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
  159. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
  160. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
  161. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
  162. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
  163. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
  164. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
  165. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
  166. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
  167. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
  168. package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
  169. package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
  170. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
  171. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
  172. package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
  173. package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
  174. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
  175. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
  176. package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
  177. package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
  178. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
  179. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
  180. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
  181. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
  182. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
  183. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
  184. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
  185. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
  186. package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
  187. package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
  188. package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
  189. package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
  190. package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
  191. package/dist/lib/utils/index.js +1 -1
  192. package/dist/lib/utils/namespacedStorage.js +1 -1
  193. package/dist/lib/utils/utils.js +1 -1
  194. package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
  195. package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
  196. package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
  197. package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
  198. package/dist/public/.well-known/mcp/server-card.json +1 -1
  199. package/dist/public/.well-known/mcp.json +1 -1
  200. package/dist/public/AGENTS.md +2 -1
  201. package/dist/public/README.md +1 -0
  202. package/dist/public/agent.worker.js +1 -1
  203. package/dist/public/app-routes-C6-H2zPH.js +1 -0
  204. package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
  205. package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
  206. package/dist/public/components/shadow-claw-pages/shadow-claw-pages.css +1 -1
  207. package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
  208. package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
  209. package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
  210. package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
  211. package/dist/public/docs/example/article/index.html +4 -4
  212. package/dist/public/docs/guides/publishing-to-github-pages.md +49 -6
  213. package/dist/public/docs/publishing/index.html +4 -4
  214. package/dist/public/docs/skill-creator/index.html +4 -4
  215. package/dist/public/docs/subsystems/pages.md +10 -3
  216. package/dist/public/docs/subsystems/webmcp.md +89 -1
  217. package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
  218. package/dist/public/e2e/README.md +2 -2
  219. package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
  220. package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
  221. package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
  222. package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
  223. package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
  224. package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
  225. package/dist/public/index.css +1 -1
  226. package/dist/public/index.html +4 -4
  227. package/dist/public/index.js +1 -1
  228. package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
  229. package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
  230. package/dist/public/llms.txt +1 -1
  231. package/dist/public/main/index.html +4 -4
  232. package/dist/public/main/memory/index.html +4 -4
  233. package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
  234. package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
  235. package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
  236. package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
  237. package/dist/public/{orchestrator-Dc_Q73La.js → orchestrator-CQcc7OFA.js} +2 -2
  238. package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
  239. package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
  240. package/dist/public/service-worker/fetch-proxy.js +1 -1
  241. package/dist/public/service-worker.js +1 -1
  242. package/dist/public/{shadow-claw-Dabb2AEp.js → shadow-claw-WwfOu-tu.js} +33 -3
  243. package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
  244. package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
  245. package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
  246. package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
  247. package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
  248. package/dist/public/{shadow-claw-file-viewer-CDs1QGXk.js → shadow-claw-file-viewer-DYgZjZb9.js} +1 -1
  249. package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
  250. package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
  251. package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
  252. package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
  253. package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
  254. package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
  255. package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
  256. package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
  257. package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
  258. package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
  259. package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
  260. package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
  261. package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
  262. package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
  263. package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
  264. package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
  265. package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
  266. package/dist/public/theme-init.js +1 -1
  267. package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
  268. package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
  269. package/dist/server.js +188 -188
  270. package/package.json +1 -1
  271. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
  272. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +7 -1
  273. package/src/cli/prerender/utils/insert-before-closing-head.test.ts +12 -0
  274. package/src/cli/prerender/utils/insert-before-closing-head.ts +4 -6
  275. package/src/cli/site-config/apply.test.ts +90 -0
  276. package/src/cli/site-config/apply.ts +181 -13
  277. package/src/components/shadow-claw/shadow-claw.css +26 -0
  278. package/src/components/shadow-claw/shadow-claw.html +4 -0
  279. package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
  280. package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
  281. package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
  282. package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
  283. package/src/components/shadow-claw/utils/getRoute.ts +18 -0
  284. package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
  285. package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
  286. package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
  287. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
  288. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
  289. package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
  290. package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
  291. package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +52 -0
  292. package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +28 -1
  293. package/src/server/middleware/static-files.test.ts +36 -0
  294. package/src/server/middleware/static-files.ts +14 -6
  295. package/src/storage/staticRouting.test.ts +36 -0
  296. package/src/storage/staticRouting.ts +31 -3
  297. package/src/subsystems/mcp/webmcp.test.ts +255 -0
  298. package/src/subsystems/mcp/webmcp.ts +253 -0
  299. package/dist/cli/chunk-00GAzVPl2.js +0 -1
  300. package/dist/cli/chunk-2hnFoNNC.js +0 -1
  301. package/dist/cli/chunk-BKQL4nzF2.js +0 -1
  302. package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
  303. package/dist/cli/chunk-BVCGo6uf2.js +0 -1
  304. package/dist/cli/chunk-BcTH_-FL.js +0 -1
  305. package/dist/cli/chunk-BmD_sPRt.js +0 -1
  306. package/dist/cli/chunk-BsxNqqer.js +0 -1
  307. package/dist/cli/chunk-CCcF5T-c.js +0 -1
  308. package/dist/cli/chunk-CFhvwUeL.js +0 -91
  309. package/dist/cli/chunk-CwWtvULn.js +0 -1
  310. package/dist/cli/chunk-Drf-hXIR2.js +0 -1
  311. package/dist/cli/chunk-Dt5HNYVi.js +0 -1
  312. package/dist/cli/chunk-DwZQi9Ax.js +0 -1
  313. package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
  314. package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
  315. package/dist/lib/shadow-claw-4x3LqlII.js.map +0 -1
  316. package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
  317. package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
  318. package/dist/public/app-routes-BMQ4HFaM.js +0 -1
  319. 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 (!navItem) {
38
- return;
37
+ if (navItem) {
38
+ navItem.hidden = hidden;
39
+ navItem.setAttribute("aria-hidden", String(hidden));
39
40
  }
40
41
 
41
- navItem.hidden = hidden;
42
- navItem.setAttribute("aria-hidden", String(hidden));
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
  }
@@ -8,5 +8,8 @@ export function removePreviewIframe(root: ShadowRoot | HTMLElement): void {
8
8
  }
9
9
 
10
10
  iframe.removeAttribute("srcdoc");
11
+ try {
12
+ iframe.src = "about:blank";
13
+ } catch {}
11
14
  iframe.remove();
12
15
  }
@@ -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
- orchestrator.webMcpEffectCleanup = effect(() => {
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
  }