shadow-claw 1.54.0 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) 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 -1
  4. package/dist/cli/build/build.js +1 -1
  5. package/dist/cli/{chunk-DO2t9bUI.js → chunk--IuVyWfT.js} +24 -23
  6. package/dist/cli/{chunk-2ZYd9gYC.js → chunk--JpdI-Ht.js} +1 -1
  7. package/dist/cli/{chunk-m4Ineemy.js → chunk-4O8yrQuN.js} +1 -1
  8. package/dist/cli/{chunk-BiH9IXnQ.js → chunk-7S9Ywlfy.js} +1 -1
  9. package/dist/cli/{chunk-BmMOdYvk.js → chunk-B0SqqDSK.js} +1 -1
  10. package/dist/cli/chunk-BNaWaEm-.js +1 -0
  11. package/dist/cli/chunk-BXCsxO9e.js +1 -0
  12. package/dist/cli/chunk-Bq37qDoZ.js +18 -0
  13. package/dist/cli/{chunk-C1htf-nO.js → chunk-BrEeCKIo.js} +2 -2
  14. package/dist/cli/{chunk-BI1SOTM1.js → chunk-Bu3ylrhT.js} +8 -8
  15. package/dist/cli/{chunk-3smiGQoQ.js → chunk-C1v_s0Kb.js} +1 -1
  16. package/dist/cli/{chunk-DGeysxz3.js → chunk-C44G533y.js} +1 -1
  17. package/dist/cli/{chunk-9KiBalKu.js → chunk-C7hJLmsL.js} +1 -1
  18. package/dist/cli/{chunk-BFv9XSNd.js → chunk-CAaQWd6t.js} +1 -1
  19. package/dist/cli/chunk-CIkgn5Fh.js +1 -0
  20. package/dist/cli/chunk-CWEIN2kc.js +1 -0
  21. package/dist/cli/{chunk-BDeDZ-0L.js → chunk-CYg1lO1a.js} +1 -1
  22. package/dist/cli/{chunk-DXLmEQ3i.js → chunk-Ce09lwXT.js} +1 -1
  23. package/dist/cli/{chunk-XsfHeN4m.js → chunk-CpdqRSO-.js} +1 -1
  24. package/dist/cli/chunk-DB4x5L-B.js +1 -0
  25. package/dist/cli/{chunk-DaNyMqrk.js → chunk-DBvObsBQ.js} +1 -1
  26. package/dist/cli/chunk-DGPpt7Ka.js +1 -0
  27. package/dist/cli/chunk-DGagHluH.js +1 -0
  28. package/dist/cli/chunk-DUIs23D9.js +1 -0
  29. package/dist/cli/{chunk-CyvfQV16.js → chunk-DaurYOqg.js} +1 -1
  30. package/dist/cli/{chunk-DSFrcSyA.js → chunk-DfwRL0Ue.js} +1 -1
  31. package/dist/cli/{chunk-M2tZrzY5.js → chunk-DudYQk0p.js} +1 -1
  32. package/dist/cli/chunk-DvKo50Tp.js +2 -0
  33. package/dist/cli/{chunk-H_Mw5ypp.js → chunk-Dvrqte5Q.js} +2 -2
  34. package/dist/cli/{chunk-BtNO-m9T.js → chunk-DzhSExU0.js} +1 -1
  35. package/dist/cli/{chunk-IOMO88kZ.js → chunk-ENKlt3i9.js} +1 -1
  36. package/dist/cli/{chunk-Bspgzwsm.js → chunk-FWRwAX75.js} +8 -8
  37. package/dist/cli/chunk-Lf-Z-h48.js +32 -0
  38. package/dist/cli/{chunk-BucXnZ4Z.js → chunk-VRfkSLrD.js} +1 -1
  39. package/dist/cli/chunk-uaTxD024.js +1 -0
  40. package/dist/cli/cli.js +1 -1
  41. package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
  42. package/dist/headless-agent.js +2 -2
  43. package/dist/lib/components/index.js +25 -25
  44. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  45. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  46. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  47. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  48. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  49. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  50. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  51. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  52. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  53. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  54. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  55. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  56. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  57. package/dist/lib/components/shadow-claw/utils/get-route.d.ts +3 -0
  58. package/dist/lib/components/shadow-claw/utils/sync-slotted-sidebar-active-links.d.ts +1 -1
  59. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  60. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  61. package/dist/lib/components/shadow-claw-channels.js +1 -1
  62. package/dist/lib/components/shadow-claw-chat.js +1 -1
  63. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  64. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  65. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  66. package/dist/lib/components/shadow-claw-files.js +1 -1
  67. package/dist/lib/components/shadow-claw-git.js +1 -1
  68. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  69. package/dist/lib/components/shadow-claw-llm.js +1 -1
  70. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  71. package/dist/lib/components/shadow-claw-networking.js +1 -1
  72. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  73. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  74. package/dist/lib/components/shadow-claw-pages.js +1 -1
  75. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  76. package/dist/lib/components/shadow-claw-settings.js +1 -1
  77. package/dist/lib/components/shadow-claw-storage.js +1 -1
  78. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  79. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  80. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  81. package/dist/lib/components/shadow-claw-tools.js +1 -1
  82. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  83. package/dist/lib/components/shadow-claw.js +1 -1
  84. package/dist/lib/{configure-peer-js-D5jDCtLx.js → configure-peer-js-C9554Slv.js} +2 -2
  85. package/dist/lib/{configure-peer-js-D5jDCtLx.js.map → configure-peer-js-C9554Slv.js.map} +1 -1
  86. package/dist/lib/{file-viewer-B4CL4e0i.js → file-viewer-DBc5dxNy.js} +2 -2
  87. package/dist/lib/{file-viewer-B4CL4e0i.js.map → file-viewer-DBc5dxNy.js.map} +1 -1
  88. package/dist/lib/index.js +28 -27
  89. package/dist/lib/{init-control-plane-l2uFnhGV.js → init-control-plane-D-41Fm5q.js} +12 -12
  90. package/dist/lib/{init-control-plane-l2uFnhGV.js.map → init-control-plane-D-41Fm5q.js.map} +1 -1
  91. package/dist/lib/{markdown-0_p_zfxL.js → markdown-KnDWzWJJ.js} +2 -2
  92. package/dist/lib/{markdown-0_p_zfxL.js.map → markdown-KnDWzWJJ.js.map} +1 -1
  93. package/dist/lib/{orchestrator-D6wm8Sec.js → orchestrator-BEZcqgR5.js} +9 -9
  94. package/dist/lib/{orchestrator-D6wm8Sec.js.map → orchestrator-BEZcqgR5.js.map} +1 -1
  95. package/dist/lib/{orchestrator-Dh-hnIpK.js → orchestrator-Cda13e5U.js} +4 -4
  96. package/dist/lib/{orchestrator-Dh-hnIpK.js.map → orchestrator-Cda13e5U.js.map} +1 -1
  97. package/dist/lib/{peerjs-BWZnsD0B.js → peerjs-DYkUTcee.js} +5 -5
  98. package/dist/lib/{peerjs-BWZnsD0B.js.map → peerjs-DYkUTcee.js.map} +1 -1
  99. package/dist/lib/{push-client-MybDfOdi.js → push-client-CQtCFDaD.js} +3 -3
  100. package/dist/lib/{push-client-MybDfOdi.js.map → push-client-CQtCFDaD.js.map} +1 -1
  101. package/dist/lib/security/custom-element-security.d.ts +8 -4
  102. package/dist/lib/{shadow-claw-PdOpgTxH.js → shadow-claw-DYe9u6Tp.js} +142 -33
  103. package/dist/lib/shadow-claw-DYe9u6Tp.js.map +1 -0
  104. package/dist/lib/{shadow-claw-accounts-3bR76EOb.js → shadow-claw-accounts-D2URoHys.js} +2 -2
  105. package/dist/lib/{shadow-claw-accounts-3bR76EOb.js.map → shadow-claw-accounts-D2URoHys.js.map} +1 -1
  106. package/dist/lib/{shadow-claw-channel-config-ChdZ1iB8.js → shadow-claw-channel-config-buXoz2xv.js} +3 -3
  107. package/dist/lib/{shadow-claw-channel-config-ChdZ1iB8.js.map → shadow-claw-channel-config-buXoz2xv.js.map} +1 -1
  108. package/dist/lib/{shadow-claw-channels-CK9sc7h_.js → shadow-claw-channels-5yVJ1gLk.js} +3 -3
  109. package/dist/lib/{shadow-claw-channels-CK9sc7h_.js.map → shadow-claw-channels-5yVJ1gLk.js.map} +1 -1
  110. package/dist/lib/{shadow-claw-chat-B6uuZnJ7.js → shadow-claw-chat-CVc-7ycM.js} +7 -7
  111. package/dist/lib/{shadow-claw-chat-B6uuZnJ7.js.map → shadow-claw-chat-CVc-7ycM.js.map} +1 -1
  112. package/dist/lib/{shadow-claw-control-plane-BpRcl312.js → shadow-claw-control-plane-B5vsF91B.js} +6 -6
  113. package/dist/lib/{shadow-claw-control-plane-BpRcl312.js.map → shadow-claw-control-plane-B5vsF91B.js.map} +1 -1
  114. package/dist/lib/{shadow-claw-conversations-Dc_Xk2mB.js → shadow-claw-conversations-PGK5zqn3.js} +54 -12
  115. package/dist/lib/{shadow-claw-conversations-Dc_Xk2mB.js.map → shadow-claw-conversations-PGK5zqn3.js.map} +1 -1
  116. package/dist/lib/{shadow-claw-file-viewer-C4x1PtB2.js → shadow-claw-file-viewer-DSqn0hgx.js} +6 -6
  117. package/dist/lib/{shadow-claw-file-viewer-C4x1PtB2.js.map → shadow-claw-file-viewer-DSqn0hgx.js.map} +1 -1
  118. package/dist/lib/{shadow-claw-files-DhnHbXKf.js → shadow-claw-files-vOa9ZWbr.js} +6 -6
  119. package/dist/lib/{shadow-claw-files-DhnHbXKf.js.map → shadow-claw-files-vOa9ZWbr.js.map} +1 -1
  120. package/dist/lib/{shadow-claw-git-BeQ1ElUd.js → shadow-claw-git-D54Z85Bj.js} +4 -4
  121. package/dist/lib/{shadow-claw-git-BeQ1ElUd.js.map → shadow-claw-git-D54Z85Bj.js.map} +1 -1
  122. package/dist/lib/{shadow-claw-integrations-Bbdolk3w.js → shadow-claw-integrations-BTubMH9d.js} +2 -2
  123. package/dist/lib/{shadow-claw-integrations-Bbdolk3w.js.map → shadow-claw-integrations-BTubMH9d.js.map} +1 -1
  124. package/dist/lib/{shadow-claw-llm-DPda4L3C.js → shadow-claw-llm-8koGTNYs.js} +3 -3
  125. package/dist/lib/{shadow-claw-llm-DPda4L3C.js.map → shadow-claw-llm-8koGTNYs.js.map} +1 -1
  126. package/dist/lib/{shadow-claw-mcp-remote-Dn9c30WI.js → shadow-claw-mcp-remote-C2jwBsQt.js} +2 -2
  127. package/dist/lib/{shadow-claw-mcp-remote-Dn9c30WI.js.map → shadow-claw-mcp-remote-C2jwBsQt.js.map} +1 -1
  128. package/dist/lib/{shadow-claw-networking-zqNuyn3-.js → shadow-claw-networking-BV-rna16.js} +2 -2
  129. package/dist/lib/{shadow-claw-networking-zqNuyn3-.js.map → shadow-claw-networking-BV-rna16.js.map} +1 -1
  130. package/dist/lib/{shadow-claw-notifications-DFgdo33v.js → shadow-claw-notifications-Dku7VBLk.js} +2 -2
  131. package/dist/lib/{shadow-claw-notifications-DFgdo33v.js.map → shadow-claw-notifications-Dku7VBLk.js.map} +1 -1
  132. package/dist/lib/{shadow-claw-page-header-B7LFGQRf.js → shadow-claw-page-header-VPjYWfsT.js} +2 -2
  133. package/dist/lib/{shadow-claw-page-header-B7LFGQRf.js.map → shadow-claw-page-header-VPjYWfsT.js.map} +1 -1
  134. package/dist/lib/{shadow-claw-pages-DLspFqdK.js → shadow-claw-pages-DXrM6hgZ.js} +29 -19
  135. package/dist/lib/shadow-claw-pages-DXrM6hgZ.js.map +1 -0
  136. package/dist/lib/{shadow-claw-peerjs-D8xWwuWR.js → shadow-claw-peerjs-D3xonez5.js} +3 -3
  137. package/dist/lib/{shadow-claw-peerjs-D8xWwuWR.js.map → shadow-claw-peerjs-D3xonez5.js.map} +1 -1
  138. package/dist/lib/{shadow-claw-settings-NbvzXASD.js → shadow-claw-settings-DMTx6OS9.js} +5 -5
  139. package/dist/lib/{shadow-claw-settings-NbvzXASD.js.map → shadow-claw-settings-DMTx6OS9.js.map} +1 -1
  140. package/dist/lib/{shadow-claw-storage-B_NAoZmd.js → shadow-claw-storage-BJLnukxa.js} +3 -3
  141. package/dist/lib/{shadow-claw-storage-B_NAoZmd.js.map → shadow-claw-storage-BJLnukxa.js.map} +1 -1
  142. package/dist/lib/{shadow-claw-task-server-BaQJXhYJ.js → shadow-claw-task-server-DcMj20hD.js} +2 -2
  143. package/dist/lib/{shadow-claw-task-server-BaQJXhYJ.js.map → shadow-claw-task-server-DcMj20hD.js.map} +1 -1
  144. package/dist/lib/{shadow-claw-tasks-CAZua0-j.js → shadow-claw-tasks-BQBe4lVi.js} +7 -7
  145. package/dist/lib/{shadow-claw-tasks-CAZua0-j.js.map → shadow-claw-tasks-BQBe4lVi.js.map} +1 -1
  146. package/dist/lib/{shadow-claw-terminal-B2YJhkJL.js → shadow-claw-terminal-DPEzTxXv.js} +2 -2
  147. package/dist/lib/{shadow-claw-terminal-B2YJhkJL.js.map → shadow-claw-terminal-DPEzTxXv.js.map} +1 -1
  148. package/dist/lib/{shadow-claw-tools-CkZWxxTm.js → shadow-claw-tools-BCnU8FaK.js} +6 -6
  149. package/dist/lib/{shadow-claw-tools-CkZWxxTm.js.map → shadow-claw-tools-BCnU8FaK.js.map} +1 -1
  150. package/dist/lib/{shadow-claw-webvm-BL9cgrP9.js → shadow-claw-webvm-CnW-hoJ8.js} +2 -2
  151. package/dist/lib/{shadow-claw-webvm-BL9cgrP9.js.map → shadow-claw-webvm-CnW-hoJ8.js.map} +1 -1
  152. package/dist/lib/subsystems/mcp/webmcp.d.ts +15 -2
  153. package/dist/lib/{sync-web-mcp-registration-dW2MVCj4.js → sync-web-mcp-registration-DwpGjVdo.js} +10 -4
  154. package/dist/lib/sync-web-mcp-registration-DwpGjVdo.js.map +1 -0
  155. package/dist/lib/{tools-D7whby3H.js → tools-CP6G0HF7.js} +5 -6
  156. package/dist/lib/tools-CP6G0HF7.js.map +1 -0
  157. package/dist/lib/{trusted-types-d0cdkut_.js → trusted-types-0cu5Dm6M.js} +29 -4
  158. package/dist/lib/{trusted-types-d0cdkut_.js.map → trusted-types-0cu5Dm6M.js.map} +1 -1
  159. package/dist/lib/utils/index.d.ts +1 -0
  160. package/dist/lib/utils/index.js +4 -2
  161. package/dist/lib/utils/index.js.map +1 -1
  162. package/dist/lib/utils/resolve-page-script-dependencies.d.ts +19 -0
  163. package/dist/lib/utils/resolve-page-script-dependencies.js +98 -0
  164. package/dist/lib/utils/resolve-page-script-dependencies.js.map +1 -0
  165. package/dist/lib/utils/utils.js +1 -1
  166. package/dist/lib/webmcp-Bf9rT7uu.js +2001 -0
  167. package/dist/lib/webmcp-Bf9rT7uu.js.map +1 -0
  168. package/dist/public/.well-known/mcp/server-card.json +1 -1
  169. package/dist/public/.well-known/mcp.json +1 -1
  170. package/dist/public/AGENTS.md +3 -3
  171. package/dist/public/README.md +1 -1
  172. package/dist/public/agent.worker.js +2 -2
  173. package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
  174. package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
  175. package/dist/public/components/shadow-claw-conversations/shadow-claw-conversations.css +1 -1
  176. package/dist/public/components/shadow-claw-conversations/shadow-claw-conversations.html +1 -1
  177. package/dist/public/{configure-peer-js-WLRM7Qa7.js → configure-peer-js-OYl3V8sW.js} +1 -1
  178. package/dist/public/{custom-element-security-Cl991z-x.js → custom-element-security-BR4V40j4.js} +1 -1
  179. package/dist/public/docs/example/article/index.html +3 -3
  180. package/dist/public/docs/example/article.html +3 -3
  181. package/dist/public/docs/publishing/index.html +3 -3
  182. package/dist/public/docs/publishing.html +3 -3
  183. package/dist/public/docs/skill-creator/index.html +3 -3
  184. package/dist/public/docs/skill-creator.html +3 -3
  185. package/dist/public/docs/subsystems/custom-element-security.md +19 -5
  186. package/dist/public/docs/subsystems/pages.md +10 -2
  187. package/dist/public/docs/subsystems/tools.md +1 -2
  188. package/dist/public/docs/subsystems/webmcp.md +41 -6
  189. package/dist/public/{download-group-file-fFjoIIp4.js → download-group-file-BNmK8wac.js} +1 -1
  190. package/dist/public/e2e/README.md +2 -2
  191. package/dist/public/{e2e-bridge-CiSfBRT4.js → e2e-bridge-4phD1JUQ.js} +1 -1
  192. package/dist/public/{file-viewer-DFC3kfeO.js → file-viewer-Dbh3K9nL.js} +1 -1
  193. package/dist/public/index.html +3 -3
  194. package/dist/public/index.js +1 -1
  195. package/dist/public/{init-control-plane-BEbcwmmv.js → init-control-plane-DGOY_RNS.js} +1 -1
  196. package/dist/public/llms.txt +1 -1
  197. package/dist/public/main/index.html +3 -3
  198. package/dist/public/main/memory/index.html +3 -3
  199. package/dist/public/main/memory.html +3 -3
  200. package/dist/public/main.html +3 -3
  201. package/dist/public/{markdown-BeglYzw9.js → markdown-BPZYpfGm.js} +1 -1
  202. package/dist/public/{orchestrator-BlvEG3PO.js → orchestrator-OoUK7CF9.js} +1 -1
  203. package/dist/public/{orchestrator-DTNrntd4.js → orchestrator-a9047bGu.js} +1 -1
  204. package/dist/public/{peerjs-CnBdK94R.js → peerjs-Cbn310Tz.js} +1 -1
  205. package/dist/public/{push-client-DOHbdCyj.js → push-client-Dy4LGJy9.js} +1 -1
  206. package/dist/public/service-worker.js +1 -1
  207. package/dist/public/shadow-claw-B-HWC1uN.js +1180 -0
  208. package/dist/public/{shadow-claw-accounts-C6FXVw2K.js → shadow-claw-accounts-Di-anCZ1.js} +1 -1
  209. package/dist/public/{shadow-claw-channels-tr_r60Kq.js → shadow-claw-channels-RVwCE8pE.js} +1 -1
  210. package/dist/public/{shadow-claw-chat-BAAPhAOs.js → shadow-claw-chat-DI1q8UnX.js} +1 -1
  211. package/dist/public/{shadow-claw-control-plane-C_5VIx8E.js → shadow-claw-control-plane-KXEdLOGl.js} +2 -2
  212. package/dist/public/{shadow-claw-conversations-DGYZdDa3.js → shadow-claw-conversations-BCBGtS30.js} +40 -6
  213. package/dist/public/{shadow-claw-file-viewer-5j2-Z3fN.js → shadow-claw-file-viewer-CW3t6j-w.js} +1 -1
  214. package/dist/public/{shadow-claw-files-DKF4Yy0m.js → shadow-claw-files-JQ-o0ImX.js} +1 -1
  215. package/dist/public/{shadow-claw-git-CtUTJXKT.js → shadow-claw-git-D2M8VixW.js} +2 -2
  216. package/dist/public/{shadow-claw-integrations-CuBy9D3z.js → shadow-claw-integrations-Cgw6v9_1.js} +1 -1
  217. package/dist/public/{shadow-claw-llm-BIfNNjGL.js → shadow-claw-llm-74ieXp7i.js} +1 -1
  218. package/dist/public/{shadow-claw-mcp-remote-C17FXvrb.js → shadow-claw-mcp-remote-BX0BkSmy.js} +1 -1
  219. package/dist/public/{shadow-claw-networking-CjxQ1CLI.js → shadow-claw-networking-DNX8vaIQ.js} +1 -1
  220. package/dist/public/{shadow-claw-notifications-Brn2O3Ps.js → shadow-claw-notifications-BLTaGAny.js} +1 -1
  221. package/dist/public/{shadow-claw-page-header-Bn1P7Cxb.js → shadow-claw-page-header-CjFOdo9v.js} +1 -1
  222. package/dist/public/shadow-claw-pages-DOQqMm0_.js +645 -0
  223. package/dist/public/{shadow-claw-settings-CfDm0hVA.js → shadow-claw-settings-DFtl0Fy8.js} +2 -2
  224. package/dist/public/{shadow-claw-storage-DOOesRzw.js → shadow-claw-storage-gQGExZRq.js} +1 -1
  225. package/dist/public/{shadow-claw-task-server-H36iDDp2.js → shadow-claw-task-server-Ca45uY7S.js} +1 -1
  226. package/dist/public/{shadow-claw-tasks-4JsjSGkN.js → shadow-claw-tasks-CCTaaSdi.js} +1 -1
  227. package/dist/public/{shadow-claw-terminal-CTRTcYz-.js → shadow-claw-terminal-BfgulLkl.js} +1 -1
  228. package/dist/public/{shadow-claw-tools-Dm1LxNf4.js → shadow-claw-tools-V8NA4n18.js} +1 -1
  229. package/dist/public/{shadow-claw-webvm-JzuPe9Nr.js → shadow-claw-webvm-CxxI2bnC.js} +1 -1
  230. package/dist/public/sync-web-mcp-registration-CkCA8UFm.js +1 -0
  231. package/dist/public/{tools-FiTtOlIt.js → tools-CwQq5tJH.js} +2 -2
  232. package/dist/public/webmcp-CmtWOAFm.js +6 -0
  233. package/dist/server.js +2 -2
  234. package/package.json +3 -3
  235. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +16 -0
  236. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +62 -1
  237. package/src/cli/prerender/pretty-paths/prerender-pretty-paths.test.ts +277 -0
  238. package/src/cli/prerender/pretty-paths/prerender-pretty-paths.ts +28 -1
  239. package/src/cli/site-config/custom-elements/build-custom-element-script-tags.ts +13 -1
  240. package/src/components/shadow-claw/shadow-claw.css +12 -0
  241. package/src/components/shadow-claw/shadow-claw.html +12 -2
  242. package/src/components/shadow-claw/utils/bind-event-listeners.test.ts +18 -0
  243. package/src/components/shadow-claw/utils/bind-event-listeners.ts +14 -0
  244. package/src/components/shadow-claw/utils/get-route.test.ts +35 -0
  245. package/src/components/shadow-claw/utils/get-route.ts +18 -0
  246. package/src/components/shadow-claw/utils/sync-slotted-sidebar-active-links.test.ts +152 -1
  247. package/src/components/shadow-claw/utils/sync-slotted-sidebar-active-links.ts +139 -8
  248. package/src/components/shadow-claw-conversations/shadow-claw-conversations.css +11 -0
  249. package/src/components/shadow-claw-conversations/shadow-claw-conversations.html +27 -4
  250. package/src/components/shadow-claw-conversations/shadow-claw-conversations.ts +32 -4
  251. package/src/components/shadow-claw-pages/shadow-claw-pages-scripts.test.ts +266 -0
  252. package/src/components/shadow-claw-pages/shadow-claw-pages.ts +41 -17
  253. package/src/core/orchestrator/utils/sync-web-mcp-registration.test.ts +18 -1
  254. package/src/core/orchestrator/utils/sync-web-mcp-registration.ts +8 -0
  255. package/src/core/utils/init-control-plane.test.ts +9 -4
  256. package/src/security/custom-element-security.test.ts +65 -0
  257. package/src/security/custom-element-security.ts +67 -4
  258. package/src/stores/tools.test.ts +5 -3
  259. package/src/stores/tools.ts +5 -10
  260. package/src/subsystems/mcp/webmcp.test.ts +84 -13
  261. package/src/subsystems/mcp/webmcp.ts +73 -10
  262. package/src/utils/index.ts +1 -0
  263. package/src/utils/resolve-page-script-dependencies.test.ts +245 -0
  264. package/src/utils/resolve-page-script-dependencies.ts +198 -0
  265. package/dist/cli/chunk-B1hufJeO.js +0 -1
  266. package/dist/cli/chunk-BZpn8npn.js +0 -1
  267. package/dist/cli/chunk-Bw5aOTFr.js +0 -1
  268. package/dist/cli/chunk-C8nH01Gw.js +0 -32
  269. package/dist/cli/chunk-CK1ZoyoE.js +0 -17
  270. package/dist/cli/chunk-CVkhDr-7.js +0 -1
  271. package/dist/cli/chunk-Cum07Vlh.js +0 -1
  272. package/dist/cli/chunk-D3UhDDmV.js +0 -2
  273. package/dist/cli/chunk-D8GI6qj6.js +0 -1
  274. package/dist/cli/chunk-DEH-FvmP.js +0 -1
  275. package/dist/cli/chunk-DksvJ_kf.js +0 -1
  276. package/dist/cli/chunk-RGb9wM4R.js +0 -1
  277. package/dist/lib/shadow-claw-PdOpgTxH.js.map +0 -1
  278. package/dist/lib/shadow-claw-pages-DLspFqdK.js.map +0 -1
  279. package/dist/lib/sync-web-mcp-registration-dW2MVCj4.js.map +0 -1
  280. package/dist/lib/tools-D7whby3H.js.map +0 -1
  281. package/dist/lib/webmcp-CYTNZfkn.js +0 -1687
  282. package/dist/lib/webmcp-CYTNZfkn.js.map +0 -1
  283. package/dist/public/shadow-claw-CnNX-WCh.js +0 -1158
  284. package/dist/public/shadow-claw-pages-BwOOqRWW.js +0 -645
  285. package/dist/public/sync-web-mcp-registration-BnHVUjat.js +0 -1
  286. package/dist/public/webmcp-_TBqXlVW.js +0 -1
@@ -0,0 +1,266 @@
1
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2
+
3
+ const mockLoadApprovedCustomElementScript = jest.fn(async () =>
4
+ document.createElement("script"),
5
+ );
6
+ const mockGetApprovedCustomElementScriptDescriptors = jest.fn(() => [
7
+ {
8
+ id: "feed-scroller",
9
+ src: "/pages/main/assets/feed-scroller.js",
10
+ global: true,
11
+ hasInit: true,
12
+ },
13
+ {
14
+ id: "x-hook-component",
15
+ src: "/pages/main/assets/x-hook-component.js",
16
+ hasInit: true,
17
+ },
18
+ {
19
+ name: "x-postpress-code",
20
+ src: "/pages/main/assets/x-postpress-code.js",
21
+ hasInit: true,
22
+ },
23
+ ]);
24
+
25
+ jest.unstable_mockModule("../../security/custom-element-security.js", () => ({
26
+ getApprovedCustomElementScriptDescriptors:
27
+ mockGetApprovedCustomElementScriptDescriptors,
28
+ loadApprovedCustomElementScript: mockLoadApprovedCustomElementScript,
29
+ isApprovedScript: jest.fn((_src: string) => true),
30
+ isAllowedCustomElement: jest.fn(() => true),
31
+ getIframeCsp: jest.fn(() => ""),
32
+ getIframeSandboxPolicy: jest.fn(() => "allow-scripts"),
33
+ hasApprovedCustomElement: jest.fn(() => false),
34
+ }));
35
+
36
+ jest.unstable_mockModule("../../core/effect.js", () => ({
37
+ effect: jest.fn(() => () => undefined),
38
+ }));
39
+
40
+ jest.unstable_mockModule("../../content/markdown.js", () => ({
41
+ renderMarkdown: jest.fn(
42
+ async (value: string) =>
43
+ `<article>${value.replace(/<script[\s\S]*?<\/script>/gi, "")}</article>`,
44
+ ),
45
+ }));
46
+
47
+ jest.unstable_mockModule("../../security/trusted-types.js", () => ({
48
+ sanitizeToTrustedHtml: jest.fn((html: string) => html),
49
+ sanitizeSrcdocHtml: jest.fn((html: string) => html),
50
+ setSanitizedHtml: jest.fn((element: Element, html: string) => {
51
+ element.innerHTML = html;
52
+ return html;
53
+ }),
54
+ setTrustedSrcdoc: jest.fn((iframe: HTMLIFrameElement, html: string) => {
55
+ iframe.srcdoc = html;
56
+ return html;
57
+ }),
58
+ toTrustedScriptUrl: jest.fn((url: string) => url),
59
+ toTrustedHtmlPresanitized: jest.fn((html: string) => html),
60
+ }));
61
+
62
+ let mockActivePinnedPage: any = null;
63
+ jest.unstable_mockModule("../../stores/orchestrator.js", () => ({
64
+ orchestratorStore: {
65
+ whenInitialized: Promise.resolve(),
66
+ whenReady: Promise.resolve(),
67
+ pages: [],
68
+ groups: [],
69
+ activeGroupId: "group-1",
70
+ isPageAllowed: jest.fn(() => true),
71
+ get activePinnedPage() {
72
+ return mockActivePinnedPage;
73
+ },
74
+ setActivePinnedPage: jest.fn(async (_db: any, val: any) => {
75
+ mockActivePinnedPage = val;
76
+ }),
77
+ _activePinnedPage: {
78
+ set: jest.fn((val: any) => {
79
+ mockActivePinnedPage = val;
80
+ }),
81
+ },
82
+ },
83
+ }));
84
+
85
+ jest.unstable_mockModule("../../storage/read-group-file.js", () => ({
86
+ readGroupFile: jest.fn(),
87
+ }));
88
+
89
+ jest.unstable_mockModule("../../storage/static-main-site.js", () => ({
90
+ getStaticMainManifest: jest.fn(async () => ({ pages: [] })),
91
+ getStaticPageContent: jest.fn(async () => null),
92
+ seedStaticMainSite: jest.fn(),
93
+ getSiteConfigSeoSettings: jest.fn(() => ({ siteTitle: "ShadowClaw" })),
94
+ }));
95
+
96
+ jest.unstable_mockModule("../../db/db.js", () => ({
97
+ getDb: jest.fn(async () => ({})),
98
+ }));
99
+
100
+ jest.unstable_mockModule("../../db/get-config.js", () => ({
101
+ getConfig: jest.fn(async () => "0"),
102
+ }));
103
+
104
+ const { ShadowClawPages } = await import("./shadow-claw-pages.js");
105
+ const { readGroupFile } = await import("../../storage/read-group-file.js");
106
+ const { setSanitizedHtml } = await import("../../security/trusted-types.js");
107
+
108
+ describe("ShadowClawPages - Script Dependencies Loading", () => {
109
+ beforeEach(() => {
110
+ jest.clearAllMocks();
111
+ });
112
+
113
+ it("loads global scripts and frontmatter-declared script dependencies", async () => {
114
+ const component = new ShadowClawPages();
115
+ component.db = {} as any;
116
+ component.selectedPage = { groupId: "group-1", path: "posts/dynamic.md" };
117
+
118
+ const postContent = [
119
+ "---",
120
+ 'title: "The Web Is Dynamic"',
121
+ "dependencies:",
122
+ ' - "x-hook-component"',
123
+ "---",
124
+ "# The Web Is Dynamic",
125
+ "<x-hook-component></x-hook-component>",
126
+ ].join("\n");
127
+
128
+ (
129
+ readGroupFile as jest.MockedFunction<typeof readGroupFile>
130
+ ).mockResolvedValueOnce(postContent);
131
+
132
+ await component.renderSelectedPage();
133
+
134
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
135
+ "/pages/main/assets/feed-scroller.js",
136
+ );
137
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
138
+ "/pages/main/assets/x-hook-component.js",
139
+ );
140
+ expect(mockLoadApprovedCustomElementScript).not.toHaveBeenCalledWith(
141
+ "/pages/main/assets/x-postpress-code.js",
142
+ );
143
+ expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
144
+ });
145
+
146
+ it("fails gracefully when an unresolvable dependency is in frontmatter without breaking rendering", async () => {
147
+ const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
148
+ const component = new ShadowClawPages();
149
+ component.db = {} as any;
150
+ component.selectedPage = { groupId: "group-1", path: "posts/unknown.md" };
151
+
152
+ const postContent = [
153
+ "---",
154
+ 'title: "Unknown Script Page"',
155
+ "dependencies:",
156
+ ' - "unknown-script-id"',
157
+ "---",
158
+ "# Unknown Script Page",
159
+ "<p>Some content</p>",
160
+ ].join("\n");
161
+
162
+ (
163
+ readGroupFile as jest.MockedFunction<typeof readGroupFile>
164
+ ).mockResolvedValueOnce(postContent);
165
+
166
+ await component.renderSelectedPage();
167
+
168
+ // Global script should still load
169
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
170
+ "/pages/main/assets/feed-scroller.js",
171
+ );
172
+ // Page rendering should still succeed
173
+ expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
174
+
175
+ warnSpy.mockRestore();
176
+ });
177
+
178
+ it("fails gracefully when loadApprovedCustomElementScript throws without breaking rendering", async () => {
179
+ const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
180
+ mockLoadApprovedCustomElementScript.mockRejectedValueOnce(
181
+ new Error("Network connection failed"),
182
+ );
183
+
184
+ const component = new ShadowClawPages();
185
+ component.db = {} as any;
186
+ component.selectedPage = { groupId: "group-1", path: "posts/faulty.md" };
187
+
188
+ const postContent = [
189
+ "---",
190
+ 'title: "Faulty Script Page"',
191
+ "dependencies:",
192
+ ' - "x-hook-component"',
193
+ "---",
194
+ "# Faulty Script Page",
195
+ ].join("\n");
196
+
197
+ (
198
+ readGroupFile as jest.MockedFunction<typeof readGroupFile>
199
+ ).mockResolvedValueOnce(postContent);
200
+
201
+ // Should not throw
202
+ await expect(component.renderSelectedPage()).resolves.toBeUndefined();
203
+ expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
204
+
205
+ warnSpy.mockRestore();
206
+ });
207
+
208
+ it("extracts and loads inline script tags from raw markdown content before DOMPurify strips them", async () => {
209
+ const component = new ShadowClawPages();
210
+ component.db = {} as any;
211
+ component.selectedPage = { groupId: "group-1", path: "posts/inline.md" };
212
+
213
+ const postContent = [
214
+ "---",
215
+ 'title: "Inline Script Page"',
216
+ "---",
217
+ "# Inline Script Page",
218
+ '<script type="module" src="/pages/main/assets/x-postpress-code.js"></script>',
219
+ ].join("\n");
220
+
221
+ (
222
+ readGroupFile as jest.MockedFunction<typeof readGroupFile>
223
+ ).mockResolvedValueOnce(postContent);
224
+
225
+ await component.renderSelectedPage();
226
+
227
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
228
+ "/pages/main/assets/x-postpress-code.js",
229
+ );
230
+ expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
231
+ });
232
+
233
+ it("resolves and loads scripts by filename stem when existing config does not specify an id", async () => {
234
+ mockGetApprovedCustomElementScriptDescriptors.mockReturnValueOnce([
235
+ { src: "/pages/main/assets/feed-scroller.js", global: true },
236
+ { src: "/pages/main/assets/x-hook-component.js" },
237
+ ] as any);
238
+
239
+ const component = new ShadowClawPages();
240
+ component.db = {} as any;
241
+ component.selectedPage = { groupId: "group-1", path: "posts/legacy.md" };
242
+
243
+ const postContent = [
244
+ "---",
245
+ 'title: "Legacy Post"',
246
+ "dependencies:",
247
+ ' - "x-hook-component"',
248
+ "---",
249
+ "# Legacy Post",
250
+ ].join("\n");
251
+
252
+ (
253
+ readGroupFile as jest.MockedFunction<typeof readGroupFile>
254
+ ).mockResolvedValueOnce(postContent);
255
+
256
+ await component.renderSelectedPage();
257
+
258
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
259
+ "/pages/main/assets/feed-scroller.js",
260
+ );
261
+ expect(mockLoadApprovedCustomElementScript).toHaveBeenCalledWith(
262
+ "/pages/main/assets/x-hook-component.js",
263
+ );
264
+ expect(setSanitizedHtml).toHaveBeenCalledTimes(1);
265
+ });
266
+ });
@@ -3,6 +3,7 @@ import { Signal } from "signal-polyfill";
3
3
  import { effect } from "../../core/effect.js";
4
4
 
5
5
  import { splitFrontmatter } from "../../utils/frontmatter.js";
6
+ import { resolvePageScriptDependencies } from "../../utils/resolve-page-script-dependencies.js";
6
7
  import { formatSeoTitle } from "../../utils/seo.js";
7
8
  import { getSiteConfigSeoSettings } from "../../storage/static-main-site.js";
8
9
  import { CONFIG_KEYS } from "../../config/config.js";
@@ -1524,16 +1525,16 @@ export class ShadowClawPages extends ShadowClawElement {
1524
1525
  return;
1525
1526
  }
1526
1527
 
1527
- // Extract approved script srcs from the raw HTML *before* DOMPurify
1528
+ // Extract approved script srcs from the raw content *before* DOMPurify
1528
1529
  // strips <script> tags. DOMPurify removes scripts during sanitization,
1529
1530
  // so we must collect them here from the raw string, not from the DOM.
1530
- const approvedScriptSrcs: string[] = [];
1531
+ const rawScriptSrcs: string[] = [];
1531
1532
  const scriptSrcRegex = /<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi;
1532
1533
  let scriptMatch: RegExpExecArray | null;
1533
- while ((scriptMatch = scriptSrcRegex.exec(html)) !== null) {
1534
+ while ((scriptMatch = scriptSrcRegex.exec(content)) !== null) {
1534
1535
  const src = scriptMatch[1];
1535
1536
  if (src && isApprovedScript(src)) {
1536
- approvedScriptSrcs.push(src);
1537
+ rawScriptSrcs.push(src);
1537
1538
  }
1538
1539
  }
1539
1540
 
@@ -1543,22 +1544,45 @@ export class ShadowClawPages extends ShadowClawElement {
1543
1544
  // connectedCallback fires correctly and slotchange is captured on
1544
1545
  // first connection rather than after a late upgrade.
1545
1546
  const scriptDescriptors = getApprovedCustomElementScriptDescriptors();
1547
+ const combinedFrontmatter = {
1548
+ ...(parsedFrontmatter?.data || {}),
1549
+ scripts: [
1550
+ ...(Array.isArray(parsedFrontmatter?.data?.scripts)
1551
+ ? parsedFrontmatter.data.scripts
1552
+ : parsedFrontmatter?.data?.scripts
1553
+ ? [parsedFrontmatter.data.scripts]
1554
+ : []),
1555
+ ...rawScriptSrcs,
1556
+ ],
1557
+ };
1558
+ const resolvedScripts = resolvePageScriptDependencies(
1559
+ scriptDescriptors,
1560
+ combinedFrontmatter,
1561
+ content,
1562
+ );
1563
+
1546
1564
  await Promise.all(
1547
- approvedScriptSrcs.map(async (src) => {
1548
- await loadApprovedCustomElementScript(src);
1549
- const desc = scriptDescriptors.find((d) => d.src === src);
1550
- if (desc?.hasInit) {
1551
- try {
1552
- const mod = await import(/* @vite-ignore */ src);
1553
- if (typeof mod?.init === "function") {
1554
- await mod.init();
1565
+ resolvedScripts.map(async (desc) => {
1566
+ try {
1567
+ await loadApprovedCustomElementScript(desc.src);
1568
+ if (desc.hasInit) {
1569
+ try {
1570
+ const mod = await import(/* @vite-ignore */ desc.src);
1571
+ if (typeof mod?.init === "function") {
1572
+ await mod.init();
1573
+ }
1574
+ } catch (e) {
1575
+ console.error(
1576
+ "[ShadowClaw] Error executing init for " + desc.src,
1577
+ e,
1578
+ );
1555
1579
  }
1556
- } catch (e) {
1557
- console.error(
1558
- "[ShadowClaw] Error executing init for " + src,
1559
- e,
1560
- );
1561
1580
  }
1581
+ } catch (err) {
1582
+ console.warn(
1583
+ `[ShadowClaw] Error loading custom element script ${desc.src}:`,
1584
+ err,
1585
+ );
1562
1586
  }
1563
1587
  }),
1564
1588
  );
@@ -25,8 +25,10 @@ const mockApplyWebMcpMode = jest.fn<(mode: WebMcpMode) => void>();
25
25
 
26
26
  let lastActivatedListener: ((event: any) => void) | null = null;
27
27
  let lastCancelListener: ((event: any) => void) | null = null;
28
+ let lastChangeListener: ((event: any) => void) | null = null;
28
29
  const mockUnsubscribeActivated = jest.fn<() => void>();
29
30
  const mockUnsubscribeCancel = jest.fn<() => void>();
31
+ const mockUnsubscribeChange = jest.fn<() => void>();
30
32
  const mockOnWebMcpToolActivated = jest.fn((cb: any) => {
31
33
  lastActivatedListener = cb;
32
34
  return mockUnsubscribeActivated;
@@ -35,6 +37,10 @@ const mockOnWebMcpToolCancel = jest.fn((cb: any) => {
35
37
  lastCancelListener = cb;
36
38
  return mockUnsubscribeCancel;
37
39
  });
40
+ const mockOnWebMcpToolChange = jest.fn((cb: any) => {
41
+ lastChangeListener = cb;
42
+ return mockUnsubscribeChange;
43
+ });
38
44
 
39
45
  jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
40
46
  registerWebMcpTools: mockRegisterWebMcpTools,
@@ -42,6 +48,7 @@ jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
42
48
  setWebMcpMode: mockApplyWebMcpMode,
43
49
  onWebMcpToolActivated: mockOnWebMcpToolActivated,
44
50
  onWebMcpToolCancel: mockOnWebMcpToolCancel,
51
+ onWebMcpToolChange: mockOnWebMcpToolChange,
45
52
  }));
46
53
 
47
54
  const mockHandleWorkerMessage = jest
@@ -234,6 +241,7 @@ describe("syncWebMcpRegistration", () => {
234
241
 
235
242
  expect(mockOnWebMcpToolActivated).toHaveBeenCalled();
236
243
  expect(mockOnWebMcpToolCancel).toHaveBeenCalled();
244
+ expect(mockOnWebMcpToolChange).toHaveBeenCalled();
237
245
 
238
246
  // Trigger activated listener
239
247
  lastActivatedListener?.({ toolName: "read_file" });
@@ -248,9 +256,17 @@ describe("syncWebMcpRegistration", () => {
248
256
  "webmcp-tool-cancel",
249
257
  { toolName: "read_file" },
250
258
  );
259
+
260
+ // Trigger change listener
261
+ const dummyEvt = new Event("toolchange");
262
+ lastChangeListener?.(dummyEvt);
263
+ expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
264
+ "webmcp-tool-change",
265
+ { event: dummyEvt },
266
+ );
251
267
  });
252
268
 
253
- it("should unsubscribe from toolactivated and toolcancel when webMcpEffectCleanup is executed", async () => {
269
+ it("should unsubscribe from toolactivated, toolcancel, and toolchange when webMcpEffectCleanup is executed", async () => {
254
270
  syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
255
271
  await mockOrchestrator.webMcpRegistrationLock;
256
272
 
@@ -261,6 +277,7 @@ describe("syncWebMcpRegistration", () => {
261
277
 
262
278
  expect(mockUnsubscribeActivated).toHaveBeenCalled();
263
279
  expect(mockUnsubscribeCancel).toHaveBeenCalled();
280
+ expect(mockUnsubscribeChange).toHaveBeenCalled();
264
281
  });
265
282
 
266
283
  it("should setup effect and register tools filtered by group toolTags", async () => {
@@ -7,6 +7,7 @@ import {
7
7
  unregisterWebMcpTools,
8
8
  onWebMcpToolActivated,
9
9
  onWebMcpToolCancel,
10
+ onWebMcpToolChange,
10
11
  } from "../../../subsystems/mcp/webmcp.js";
11
12
 
12
13
  import { effect } from "../../effect.js";
@@ -53,6 +54,10 @@ export function syncWebMcpRegistration(
53
54
  });
54
55
  });
55
56
 
57
+ const unsubscribeChange = onWebMcpToolChange((event) => {
58
+ orchestrator.events?.emit("webmcp-tool-change", { event });
59
+ });
60
+
56
61
  // Register WebMCP tools and re-register when tool config changes.
57
62
  // This effect runs once immediately to perform the initial registration.
58
63
  // We intentionally do NOT call isWebMcpSupported() here — that accesses
@@ -132,6 +137,9 @@ export function syncWebMcpRegistration(
132
137
  try {
133
138
  unsubscribeCancel();
134
139
  } catch {}
140
+ try {
141
+ unsubscribeChange();
142
+ } catch {}
135
143
  if (typeof disposeEffect === "function") {
136
144
  disposeEffect();
137
145
  }
@@ -861,14 +861,19 @@ describe("initControlPlane", () => {
861
861
  });
862
862
 
863
863
  try {
864
- // 1. Client has WebMCP tools registered, but ask_user is not among them
864
+ // 1. Client has WebMCP tools registered, but read_file is not among them
865
865
  await expect(
866
- executeClientControlCommand("invoke-tool", { toolName: "ask_user" }),
866
+ executeClientControlCommand("invoke-tool", { toolName: "read_file" }),
867
867
  ).rejects.toThrow(
868
- "Tool 'ask_user' is not enabled or registered on this client.",
868
+ "Tool 'read_file' is not enabled or registered on this client.",
869
869
  );
870
870
 
871
- // 2. Conversation group has restricted toolTags
871
+ // 2. Tool is not enabled on this client
872
+ await expect(
873
+ executeClientControlCommand("invoke-tool", { toolName: "ask_user" }),
874
+ ).rejects.toThrow("Tool 'ask_user' is not enabled on this client.");
875
+
876
+ // 3. Conversation group has restricted toolTags
872
877
  const { orchestratorStore } =
873
878
  await import("../../stores/orchestrator.js");
874
879
  const groupsSpy = jest
@@ -465,6 +465,58 @@ describe("custom-element-security", () => {
465
465
  siteConfigScript.remove();
466
466
  }
467
467
  });
468
+
469
+ it("preserves id, name, global, and autoload in script descriptors and approves by id", () => {
470
+ const siteConfigScript = document.createElement("script");
471
+ siteConfigScript.id = "shadow-claw-site-config";
472
+ siteConfigScript.type = "application/json";
473
+ siteConfigScript.textContent = JSON.stringify({
474
+ customElements: {
475
+ allowedDomains: ["kherrick.github.io"],
476
+ scripts: [
477
+ {
478
+ id: "feed-scroller",
479
+ src: "/pages/main/assets/feed-scroller.js",
480
+ global: true,
481
+ hasInit: true,
482
+ },
483
+ {
484
+ name: "x-hook-component",
485
+ src: "/pages/main/assets/x-hook-component.js",
486
+ hasInit: true,
487
+ },
488
+ ],
489
+ },
490
+ });
491
+ document.head.appendChild(siteConfigScript);
492
+
493
+ try {
494
+ installCustomElementsRegistryGuard();
495
+
496
+ const descriptors = getApprovedCustomElementScriptDescriptors();
497
+ expect(descriptors).toEqual([
498
+ {
499
+ id: "feed-scroller",
500
+ src: "/pages/main/assets/feed-scroller.js",
501
+ global: true,
502
+ autoload: true,
503
+ hasInit: true,
504
+ },
505
+ {
506
+ name: "x-hook-component",
507
+ src: "/pages/main/assets/x-hook-component.js",
508
+ hasInit: true,
509
+ },
510
+ ]);
511
+
512
+ expect(isApprovedScript("feed-scroller")).toBe(true);
513
+ expect(isApprovedScript("x-hook-component")).toBe(true);
514
+ expect(isApprovedScript("unknown-id")).toBe(false);
515
+ } finally {
516
+ uninstallCustomElementsRegistryGuard();
517
+ siteConfigScript.remove();
518
+ }
519
+ });
468
520
  });
469
521
 
470
522
  describe("hasApprovedCustomElement", () => {
@@ -564,6 +616,19 @@ describe("custom-element-security", () => {
564
616
  ).toBe(true);
565
617
  });
566
618
 
619
+ it("approves script by stem or filename when config has no id", () => {
620
+ setApprovedCustomElementScripts([
621
+ "/pages/main/assets/x-hook-component.js",
622
+ { src: "/pages/main/assets/x-postpress-code.js" },
623
+ ]);
624
+
625
+ expect(isApprovedScript("x-hook-component")).toBe(true);
626
+ expect(isApprovedScript("x-hook-component.js")).toBe(true);
627
+ expect(isApprovedScript("x-postpress-code")).toBe(true);
628
+ expect(isApprovedScript("x-postpress-code.js")).toBe(true);
629
+ expect(isApprovedScript("missing-widget")).toBe(false);
630
+ });
631
+
567
632
  it("rejects unapproved script paths", () => {
568
633
  setApprovedCustomElementScripts([
569
634
  "/pages/main/assets/x-hook-component.js",
@@ -206,6 +206,13 @@ setEmbeddedConfigHook(configureCustomElementSecurity);
206
206
 
207
207
  export interface CustomElementScriptDescriptor {
208
208
  src: string;
209
+ id?: string;
210
+ name?: string;
211
+ global?: boolean;
212
+ autoload?: boolean;
213
+ type?: string;
214
+ async?: boolean;
215
+ defer?: boolean;
209
216
  hasInit: boolean;
210
217
  }
211
218
 
@@ -215,15 +222,50 @@ let activeApprovedScripts: CustomElementScriptDescriptor[] = [];
215
222
  * Set approved custom element scripts in memory.
216
223
  */
217
224
  export function setApprovedCustomElementScripts(
218
- scripts: Array<string | { src: string; hasInit?: boolean }>,
225
+ scripts: Array<string | Partial<CustomElementScriptDescriptor> | any>,
219
226
  ): void {
220
227
  if (Array.isArray(scripts)) {
221
228
  activeApprovedScripts = scripts
222
229
  .map((s: any) => {
223
230
  const src = typeof s === "string" ? s : s?.src;
231
+ const id =
232
+ typeof s === "object" && s !== null && typeof s.id === "string"
233
+ ? s.id.trim()
234
+ : undefined;
235
+ const name =
236
+ typeof s === "object" && s !== null && typeof s.name === "string"
237
+ ? s.name.trim()
238
+ : undefined;
239
+ const global =
240
+ typeof s === "object" && s !== null
241
+ ? Boolean(s.global || s.autoload)
242
+ : false;
243
+ const type =
244
+ typeof s === "object" && s !== null && typeof s.type === "string"
245
+ ? s.type
246
+ : undefined;
247
+ const async =
248
+ typeof s === "object" && s !== null && typeof s.async === "boolean"
249
+ ? s.async
250
+ : undefined;
251
+ const defer =
252
+ typeof s === "object" && s !== null && typeof s.defer === "boolean"
253
+ ? s.defer
254
+ : undefined;
224
255
  const hasInit =
225
256
  typeof s === "object" && s !== null ? Boolean(s.hasInit) : false;
226
- return { src, hasInit };
257
+
258
+ const descriptor: CustomElementScriptDescriptor = {
259
+ src,
260
+ ...(id ? { id } : {}),
261
+ ...(name ? { name } : {}),
262
+ ...(global ? { global: true, autoload: true } : {}),
263
+ ...(type ? { type } : {}),
264
+ ...(async ? { async } : {}),
265
+ ...(defer ? { defer } : {}),
266
+ hasInit,
267
+ };
268
+ return descriptor;
227
269
  })
228
270
  .filter(
229
271
  (entry): entry is CustomElementScriptDescriptor =>
@@ -292,11 +334,32 @@ export function isApprovedScript(src: string): boolean {
292
334
  return false;
293
335
  }
294
336
 
295
- const approved = getApprovedCustomElementScripts();
296
- if (approved.length === 0) {
337
+ const descriptors = getApprovedCustomElementScriptDescriptors();
338
+ if (descriptors.length === 0) {
297
339
  return false;
298
340
  }
299
341
 
342
+ const trimmedLower = trimmed.toLowerCase();
343
+ const matchByIdOrName = descriptors.some(
344
+ (d) =>
345
+ (d.id && d.id.toLowerCase() === trimmedLower) ||
346
+ (d.name && d.name.toLowerCase() === trimmedLower),
347
+ );
348
+ if (matchByIdOrName) {
349
+ return true;
350
+ }
351
+
352
+ const matchByStemOrFilename = descriptors.some((d) => {
353
+ const base = (d.src.split("/").pop() || "").toLowerCase();
354
+ const stem = base.replace(/\.(js|mjs|cjs|ts)$/i, "");
355
+ return base === trimmedLower || stem === trimmedLower;
356
+ });
357
+ if (matchByStemOrFilename) {
358
+ return true;
359
+ }
360
+
361
+ const approved = getApprovedCustomElementScripts();
362
+
300
363
  const clean = trimmed.split("?")[0].split("#")[0].trim();
301
364
  const stripPrefix = (s: string) =>
302
365
  s
@@ -237,9 +237,11 @@ describe("ToolsStore — declarative tools management", () => {
237
237
  store = new ToolsStore();
238
238
  });
239
239
 
240
- it("defaults to enabling all declarative tools when not explicitly configured", () => {
240
+ it("defaults to having declarative tools not enabled when not explicitly configured", () => {
241
241
  expect(store.declarativeToolNamesEnabled).toBeNull();
242
- expect(store.isDeclarativeToolEnabled("generate_random_number")).toBe(true);
242
+ expect(store.isDeclarativeToolEnabled("generate_random_number")).toBe(
243
+ false,
244
+ );
243
245
  });
244
246
 
245
247
  it("toggles declarative tools and persists enabled state", async () => {
@@ -298,7 +300,7 @@ describe("ToolsStore — declarative tools management", () => {
298
300
  const refreshed = await store.refreshDeclarativeTools(db, "br:main");
299
301
  expect(refreshed).toHaveLength(1);
300
302
  expect(store.declarativeTools).toHaveLength(1);
301
- expect(store.enabledDeclarativeTools).toHaveLength(1);
303
+ expect(store.enabledDeclarativeTools).toHaveLength(0);
302
304
  });
303
305
 
304
306
  describe("Remote Site Integration", () => {