shadow-claw 1.40.0 → 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 (373) hide show
  1. package/.well-known/mcp/server-card.json +2 -2
  2. package/.well-known/mcp/servers.json +1 -1
  3. package/.well-known/mcp.json +2 -2
  4. package/README.md +1 -0
  5. package/dist/cli/build/build.js +1 -1
  6. package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
  7. package/dist/cli/chunk-3oLxlxPn.js +1 -0
  8. package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
  9. package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
  10. package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
  11. package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
  12. package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
  13. package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
  14. package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
  15. package/dist/cli/chunk-BjOeVLb42.js +1 -0
  16. package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
  17. package/dist/cli/chunk-BqWBd_jF2.js +1 -0
  18. package/dist/cli/{chunk-Bc9nRNyN.js → chunk-BvVUnHTq.js} +12 -9
  19. package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
  20. package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
  21. package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
  22. package/dist/cli/chunk-CCx8WOwz2.js +1 -0
  23. package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
  24. package/dist/cli/chunk-CUUERioR2.js +1 -0
  25. package/dist/cli/chunk-CXAobnJd.js +1 -0
  26. package/dist/cli/chunk-CY1X0pOF2.js +1 -0
  27. package/dist/cli/chunk-CZvvgLVv.js +1 -0
  28. package/dist/cli/chunk-CbcwbfJB.js +1 -0
  29. package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
  30. package/dist/cli/chunk-Cj9wl0qx.js +1 -0
  31. package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
  32. package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
  33. package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
  34. package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
  35. package/dist/cli/chunk-DR2XtWRo.js +1 -0
  36. package/dist/cli/{chunk-DID5VvOG.js → chunk-DTES-dE6.js} +3 -3
  37. package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
  38. package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
  39. package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
  40. package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
  41. package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
  42. package/dist/cli/chunk-UcNbHaRQ.js +1 -0
  43. package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
  44. package/dist/cli/chunk-bQQkVCw-.js +94 -0
  45. package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
  46. package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
  47. package/dist/cli/chunk-p0n_c-I2.js +1 -0
  48. package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
  49. package/dist/cli/cli.js +3 -3
  50. package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
  51. package/dist/electron/main.cjs +1 -1
  52. package/dist/headless-agent.js +1 -1
  53. package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
  54. package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
  55. package/dist/lib/components/index.js +26 -26
  56. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  57. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  58. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  59. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  60. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  61. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  62. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  63. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  64. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  65. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  66. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  67. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  68. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  69. package/dist/lib/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.d.ts +10 -0
  70. package/dist/lib/components/shadow-claw-a2ui.js +1 -1
  71. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  72. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  73. package/dist/lib/components/shadow-claw-channels.js +1 -1
  74. package/dist/lib/components/shadow-claw-chat.js +1 -1
  75. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  76. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  77. package/dist/lib/components/shadow-claw-file-viewer/shadow-claw-file-viewer.d.ts +1 -1
  78. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  79. package/dist/lib/components/shadow-claw-files.js +1 -1
  80. package/dist/lib/components/shadow-claw-git.js +1 -1
  81. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  82. package/dist/lib/components/shadow-claw-llm.js +1 -1
  83. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  84. package/dist/lib/components/shadow-claw-networking.js +1 -1
  85. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  86. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  87. package/dist/lib/components/shadow-claw-pages.js +1 -1
  88. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  89. package/dist/lib/components/shadow-claw-settings.js +1 -1
  90. package/dist/lib/components/shadow-claw-storage.js +1 -1
  91. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  92. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  93. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  94. package/dist/lib/components/shadow-claw-tools.js +1 -1
  95. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  96. package/dist/lib/components/shadow-claw.js +1 -1
  97. package/dist/lib/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
  98. package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
  99. package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
  100. package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
  101. package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
  102. package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
  103. package/dist/lib/core/orchestrator/orchestrator-state.d.ts +2 -1
  104. package/dist/lib/core/orchestrator/orchestrator.d.ts +2 -1
  105. package/dist/lib/core/orchestrator/utils/operations/task.d.ts +1 -0
  106. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
  107. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
  108. package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
  109. package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
  110. package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
  111. package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
  112. package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
  113. package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
  114. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
  115. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
  116. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
  117. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
  118. package/dist/lib/index.js +27 -27
  119. package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
  120. package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
  121. package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
  122. package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
  123. package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
  124. package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
  125. package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
  126. package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
  127. package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
  128. package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
  129. package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
  130. package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
  131. package/dist/lib/{orchestrator-Bq9ft3uG.js → orchestrator-Dn1ZOz9B.js} +20 -24
  132. package/dist/lib/orchestrator-Dn1ZOz9B.js.map +1 -0
  133. package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
  134. package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
  135. package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
  136. package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
  137. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
  138. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
  139. package/dist/lib/{shadow-claw-MumteAJy.js → shadow-claw-CtCj5Rn_.js} +121 -16
  140. package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
  141. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
  142. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
  143. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
  144. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
  145. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
  146. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
  147. package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
  148. package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
  149. package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
  150. package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
  151. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
  152. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
  153. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
  154. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
  155. package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js → shadow-claw-file-viewer-CAsn9NxH.js} +30 -30
  156. package/dist/lib/{shadow-claw-file-viewer-CMOnFmcP.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
  157. package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
  158. package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
  159. package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
  160. package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
  161. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
  162. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
  163. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
  164. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
  165. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
  166. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
  167. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
  168. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
  169. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
  170. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
  171. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
  172. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
  173. package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
  174. package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
  175. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
  176. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
  177. package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
  178. package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
  179. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
  180. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
  181. package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
  182. package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
  183. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
  184. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
  185. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
  186. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
  187. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
  188. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
  189. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
  190. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
  191. package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
  192. package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
  193. package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
  194. package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
  195. package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
  196. package/dist/lib/utils/index.js +1 -1
  197. package/dist/lib/utils/namespacedStorage.js +1 -1
  198. package/dist/lib/utils/utils.js +1 -1
  199. package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
  200. package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
  201. package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
  202. package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
  203. package/dist/public/.well-known/mcp/server-card.json +2 -2
  204. package/dist/public/.well-known/mcp/servers.json +1 -1
  205. package/dist/public/.well-known/mcp.json +2 -2
  206. package/dist/public/AGENTS.md +2 -1
  207. package/dist/public/README.md +1 -0
  208. package/dist/public/agent.worker.js +1 -1
  209. package/dist/public/app-routes-C6-H2zPH.js +1 -0
  210. package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
  211. package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
  212. package/dist/public/components/shadow-claw-pages/shadow-claw-pages.css +1 -1
  213. package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
  214. package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
  215. package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
  216. package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
  217. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  218. package/dist/public/docs/example/article/index.html +5 -5
  219. package/dist/public/docs/guides/publishing-to-github-pages.md +49 -6
  220. package/dist/public/docs/publishing/index.html +5 -5
  221. package/dist/public/docs/skill-creator/index.html +5 -5
  222. package/dist/public/docs/subsystems/pages.md +10 -3
  223. package/dist/public/docs/subsystems/webmcp.md +89 -1
  224. package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
  225. package/dist/public/e2e/README.md +2 -2
  226. package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
  227. package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
  228. package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
  229. package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
  230. package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
  231. package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
  232. package/dist/public/index.css +1 -1
  233. package/dist/public/index.html +5 -5
  234. package/dist/public/index.js +1 -1
  235. package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
  236. package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
  237. package/dist/public/llms.txt +1 -1
  238. package/dist/public/main/index.html +5 -5
  239. package/dist/public/main/memory/index.html +5 -5
  240. package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
  241. package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
  242. package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
  243. package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
  244. package/dist/public/{orchestrator-BLkS_jpm.js → orchestrator-CQcc7OFA.js} +2 -2
  245. package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
  246. package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
  247. package/dist/public/service-worker/fetch-proxy.js +1 -1
  248. package/dist/public/service-worker.js +1 -1
  249. package/dist/public/{shadow-claw-DmrxnX3Q.js → shadow-claw-WwfOu-tu.js} +33 -3
  250. package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
  251. package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
  252. package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
  253. package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
  254. package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
  255. package/dist/public/{shadow-claw-file-viewer-a_uRA30F.js → shadow-claw-file-viewer-DYgZjZb9.js} +2 -2
  256. package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
  257. package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
  258. package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
  259. package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
  260. package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
  261. package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
  262. package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
  263. package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
  264. package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
  265. package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
  266. package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
  267. package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
  268. package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
  269. package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
  270. package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
  271. package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
  272. package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
  273. package/dist/public/theme-init.js +1 -1
  274. package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
  275. package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
  276. package/dist/server.js +189 -189
  277. package/package.json +1 -1
  278. package/src/cli/cli.ts +2 -2
  279. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
  280. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +7 -1
  281. package/src/cli/prerender/utils/insert-before-closing-head.test.ts +12 -0
  282. package/src/cli/prerender/utils/insert-before-closing-head.ts +4 -6
  283. package/src/cli/site-config/apply.test.ts +90 -0
  284. package/src/cli/site-config/apply.ts +181 -13
  285. package/src/components/shadow-claw/shadow-claw.css +26 -0
  286. package/src/components/shadow-claw/shadow-claw.html +4 -0
  287. package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
  288. package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
  289. package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
  290. package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
  291. package/src/components/shadow-claw/utils/getRoute.ts +18 -0
  292. package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
  293. package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
  294. package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
  295. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
  296. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
  297. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +31 -31
  298. package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
  299. package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
  300. package/src/core/effect.test.ts +19 -0
  301. package/src/core/orchestrator/orchestrator-state.ts +2 -1
  302. package/src/core/orchestrator/orchestrator.test.ts +686 -5
  303. package/src/core/orchestrator/orchestrator.ts +3 -10
  304. package/src/core/orchestrator/utils/compactContext.test.ts +423 -121
  305. package/src/core/orchestrator/utils/configurePeerJs.test.ts +27 -0
  306. package/src/core/orchestrator/utils/deliverResponse.test.ts +127 -0
  307. package/src/core/orchestrator/utils/dispatchSubagentInvoke.test.ts +23 -0
  308. package/src/core/orchestrator/utils/enqueue.test.ts +520 -86
  309. package/src/core/orchestrator/utils/handleWorkerMessage.test.ts +664 -144
  310. package/src/core/orchestrator/utils/initTasks.node.test.ts +147 -0
  311. package/src/core/orchestrator/utils/initTasks.test.ts +926 -191
  312. package/src/core/orchestrator/utils/initTasks.ts +5 -4
  313. package/src/core/orchestrator/utils/invokeAgent.test.ts +563 -110
  314. package/src/core/orchestrator/utils/invokeAgent.ts +8 -10
  315. package/src/core/orchestrator/utils/loadChannelConfigurations.test.ts +50 -0
  316. package/src/core/orchestrator/utils/operations/channel.test.ts +377 -170
  317. package/src/core/orchestrator/utils/operations/provider.test.ts +663 -85
  318. package/src/core/orchestrator/utils/operations/room.test.ts +74 -11
  319. package/src/core/orchestrator/utils/operations/task.test.ts +452 -56
  320. package/src/core/orchestrator/utils/operations/task.ts +1 -1
  321. package/src/core/orchestrator/utils/operations/vm.test.ts +66 -7
  322. package/src/core/orchestrator/utils/parseDirectToolCommand.test.ts +6 -0
  323. package/src/core/orchestrator/utils/settings.test.ts +248 -81
  324. package/src/core/orchestrator/utils/setupPushTaskListener.test.ts +58 -0
  325. package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +392 -64
  326. package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +32 -3
  327. package/src/server/middleware/static-files.test.ts +36 -0
  328. package/src/server/middleware/static-files.ts +14 -6
  329. package/src/server/routes/mcp.ts +2 -2
  330. package/src/storage/staticRouting.test.ts +36 -0
  331. package/src/storage/staticRouting.ts +31 -3
  332. package/src/stores/file-viewer.test.ts +11 -0
  333. package/src/stores/theme.test.ts +41 -2
  334. package/src/stores/toast.test.ts +25 -0
  335. package/src/subsystems/mcp/webmcp.test.ts +255 -0
  336. package/src/subsystems/mcp/webmcp.ts +253 -0
  337. package/src/worker/tools/bash/native-bash-executor.test.ts +31 -1
  338. package/src/worker/tools/git/utils/extractConflictPaths.test.ts +24 -0
  339. package/src/worker/tools/git/utils/parseConflictRegions.test.ts +46 -0
  340. package/src/worker/tools/git/utils/resolveCorsProxy.test.ts +26 -5
  341. package/src/worker/tools/git/utils/truncateSnippet.test.ts +16 -0
  342. package/src/worker/tools/node-transformers-executor.test.ts +105 -6
  343. package/src/worker/tools/remote-mcp/remote-mcp.test.ts +172 -47
  344. package/src/worker/tools/remote-mcp/utils/formatListToolsOutput.test.ts +19 -0
  345. package/src/worker/tools/remote-mcp/utils/isMcpReauthError.test.ts +45 -0
  346. package/src/worker/tools/remote-mcp/utils/resolveMcpReauth.test.ts +48 -0
  347. package/src/worker/tools/rooms/list-room-members.test.ts +24 -10
  348. package/src/worker/tools/tasks/list-tasks.test.ts +39 -8
  349. package/src/worker/tools/tasks/update-task.test.ts +78 -14
  350. package/src/worker/tools/ui/javascript.test.ts +70 -17
  351. package/src/worker/tools/ui/list-tool-profiles.test.ts +31 -9
  352. package/dist/cli/chunk-00GAzVPl2.js +0 -1
  353. package/dist/cli/chunk-2hnFoNNC.js +0 -1
  354. package/dist/cli/chunk-BKQL4nzF2.js +0 -1
  355. package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
  356. package/dist/cli/chunk-BVCGo6uf2.js +0 -1
  357. package/dist/cli/chunk-BcTH_-FL.js +0 -1
  358. package/dist/cli/chunk-BmD_sPRt.js +0 -1
  359. package/dist/cli/chunk-BsxNqqer.js +0 -1
  360. package/dist/cli/chunk-CCcF5T-c.js +0 -1
  361. package/dist/cli/chunk-CFhvwUeL.js +0 -91
  362. package/dist/cli/chunk-CwWtvULn.js +0 -1
  363. package/dist/cli/chunk-Drf-hXIR2.js +0 -1
  364. package/dist/cli/chunk-Dt5HNYVi.js +0 -1
  365. package/dist/cli/chunk-DwZQi9Ax.js +0 -1
  366. package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
  367. package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
  368. package/dist/lib/orchestrator-Bq9ft3uG.js.map +0 -1
  369. package/dist/lib/shadow-claw-MumteAJy.js.map +0 -1
  370. package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
  371. package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
  372. package/dist/public/app-routes-BMQ4HFaM.js +0 -1
  373. package/dist/public/syncWebMcpRegistration-4bT0YUyZ.js +0 -1
@@ -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";
@@ -14,6 +16,8 @@ import { CONFIG_KEYS } from "../../../config/config.js";
14
16
  import { setConfig } from "../../../db/setConfig.js";
15
17
 
16
18
  import type { ShadowClawDatabase } from "../../../db/db.js";
19
+ import type { DeclarativeToolDefinition } from "../../../subsystems/tools/declarative.js";
20
+ import type { SkillRecord } from "../../../subsystems/skills/types.js";
17
21
  import type { WebMcpMode } from "../../../subsystems/mcp/webmcp.js";
18
22
  import type { OrchestratorState } from "../orchestrator-state.js";
19
23
  import type { Orchestrator } from "../orchestrator.js";
@@ -36,6 +40,19 @@ export function syncWebMcpRegistration(
36
40
  return;
37
41
  }
38
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
+
39
56
  // Register WebMCP tools and re-register when tool config changes.
40
57
  // This effect runs once immediately to perform the initial registration.
41
58
  // We intentionally do NOT call isWebMcpSupported() here — that accesses
@@ -43,7 +60,7 @@ export function syncWebMcpRegistration(
43
60
  // early-preview renderer. Instead, registerWebMcpTools handles feature detection
44
61
  // internally and skips modelContext access entirely when 0 tools are
45
62
  // passed.
46
- orchestrator.webMcpEffectCleanup = effect(() => {
63
+ const disposeEffect = effect(() => {
47
64
  // Access signals to establish tracking.
48
65
  const activeGroupId = orchestratorStore.activeGroupId;
49
66
  const allTools = toolsStore.allTools;
@@ -60,7 +77,7 @@ export function syncWebMcpRegistration(
60
77
  // Small delay to allow the browser's ModelContext to process the unregistrations.
61
78
  await new Promise((resolve) => setTimeout(resolve, 0));
62
79
 
63
- let declarativeTools: any[] = [];
80
+ let declarativeTools: DeclarativeToolDefinition[] = [];
64
81
  try {
65
82
  declarativeTools = await toolsStore.refreshDeclarativeTools(
66
83
  db,
@@ -80,7 +97,7 @@ export function syncWebMcpRegistration(
80
97
  ? candidateTools.filter((t) => group.toolTags!.includes(t.name))
81
98
  : [...globalTools, ...enabledDeclarativeTools];
82
99
 
83
- let skillDiscovery: { skills: any[] } = { skills: [] };
100
+ let skillDiscovery: { skills: SkillRecord[] } = { skills: [] };
84
101
  try {
85
102
  skillDiscovery = await discoverSkills(db, activeGroupId);
86
103
  } catch {
@@ -107,6 +124,18 @@ export function syncWebMcpRegistration(
107
124
  console.error("WebMCP registration failed:", err);
108
125
  });
109
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
+ };
110
139
  }
111
140
  export async function setWebMcpMode(
112
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
  }
@@ -256,7 +256,7 @@ export function registerMcpRoutes(
256
256
  name: "shadow-claw",
257
257
  version: getPackageVersion(),
258
258
  description:
259
- "Browser-native personal AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution.",
259
+ "Multi-runtime AI assistant with local and remote LLM orchestration, Web Workers, OPFS storage, and agentic tool execution.",
260
260
  title: "ShadowClaw MCP Server",
261
261
  websiteUrl: `${origin}/shadow-claw/`,
262
262
  repository: {
@@ -332,7 +332,7 @@ export function registerMcpRoutes(
332
332
  servers: [
333
333
  {
334
334
  name: "shadow-claw",
335
- description: "ShadowClaw Browser-Native AI Assistant MCP Server",
335
+ description: "ShadowClaw Multi-Runtime AI Assistant MCP Server",
336
336
  endpoint: `${origin}/mcp`,
337
337
  capabilities: ["tools", "extensions"],
338
338
  card: `${origin}/.well-known/mcp.json`,
@@ -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
  }
@@ -155,4 +155,15 @@ describe("FileViewerStore", () => {
155
155
  mimeType: "text/plain",
156
156
  });
157
157
  });
158
+
159
+ it("handles empty path and extension-less filenames", async () => {
160
+ (readGroupFile as any).mockResolvedValue("empty-content");
161
+ const s = new FileViewerStore();
162
+
163
+ expect(s.getPreviewBinaryMimeType("")).toBe("");
164
+ expect(s.getPreviewBinaryMimeType("noextension")).toBe("");
165
+
166
+ await s.openFile({} as any, "");
167
+ expect(s.file?.name).toBe("");
168
+ });
158
169
  });
@@ -11,8 +11,8 @@ describe("themeStore", () => {
11
11
  localStorage.clear();
12
12
  document.documentElement.className = "";
13
13
 
14
- // Mock dispatchEvent
15
- (window as any).dispatchEvent = jest.fn();
14
+ // Spy on dispatchEvent so event listeners still fire
15
+ jest.spyOn(window, "dispatchEvent");
16
16
  });
17
17
 
18
18
  it("applies and persists explicit theme", async () => {
@@ -135,4 +135,43 @@ describe("themeStore", () => {
135
135
  expect(themeStore.resolved).toBe("light");
136
136
  expect(localStorage.getItem("shadow-claw-theme")).toBe("system");
137
137
  });
138
+
139
+ it("handles storage event for tab synchronization", async () => {
140
+ const { getNamespacedStorageKey } =
141
+ await import("../utils/namespacedStorage.js");
142
+ const { themeStore } = await import("./theme.js");
143
+ const targetKey = getNamespacedStorageKey("shadow-claw-theme");
144
+
145
+ // Other key should be ignored
146
+ const otherEv = new Event("storage") as any;
147
+ otherEv.key = "other-key";
148
+ otherEv.newValue = "dark";
149
+ window.dispatchEvent(otherEv);
150
+ expect(themeStore.theme).not.toBe("dark");
151
+
152
+ // Target key with null newValue should be ignored
153
+ const nullEv = new Event("storage") as any;
154
+ nullEv.key = targetKey;
155
+ nullEv.newValue = null;
156
+ window.dispatchEvent(nullEv);
157
+
158
+ // Target key with valid newValue updates theme
159
+ const validEv = new Event("storage") as any;
160
+ validEv.key = targetKey;
161
+ validEv.newValue = "dark";
162
+ window.dispatchEvent(validEv);
163
+
164
+ expect(themeStore.theme).toBe("dark");
165
+ expect(themeStore.resolved).toBe("dark");
166
+ });
167
+
168
+ it("falls back gracefully when matchMedia is not a function", async () => {
169
+ (window as any).matchMedia = undefined;
170
+ jest.resetModules();
171
+ localStorage.clear();
172
+ const { themeStore } = await import("./theme.js");
173
+
174
+ themeStore.setTheme("system");
175
+ expect(themeStore.resolved).toBe("light");
176
+ });
138
177
  });
@@ -101,6 +101,31 @@ describe("ToastStore", () => {
101
101
 
102
102
  // No-op for non-existent or action-less toast
103
103
  await store.runAction(999);
104
+
105
+ const noActionId = store.show("without action");
106
+ await store.runAction(noActionId);
107
+ });
108
+
109
+ test("pause and resume handle missing or invalid timers gracefully", () => {
110
+ const store = new ToastStore();
111
+
112
+ // Calling pause on nonexistent toast or already paused toast
113
+ expect(() => store.pause(999)).not.toThrow();
114
+
115
+ const id = store.show("toast", { duration: 1000 });
116
+ store.pause(id);
117
+ // Pause again when timeoutId is undefined
118
+ expect(() => store.pause(id)).not.toThrow();
119
+
120
+ // Calling resume on nonexistent toast, or already running toast, or expired toast
121
+ expect(() => store.resume(999)).not.toThrow();
122
+
123
+ const runningId = store.show("running", { duration: 1000 });
124
+ // Resume when timer.timeoutId is still active
125
+ expect(() => store.resume(runningId)).not.toThrow();
126
+
127
+ // Clear timer on nonexistent toast
128
+ expect(() => store.clearTimer(999)).not.toThrow();
104
129
  });
105
130
 
106
131
  test("resolvers handle invalid inputs gracefully", () => {
@@ -817,4 +817,259 @@ describe("webmcp integration", () => {
817
817
  errorSpy.mockRestore();
818
818
  });
819
819
  });
820
+
821
+ describe("WebMCP lifecycle events (PR #245 / Chrome 156+)", () => {
822
+ let originalWindowOntoolactivated: any;
823
+ let originalWindowOntoolcancel: any;
824
+
825
+ beforeEach(() => {
826
+ originalWindowOntoolactivated = (globalThis as any).window
827
+ ?.ontoolactivated;
828
+ originalWindowOntoolcancel = (globalThis as any).window?.ontoolcancel;
829
+ if (typeof window !== "undefined") {
830
+ delete (window as any).ontoolactivated;
831
+ delete (window as any).ontoolcancel;
832
+ }
833
+ });
834
+
835
+ afterEach(() => {
836
+ if (typeof window !== "undefined") {
837
+ if (originalWindowOntoolactivated !== undefined) {
838
+ (window as any).ontoolactivated = originalWindowOntoolactivated;
839
+ } else {
840
+ delete (window as any).ontoolactivated;
841
+ }
842
+ if (originalWindowOntoolcancel !== undefined) {
843
+ (window as any).ontoolcancel = originalWindowOntoolcancel;
844
+ } else {
845
+ delete (window as any).ontoolcancel;
846
+ }
847
+ }
848
+ });
849
+
850
+ describe("getWebMcpEventTarget", () => {
851
+ it("resolves to document.modelContext when ontoolactivated is in document.modelContext (Chrome 156+ / PR #245)", async () => {
852
+ const { getWebMcpEventTarget } = await import("./webmcp.js");
853
+
854
+ const mockTarget = new EventTarget();
855
+ (mockTarget as any).ontoolactivated = null;
856
+ (mockTarget as any).ontoolcancel = null;
857
+
858
+ Object.defineProperty((globalThis as any).document, "modelContext", {
859
+ configurable: true,
860
+ value: mockTarget,
861
+ });
862
+
863
+ // Also define ontoolactivated on window to ensure modelContext takes precedence
864
+ if (typeof window !== "undefined") {
865
+ (window as any).ontoolactivated = null;
866
+ }
867
+
868
+ expect(getWebMcpEventTarget("toolactivated")).toBe(mockTarget);
869
+ expect(getWebMcpEventTarget("toolcancel")).toBe(mockTarget);
870
+ });
871
+
872
+ it("falls back to window when ontoolactivated is only present on window (legacy Chrome < 156)", async () => {
873
+ const { getWebMcpEventTarget } = await import("./webmcp.js");
874
+
875
+ const mockModelContext = {
876
+ registerTool: jest.fn(),
877
+ // No ontoolactivated or ontoolcancel
878
+ };
879
+
880
+ Object.defineProperty((globalThis as any).document, "modelContext", {
881
+ configurable: true,
882
+ value: mockModelContext,
883
+ });
884
+
885
+ if (typeof window !== "undefined") {
886
+ (window as any).ontoolactivated = null;
887
+ (window as any).ontoolcancel = null;
888
+ }
889
+
890
+ expect(getWebMcpEventTarget("toolactivated")).toBe(window);
891
+ expect(getWebMcpEventTarget("toolcancel")).toBe(window);
892
+ });
893
+
894
+ it("resolves to navigator.modelContext if document.modelContext is absent (Chrome < 152)", async () => {
895
+ const { getWebMcpEventTarget } = await import("./webmcp.js");
896
+
897
+ delete ((globalThis as any).document as any).modelContext;
898
+
899
+ const navTarget = new EventTarget();
900
+ (navTarget as any).ontoolactivated = null;
901
+
902
+ Object.defineProperty((globalThis as any).navigator, "modelContext", {
903
+ configurable: true,
904
+ value: navTarget,
905
+ });
906
+
907
+ expect(getWebMcpEventTarget("toolactivated")).toBe(navTarget);
908
+
909
+ delete ((globalThis as any).navigator as any).modelContext;
910
+ });
911
+
912
+ it("prefers modelContext when neither target has on{type} property but modelContext is an EventTarget", async () => {
913
+ const { getWebMcpEventTarget } = await import("./webmcp.js");
914
+
915
+ const mockTarget = new EventTarget();
916
+ Object.defineProperty((globalThis as any).document, "modelContext", {
917
+ configurable: true,
918
+ value: mockTarget,
919
+ });
920
+
921
+ expect(getWebMcpEventTarget("toolactivated")).toBe(mockTarget);
922
+ expect(getWebMcpEventTarget()).toBe(mockTarget);
923
+ });
924
+
925
+ it("falls back to window if modelContext lacks addEventListener", async () => {
926
+ const { getWebMcpEventTarget } = await import("./webmcp.js");
927
+
928
+ Object.defineProperty((globalThis as any).document, "modelContext", {
929
+ configurable: true,
930
+ value: {
931
+ // Not an EventTarget
932
+ registerTool: jest.fn(),
933
+ },
934
+ });
935
+
936
+ expect(getWebMcpEventTarget("toolactivated")).toBe(window);
937
+ });
938
+ });
939
+
940
+ describe("addWebMcpEventListener and convenience helpers", () => {
941
+ it("listens to toolactivated on document.modelContext in Chrome 156+", async () => {
942
+ const { addWebMcpEventListener } = await import("./webmcp.js");
943
+
944
+ const mockTarget = new EventTarget();
945
+ (mockTarget as any).ontoolactivated = null;
946
+
947
+ Object.defineProperty((globalThis as any).document, "modelContext", {
948
+ configurable: true,
949
+ value: mockTarget,
950
+ });
951
+
952
+ const listener = jest.fn();
953
+ const unsubscribe = addWebMcpEventListener("toolactivated", listener);
954
+
955
+ const event = new CustomEvent("toolactivated");
956
+ (event as any).toolName = "read_file";
957
+ mockTarget.dispatchEvent(event);
958
+
959
+ expect(listener).toHaveBeenCalledTimes(1);
960
+ expect(listener).toHaveBeenCalledWith(
961
+ expect.objectContaining({ toolName: "read_file" }),
962
+ );
963
+
964
+ // Unsubscribe should remove the listener
965
+ unsubscribe();
966
+ mockTarget.dispatchEvent(event);
967
+ expect(listener).toHaveBeenCalledTimes(1);
968
+ });
969
+
970
+ it("normalizes toolName from detail.toolName if event.toolName is not directly set", async () => {
971
+ const { onWebMcpToolActivated } = await import("./webmcp.js");
972
+
973
+ const mockTarget = new EventTarget();
974
+ (mockTarget as any).ontoolactivated = null;
975
+
976
+ Object.defineProperty((globalThis as any).document, "modelContext", {
977
+ configurable: true,
978
+ value: mockTarget,
979
+ });
980
+
981
+ const listener = jest.fn();
982
+ const unsubscribe = onWebMcpToolActivated(listener);
983
+
984
+ const legacyEvent = new CustomEvent("toolactivated", {
985
+ detail: { toolName: "bash" },
986
+ });
987
+ mockTarget.dispatchEvent(legacyEvent);
988
+
989
+ expect(listener).toHaveBeenCalledTimes(1);
990
+ expect(listener).toHaveBeenCalledWith(
991
+ expect.objectContaining({ toolName: "bash" }),
992
+ );
993
+
994
+ unsubscribe();
995
+ });
996
+
997
+ it("listens to toolcancel on window in legacy Chrome < 156", async () => {
998
+ const { onWebMcpToolCancel } = await import("./webmcp.js");
999
+
1000
+ delete ((globalThis as any).document as any).modelContext;
1001
+ (window as any).ontoolcancel = null;
1002
+
1003
+ const listener = jest.fn();
1004
+ const unsubscribe = onWebMcpToolCancel(listener);
1005
+
1006
+ const event = new CustomEvent("toolcancel");
1007
+ (event as any).toolName = "fetch_url";
1008
+ window.dispatchEvent(event);
1009
+
1010
+ expect(listener).toHaveBeenCalledTimes(1);
1011
+ expect(listener).toHaveBeenCalledWith(
1012
+ expect.objectContaining({ toolName: "fetch_url" }),
1013
+ );
1014
+
1015
+ unsubscribe();
1016
+ window.dispatchEvent(event);
1017
+ expect(listener).toHaveBeenCalledTimes(1);
1018
+ });
1019
+
1020
+ it("automatically cleans up when AbortSignal is aborted", async () => {
1021
+ const { onWebMcpToolActivated } = await import("./webmcp.js");
1022
+
1023
+ const mockTarget = new EventTarget();
1024
+ (mockTarget as any).ontoolactivated = null;
1025
+
1026
+ Object.defineProperty((globalThis as any).document, "modelContext", {
1027
+ configurable: true,
1028
+ value: mockTarget,
1029
+ });
1030
+
1031
+ const controller = new AbortController();
1032
+ const listener = jest.fn();
1033
+ onWebMcpToolActivated(listener, { signal: controller.signal });
1034
+
1035
+ controller.abort();
1036
+
1037
+ const event = new CustomEvent("toolactivated");
1038
+ (event as any).toolName = "write_file";
1039
+ mockTarget.dispatchEvent(event);
1040
+
1041
+ expect(listener).not.toHaveBeenCalled();
1042
+ });
1043
+
1044
+ it("supports listenBoth: true and deduplicates events within same tick", async () => {
1045
+ const { addWebMcpEventListener } = await import("./webmcp.js");
1046
+
1047
+ const mockTarget = new EventTarget();
1048
+ (mockTarget as any).ontoolactivated = null;
1049
+
1050
+ Object.defineProperty((globalThis as any).document, "modelContext", {
1051
+ configurable: true,
1052
+ value: mockTarget,
1053
+ });
1054
+
1055
+ const listener = jest.fn();
1056
+ const unsubscribe = addWebMcpEventListener("toolactivated", listener, {
1057
+ listenBoth: true,
1058
+ });
1059
+
1060
+ const evt1 = new CustomEvent("toolactivated");
1061
+ (evt1 as any).toolName = "list_files";
1062
+ const evt2 = new CustomEvent("toolactivated");
1063
+ (evt2 as any).toolName = "list_files";
1064
+
1065
+ // Dispatch on both targets
1066
+ mockTarget.dispatchEvent(evt1);
1067
+ window.dispatchEvent(evt2);
1068
+
1069
+ expect(listener).toHaveBeenCalledTimes(1);
1070
+
1071
+ unsubscribe();
1072
+ });
1073
+ });
1074
+ });
820
1075
  });