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
@@ -1,16 +1,80 @@
1
1
  import { jest } from "@jest/globals";
2
+ import { webcrypto } from "node:crypto";
3
+
4
+ const mockSubtle = {
5
+ generateKey: jest.fn(async () => ({ type: "secret" })),
6
+ encrypt: jest.fn(
7
+ async (_cfg: unknown, _key: unknown, data: BufferSource) =>
8
+ new Uint8Array(data as ArrayBuffer).slice().buffer,
9
+ ),
10
+ decrypt: jest.fn(
11
+ async (_cfg: unknown, _key: unknown, data: BufferSource) =>
12
+ new Uint8Array(data as ArrayBuffer).slice().buffer,
13
+ ),
14
+ };
15
+
16
+ Object.defineProperty(globalThis, "crypto", {
17
+ value: {
18
+ ...webcrypto,
19
+ subtle: mockSubtle,
20
+ getRandomValues: <T extends ArrayBufferView | null>(array: T): T => {
21
+ if (array && "fill" in array) {
22
+ (array as unknown as Uint8Array).fill(1);
23
+ }
24
+ return array;
25
+ },
26
+ },
27
+ configurable: true,
28
+ writable: true,
29
+ });
30
+
31
+ async function resetKeystore(): Promise<void> {
32
+ const db = await new Promise<IDBDatabase>((resolve, reject) => {
33
+ const req = indexedDB.open("shadowclaw-keystore", 1);
34
+ req.onupgradeneeded = () => {
35
+ req.result.createObjectStore("keys");
36
+ };
37
+ req.onsuccess = () => resolve(req.result);
38
+ req.onerror = () => reject(req.error);
39
+ });
40
+ const tx = db.transaction("keys", "readwrite");
41
+ tx.objectStore("keys").put(
42
+ { type: "secret" } as CryptoKey,
43
+ "api-key-encryption",
44
+ );
45
+ await new Promise<void>((resolve, reject) => {
46
+ tx.oncomplete = () => resolve();
47
+ tx.onerror = () => reject(tx.error);
48
+ });
49
+ db.close();
50
+ }
2
51
 
3
52
  import {
4
53
  ASSISTANT_NAME,
54
+ CONFIG_KEYS,
5
55
  LLAMAFILE_PROXY_URL,
6
56
  getProvider,
57
+ getProviderApiKeyConfigKey,
7
58
  } from "../../config/config.js";
8
- import { createGroup, updateGroupPinnedProvider } from "../../db/groups.js";
59
+ import {
60
+ createGroup,
61
+ saveGroupMetadata,
62
+ updateGroupPinnedProvider,
63
+ } from "../../db/groups.js";
64
+ import { openDatabase } from "../../db/openDatabase.js";
65
+ import { getConfig } from "../../db/getConfig.js";
66
+ import { setConfig } from "../../db/setConfig.js";
67
+ import { encryptValue } from "../../security/crypto.js";
9
68
  import { orchestratorStore } from "../../stores/orchestrator.js";
10
69
  import { toolsStore } from "../../stores/tools.js";
11
70
  import { getWebMcpMode } from "../../subsystems/mcp/webmcp.js";
12
71
  import { buildSystemPrompt } from "../../worker/utils/system-prompt.js";
13
72
  import { Orchestrator } from "./orchestrator.js";
73
+ import type { ShadowClawDatabase } from "../../db/db.js";
74
+ import type { A2UIAction } from "../../ui/a2ui/types.js";
75
+ import type { ToolDefinition } from "../../subsystems/tools/tools.js";
76
+ import type { TaskScheduler } from "../../subsystems/tools/task-scheduler.js";
77
+ import type { ControlPlaneClient } from "../control-plane-client.js";
14
78
 
15
79
  import {
16
80
  deliverIntermediateResponse,
@@ -1898,7 +1962,7 @@ describe("Orchestrator", () => {
1898
1962
  describe("ensureAllConnections", () => {
1899
1963
  it("coordinates Control Plane, channels, task server, and scheduler", async () => {
1900
1964
  const o = new Orchestrator();
1901
- const fakeDb = {} as any;
1965
+ const fakeDb = {} as unknown as ShadowClawDatabase;
1902
1966
  o.db = fakeDb;
1903
1967
  o.taskServerEnabled = true;
1904
1968
 
@@ -1906,14 +1970,16 @@ describe("Orchestrator", () => {
1906
1970
  start: jest.fn(),
1907
1971
  tick: jest.fn().mockResolvedValue(undefined as never),
1908
1972
  };
1909
- o.scheduler = mockScheduler as any;
1973
+ o.scheduler = mockScheduler as unknown as TaskScheduler;
1910
1974
 
1911
1975
  const ensureControlPlaneSpy = jest
1912
1976
  .spyOn(o, "ensureControlPlaneConnected")
1913
- .mockResolvedValue(null as any);
1977
+ .mockResolvedValue(null as unknown as ControlPlaneClient);
1914
1978
 
1915
1979
  const peerjsEnsureSpy = jest.fn();
1916
- (o.peerjs as any).ensureConnected = peerjsEnsureSpy;
1980
+ (
1981
+ o.peerjs as unknown as { ensureConnected: (force?: boolean) => void }
1982
+ ).ensureConnected = peerjsEnsureSpy;
1917
1983
  o.peerjs.running = true;
1918
1984
  o.channelEnabledByType.peerjs = true;
1919
1985
  o.peerjsMyPeerId = "my-peer";
@@ -1938,9 +2004,624 @@ describe("Orchestrator", () => {
1938
2004
  expect(mockScheduler.start).toHaveBeenCalled();
1939
2005
  expect(mockScheduler.tick).toHaveBeenCalled();
1940
2006
 
2007
+ // Test with default options (force: false) and no taskServer / no scheduler
2008
+ o.taskServerEnabled = false;
2009
+ o.db = null;
2010
+ o.scheduler = null;
2011
+ await o.ensureAllConnections();
2012
+ expect(ensureControlPlaneSpy).toHaveBeenCalledWith({
2013
+ orchestrator: o,
2014
+ db: undefined,
2015
+ force: false,
2016
+ });
2017
+
2018
+ // Test error handling in Control Plane and channel running states
2019
+ const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
2020
+ ensureControlPlaneSpy.mockRejectedValueOnce(
2021
+ new Error("CP network failure"),
2022
+ );
2023
+ jest.spyOn(o.browserChat, "start").mockImplementationOnce(() => {
2024
+ throw new Error("Browser chat error");
2025
+ });
2026
+
2027
+ await o.ensureAllConnections();
2028
+ expect(warnSpy).toHaveBeenCalled();
2029
+ warnSpy.mockRestore();
2030
+
1941
2031
  ensureControlPlaneSpy.mockRestore();
1942
2032
  replaySpy.mockRestore();
1943
2033
  loadTasksSpy.mockRestore();
1944
2034
  });
1945
2035
  });
2036
+
2037
+ describe("Orchestrator instance methods and lifecycle", () => {
2038
+ it("initializes room notification handler delegating to roomManager", () => {
2039
+ const o = new Orchestrator();
2040
+ const handleSpy = jest
2041
+ .spyOn(o.roomManager, "handleNotification")
2042
+ .mockImplementation(() => {});
2043
+
2044
+ const peerChannel = o.peerjs as unknown as {
2045
+ _roomNotificationHandler?: (
2046
+ from: string,
2047
+ method: string,
2048
+ params: unknown,
2049
+ ) => void;
2050
+ };
2051
+ peerChannel._roomNotificationHandler?.("peer-sender", "room/ping", {
2052
+ data: 123,
2053
+ });
2054
+
2055
+ expect(handleSpy).toHaveBeenCalledWith("peer-sender", "room/ping", {
2056
+ data: 123,
2057
+ });
2058
+ });
2059
+
2060
+ it("initializes orchestrator via init() and sets database and listeners", async () => {
2061
+ const origWorker = globalThis.Worker;
2062
+ globalThis.Worker = class {
2063
+ postMessage() {}
2064
+ terminate() {}
2065
+ addEventListener() {}
2066
+ removeEventListener() {}
2067
+ } as unknown as typeof Worker;
2068
+
2069
+ const o = new Orchestrator();
2070
+ jest.spyOn(o.roomManager, "loadRooms").mockImplementation(() => {});
2071
+ jest.spyOn(toolsStore, "load").mockResolvedValue(undefined);
2072
+ jest.spyOn(o, "loadApiKeyForProvider").mockResolvedValue();
2073
+ jest.spyOn(o, "loadSecretConfig").mockResolvedValue("");
2074
+
2075
+ const readySpy = jest.fn();
2076
+ o.events.on("ready", readySpy);
2077
+
2078
+ const db = await o.init();
2079
+ expect(db).toBeDefined();
2080
+ expect(o.db).toBe(db);
2081
+ expect(readySpy).toHaveBeenCalled();
2082
+
2083
+ // Flush microtasks and tick timer for background channel setup
2084
+ await new Promise<void>((resolve) => setTimeout(resolve, 30));
2085
+ await o.browserChat.send("br:main", "test-display");
2086
+
2087
+ if (!origWorker) {
2088
+ delete (globalThis as unknown as { Worker?: unknown }).Worker;
2089
+ } else {
2090
+ globalThis.Worker = origWorker;
2091
+ }
2092
+ });
2093
+
2094
+ it("shuts down channels, scheduler, pollers, worker, and webMcp cleanup", () => {
2095
+ const o = new Orchestrator();
2096
+ const mockWorker = {
2097
+ terminate: jest.fn(),
2098
+ postMessage: jest.fn(),
2099
+ } as unknown as Worker;
2100
+ o.agentWorker = mockWorker;
2101
+
2102
+ const mockScheduler = {
2103
+ stop: jest.fn(),
2104
+ start: jest.fn(),
2105
+ tick: jest.fn(),
2106
+ } as unknown as TaskScheduler;
2107
+ o.scheduler = mockScheduler;
2108
+
2109
+ const mockCleanup = jest.fn();
2110
+ o.webMcpEffectCleanup = mockCleanup;
2111
+
2112
+ o.transformersProgressPollers.set("group-poll-1", 123);
2113
+
2114
+ o.shutdown();
2115
+
2116
+ expect(mockWorker.terminate).toHaveBeenCalled();
2117
+ expect(mockScheduler.stop).toHaveBeenCalled();
2118
+ expect(mockCleanup).toHaveBeenCalled();
2119
+ expect(o.transformersProgressPollers.size).toBe(0);
2120
+ });
2121
+
2122
+ it("manages provider request IDs for llamafile vs other providers", () => {
2123
+ const o = new Orchestrator();
2124
+ o.provider = "openrouter";
2125
+ expect(o.createProviderRequestId("group-1")).toBe("");
2126
+
2127
+ o.provider = "llamafile";
2128
+ const reqId = o.createProviderRequestId("group-llama");
2129
+ expect(reqId).toContain("group-llama:");
2130
+ expect(o.inFlightProviderRequestIds.get("group-llama")).toBe(reqId);
2131
+
2132
+ o.clearProviderRequest("group-llama");
2133
+ expect(o.inFlightProviderRequestIds.has("group-llama")).toBe(false);
2134
+ });
2135
+
2136
+ it("manages state transitions, submitMessage, and stopCurrentRequest across providers", () => {
2137
+ const o = new Orchestrator();
2138
+ const stateSpy = jest.fn();
2139
+ o.events.on("state-change", stateSpy);
2140
+
2141
+ o.setState("thinking", "group-state");
2142
+ expect(o.state).toBe("thinking");
2143
+ expect(stateSpy).toHaveBeenCalledWith({
2144
+ state: "thinking",
2145
+ groupId: "group-state",
2146
+ });
2147
+
2148
+ const submitSpy = jest
2149
+ .spyOn(o.browserChat, "submit")
2150
+ .mockImplementation(() => {});
2151
+ o.submitMessage("Hello from user", "group-submit");
2152
+ expect(submitSpy).toHaveBeenCalledWith("Hello from user", "group-submit");
2153
+ o.submitMessage("Default message");
2154
+ expect(submitSpy).toHaveBeenCalledWith("Default message", "br:main");
2155
+
2156
+ // Early return when not thinking or responding
2157
+ o.state = "idle";
2158
+ o.stopCurrentRequest("group-idle");
2159
+ o.stopCurrentRequest();
2160
+
2161
+ // Stop current request with llamafile, promptController, and agentWorker
2162
+ o.state = "thinking";
2163
+ o.provider = "llamafile";
2164
+ o.inFlightProviderRequestIds.set("group-stop", "req-stop-1");
2165
+ const mockAbort = jest.fn();
2166
+ o.promptControllers.set("group-stop", {
2167
+ abort: mockAbort,
2168
+ } as unknown as AbortController);
2169
+ o.inFlightTriggerByGroup.set("group-stop", "trigger-content");
2170
+ o.inFlightEffectiveProviderByGroup.set("group-stop", {
2171
+ providerId: "llamafile",
2172
+ providerConfig: o.providerConfig,
2173
+ model: "model",
2174
+ });
2175
+
2176
+ const mockWorker = {
2177
+ postMessage: jest.fn(),
2178
+ terminate: jest.fn(),
2179
+ } as unknown as Worker;
2180
+ o.agentWorker = mockWorker;
2181
+
2182
+ o.stopCurrentRequest("group-stop");
2183
+
2184
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({
2185
+ type: "cancel",
2186
+ payload: { groupId: "group-stop" },
2187
+ });
2188
+ expect(mockAbort).toHaveBeenCalled();
2189
+ expect(o.inFlightTriggerByGroup.has("group-stop")).toBe(false);
2190
+ expect(o.inFlightEffectiveProviderByGroup.has("group-stop")).toBe(false);
2191
+ expect(o.state).toBe("idle");
2192
+
2193
+ // Stop current request when responding with non-llamafile
2194
+ o.state = "responding";
2195
+ o.provider = "anthropic";
2196
+ o.stopCurrentRequest("group-resp");
2197
+ expect(o.state).toBe("idle");
2198
+ });
2199
+
2200
+ it("handles API key retrieval, caching, setting, and decryption failure", async () => {
2201
+ const o = new Orchestrator();
2202
+ const db = await openDatabase();
2203
+
2204
+ // No encrypted key returns null
2205
+ expect(await o.getApiKey()).toBeNull();
2206
+
2207
+ // Set key and retrieve (first call decrypts, second hits cache)
2208
+ await o.setApiKey(db, "sk-test-secret-key-1");
2209
+ const key1 = await o.getApiKey();
2210
+ expect(key1).toBe("sk-test-secret-key-1");
2211
+
2212
+ const key2 = await o.getApiKey();
2213
+ expect(key2).toBe("sk-test-secret-key-1");
2214
+
2215
+ // setApiKey throws when encryptValue returns null
2216
+ indexedDB.deleteDatabase("shadowclaw-keystore");
2217
+ mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
2218
+ await expect(o.setApiKey(db, "bad-key")).rejects.toThrow(
2219
+ "key failed to encrypt",
2220
+ );
2221
+ mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
2222
+ await resetKeystore();
2223
+
2224
+ // Decryption failure caught and returns null
2225
+ const errSpy = jest.spyOn(console, "error").mockImplementation(() => {});
2226
+ await o.setApiKey(db, "key-to-fail");
2227
+ mockSubtle.decrypt.mockRejectedValueOnce(new Error("Decryption error"));
2228
+ const failedKey = await o.getApiKey();
2229
+ expect(failedKey).toBeNull();
2230
+ errSpy.mockRestore();
2231
+
2232
+ // Decryption returns null when decryptValue returns null
2233
+ indexedDB.deleteDatabase("shadowclaw-keystore");
2234
+ mockSubtle.generateKey.mockResolvedValueOnce(
2235
+ null as unknown as CryptoKey,
2236
+ );
2237
+ const nullKey = await o.getApiKey();
2238
+ expect(nullKey).toBeNull();
2239
+ await resetKeystore();
2240
+ });
2241
+
2242
+ it("gets API key for specific provider with openrouter legacy fallback and error handling", async () => {
2243
+ const o = new Orchestrator();
2244
+ const db = await openDatabase();
2245
+
2246
+ // Provider key exists
2247
+ const encAnthropic = await encryptValue("sk-anthropic-specific");
2248
+ await setConfig(
2249
+ db,
2250
+ getProviderApiKeyConfigKey("anthropic"),
2251
+ encAnthropic,
2252
+ );
2253
+ expect(await o.getApiKeyForSpecificProvider(db, "anthropic")).toBe(
2254
+ "sk-anthropic-specific",
2255
+ );
2256
+
2257
+ // OpenRouter legacy fallback
2258
+ await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
2259
+ const encLegacy = await encryptValue("sk-openrouter-legacy");
2260
+ await setConfig(db, CONFIG_KEYS.API_KEY, encLegacy);
2261
+ expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe(
2262
+ "sk-openrouter-legacy",
2263
+ );
2264
+
2265
+ // OpenRouter with empty key and empty legacy key
2266
+ await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
2267
+ await setConfig(db, CONFIG_KEYS.API_KEY, "");
2268
+ expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
2269
+
2270
+ // Decrypted empty string returns empty string
2271
+ const encVal = await encryptValue("val");
2272
+ await setConfig(db, getProviderApiKeyConfigKey("openrouter"), encVal);
2273
+ mockSubtle.decrypt.mockResolvedValueOnce(new Uint8Array(0).buffer);
2274
+ expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
2275
+
2276
+ // Missing key returns empty string
2277
+ expect(await o.getApiKeyForSpecificProvider(db, "nonexistent")).toBe("");
2278
+
2279
+ // Decryption failure caught and returns empty string
2280
+ const errSpy = jest.spyOn(console, "error").mockImplementation(() => {});
2281
+ mockSubtle.decrypt.mockRejectedValueOnce(new Error("Bad cipher"));
2282
+ expect(await o.getApiKeyForSpecificProvider(db, "openrouter")).toBe("");
2283
+ errSpy.mockRestore();
2284
+ });
2285
+
2286
+ it("loads API key for provider with legacy migration and missing key handling", async () => {
2287
+ const o = new Orchestrator();
2288
+ const db = await openDatabase();
2289
+
2290
+ // Direct key exists
2291
+ const encDirect = await encryptValue("sk-direct-key");
2292
+ await setConfig(
2293
+ db,
2294
+ getProviderApiKeyConfigKey("bedrock_proxy"),
2295
+ encDirect,
2296
+ );
2297
+ await o.loadApiKeyForProvider(db, "bedrock_proxy");
2298
+ expect(await o.getApiKey()).toBe("sk-direct-key");
2299
+
2300
+ // OpenRouter with legacy key migration
2301
+ await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
2302
+ const encLegacy = await encryptValue("sk-migrated-openrouter");
2303
+ await setConfig(db, CONFIG_KEYS.API_KEY, encLegacy);
2304
+ await o.loadApiKeyForProvider(db, "openrouter");
2305
+ expect(await o.getApiKey()).toBe("sk-migrated-openrouter");
2306
+ expect(
2307
+ await getConfig(db, getProviderApiKeyConfigKey("openrouter")),
2308
+ ).toBe(encLegacy);
2309
+
2310
+ // OpenRouter with missing key and missing legacy key
2311
+ await setConfig(db, getProviderApiKeyConfigKey("openrouter"), "");
2312
+ await setConfig(db, CONFIG_KEYS.API_KEY, "");
2313
+ await o.loadApiKeyForProvider(db, "openrouter");
2314
+ expect(await o.getApiKey()).toBeNull();
2315
+
2316
+ // Missing key clears internal key
2317
+ await o.loadApiKeyForProvider(db, "missing-provider");
2318
+ expect(await o.getApiKey()).toBeNull();
2319
+ });
2320
+
2321
+ it("loads and saves secret config with auto-migration and error handling", async () => {
2322
+ const o = new Orchestrator();
2323
+ const db = await openDatabase();
2324
+
2325
+ // Empty value clears secret
2326
+ await o.saveSecretConfig(db, "empty_secret", "");
2327
+ expect(await getConfig(db, "empty_secret")).toBe("");
2328
+ expect(await o.loadSecretConfig(db, "empty_secret")).toBe("");
2329
+
2330
+ // Non-empty value encrypted and decrypted
2331
+ await o.saveSecretConfig(db, "my_secret", "secret-content-xyz");
2332
+ const storedEncrypted = await getConfig(db, "my_secret");
2333
+ expect(storedEncrypted).not.toBe("secret-content-xyz");
2334
+ expect(await o.loadSecretConfig(db, "my_secret")).toBe(
2335
+ "secret-content-xyz",
2336
+ );
2337
+
2338
+ // Decrypted empty string returns empty string
2339
+ const encEmptyCipher = await encryptValue("val-empty");
2340
+ await setConfig(db, "empty_cipher_secret", encEmptyCipher);
2341
+ mockSubtle.decrypt.mockResolvedValueOnce(new Uint8Array(0).buffer);
2342
+ expect(await o.loadSecretConfig(db, "empty_cipher_secret")).toBe("");
2343
+
2344
+ // Auto-encrypt migration for plaintext value
2345
+ await setConfig(db, "legacy_plaintext", "raw-secret-value");
2346
+ const migrated = await o.loadSecretConfig(db, "legacy_plaintext");
2347
+ expect(migrated).toBe("raw-secret-value");
2348
+ const newEncrypted = await getConfig(db, "legacy_plaintext");
2349
+ expect(newEncrypted).not.toBe("raw-secret-value");
2350
+
2351
+ // Auto-encrypt migration where encryptValue returns null
2352
+ const encTemp = await encryptValue("plain-text-val");
2353
+ await setConfig(db, "unencrypted-secret-fail", encTemp);
2354
+ mockSubtle.decrypt.mockImplementationOnce(async () => {
2355
+ indexedDB.deleteDatabase("shadowclaw-keystore");
2356
+ mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
2357
+ throw new Error("bad decrypt");
2358
+ });
2359
+ expect(await o.loadSecretConfig(db, "unencrypted-secret-fail")).toBe(
2360
+ encTemp,
2361
+ );
2362
+ mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
2363
+ await resetKeystore();
2364
+
2365
+ // saveSecretConfig throws when encryptValue returns null
2366
+ indexedDB.deleteDatabase("shadowclaw-keystore");
2367
+ mockSubtle.generateKey.mockResolvedValueOnce(
2368
+ null as unknown as CryptoKey,
2369
+ );
2370
+ await expect(
2371
+ o.saveSecretConfig(db, "fail_secret", "val"),
2372
+ ).rejects.toThrow("Failed to encrypt secret config");
2373
+ mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
2374
+ await resetKeystore();
2375
+ });
2376
+
2377
+ it("handles compaction and new session lifecycle", async () => {
2378
+ const o = new Orchestrator();
2379
+ const db = await openDatabase();
2380
+
2381
+ const compactSpy = jest.fn();
2382
+ const typingSpy = jest.fn();
2383
+ o.events.on("context-compacted", compactSpy);
2384
+ o.events.on("typing", typingSpy);
2385
+
2386
+ await o.handleCompactDone(db, "group-compact", "Compacted chat history");
2387
+ expect(compactSpy).toHaveBeenCalledWith({
2388
+ groupId: "group-compact",
2389
+ summary: "Compacted chat history",
2390
+ });
2391
+ expect(typingSpy).toHaveBeenCalledWith({
2392
+ groupId: "group-compact",
2393
+ typing: false,
2394
+ });
2395
+ expect(o.state).toBe("idle");
2396
+
2397
+ const sessionSpy = jest.fn();
2398
+ o.events.on("session-reset", sessionSpy);
2399
+ await o.newSession(db, "group-session");
2400
+ expect(sessionSpy).toHaveBeenCalledWith({ groupId: "group-session" });
2401
+
2402
+ // newSession with default groupId
2403
+ await o.newSession(db);
2404
+ expect(sessionSpy).toHaveBeenCalledWith({ groupId: "br:main" });
2405
+ });
2406
+
2407
+ it("restarts current request when active or returns false when inactive", async () => {
2408
+ const o = new Orchestrator();
2409
+
2410
+ // State is idle (also testing default groupId)
2411
+ o.state = "idle";
2412
+ expect(await o.restartCurrentRequest("g-restart")).toBe(false);
2413
+ expect(await o.restartCurrentRequest()).toBe(false);
2414
+
2415
+ // State is thinking but db is null
2416
+ o.state = "thinking";
2417
+ o.db = null;
2418
+ expect(await o.restartCurrentRequest("g-restart")).toBe(false);
2419
+
2420
+ // Db present but no in-flight trigger
2421
+ const db = await openDatabase();
2422
+ o.db = db;
2423
+ expect(await o.restartCurrentRequest("g-restart")).toBe(false);
2424
+
2425
+ // Active request restarted
2426
+ o.state = "thinking";
2427
+ o.inFlightTriggerByGroup.set("g-restart", "Original prompt");
2428
+ o.agentWorker = {
2429
+ postMessage: jest.fn(),
2430
+ terminate: jest.fn(),
2431
+ } as unknown as Worker;
2432
+
2433
+ const stopSpy = jest.spyOn(o, "stopCurrentRequest");
2434
+ const restarted = await o.restartCurrentRequest("g-restart");
2435
+ expect(restarted).toBe(true);
2436
+ expect(stopSpy).toHaveBeenCalledWith("g-restart");
2437
+ });
2438
+
2439
+ it("routes room A2UI actions to local enqueue or remote broadcast", async () => {
2440
+ const o = new Orchestrator();
2441
+ const action: A2UIAction = {
2442
+ type: "a2ui-action",
2443
+ actionId: "act-1",
2444
+ surfaceId: "surf-1",
2445
+ dataModel: { val: 42 },
2446
+ };
2447
+
2448
+ // Local surface with no db returns early
2449
+ o.db = null;
2450
+ await o.routeRoomA2UIAction("room:room-local", action);
2451
+ expect(o.messageQueue).toHaveLength(0);
2452
+
2453
+ // Local surface with db enqueues action
2454
+ const db = await openDatabase();
2455
+ o.db = db;
2456
+ o.peerjsMyPeerId = "peer-local";
2457
+ o.peerjs.myPeerId = "peer-local";
2458
+ jest
2459
+ .spyOn(o.roomManager, "getSurfaceOwner")
2460
+ .mockReturnValue("peer-local");
2461
+
2462
+ await o.routeRoomA2UIAction("room:room-local", action);
2463
+ expect(o.messageQueue.length).toBeGreaterThanOrEqual(1);
2464
+
2465
+ // Local surface with missing alias and peer id defaults sender to "you"
2466
+ o.peerjsMyAlias = "";
2467
+ o.peerjs.myPeerId = "";
2468
+ o.peerjsMyPeerId = "";
2469
+ jest.spyOn(o.roomManager, "getSurfaceOwner").mockReturnValue("");
2470
+ await o.routeRoomA2UIAction("room:room-local-you", action);
2471
+ expect(o.messageQueue[o.messageQueue.length - 1].sender).toBe("you");
2472
+
2473
+ // Remote surface broadcasts action to room mesh
2474
+ jest
2475
+ .spyOn(o.roomManager, "getSurfaceOwner")
2476
+ .mockReturnValue("peer-remote-99");
2477
+ const broadcastSpy = jest
2478
+ .spyOn(o.roomManager, "broadcastA2UIAction")
2479
+ .mockImplementation(() => null);
2480
+
2481
+ await o.routeRoomA2UIAction("room:room-remote", action);
2482
+ expect(broadcastSpy).toHaveBeenCalledWith("room-remote", action);
2483
+ });
2484
+
2485
+ it("refreshes context usage across group pinned models, prompt_api fallbacks, and tokenUsage blending", async () => {
2486
+ const o = new Orchestrator();
2487
+ const db = await openDatabase();
2488
+ o.db = db;
2489
+
2490
+ const contextSpy = jest.fn();
2491
+ o.events.on("context-usage", contextSpy);
2492
+
2493
+ // Groups with pinnedProvider, pinnedModel, pinnedMaxTokens, and prompt_api
2494
+ await saveGroupMetadata(db, [
2495
+ {
2496
+ groupId: "group-pinned-model",
2497
+ name: "Pinned Group",
2498
+ createdAt: Date.now(),
2499
+ pinnedProvider: "openrouter",
2500
+ pinnedModel: "anthropic/claude-3-5-sonnet",
2501
+ pinnedMaxTokens: 4096,
2502
+ },
2503
+ {
2504
+ groupId: "group-provider-default-model",
2505
+ name: "Provider Default Model",
2506
+ createdAt: Date.now(),
2507
+ pinnedProvider: "anthropic",
2508
+ },
2509
+ {
2510
+ groupId: "group-unknown-provider",
2511
+ name: "Unknown Provider",
2512
+ createdAt: Date.now(),
2513
+ pinnedProvider: "unknown_xyz_provider",
2514
+ },
2515
+ {
2516
+ groupId: "group-prompt-api",
2517
+ name: "Prompt API Group",
2518
+ createdAt: Date.now(),
2519
+ pinnedProvider: "prompt_api",
2520
+ pinnedModel: "browser-built-in",
2521
+ },
2522
+ {
2523
+ groupId: "group-prompt-api-default",
2524
+ name: "Prompt API Default Fallback",
2525
+ createdAt: Date.now(),
2526
+ pinnedProvider: "prompt_api",
2527
+ pinnedModel: "",
2528
+ },
2529
+ ]);
2530
+
2531
+ // Token usage blending via orchestratorStore
2532
+ const usageSpy = jest
2533
+ .spyOn(orchestratorStore, "tokenUsage", "get")
2534
+ .mockReturnValue({
2535
+ inputTokens: 600,
2536
+ cacheReadTokens: 150,
2537
+ outputTokens: 250,
2538
+ totalTokens: 1000,
2539
+ cacheCreationTokens: 0,
2540
+ contextLimit: 4096,
2541
+ groupId: "group-pinned-model",
2542
+ });
2543
+
2544
+ await o.refreshContextUsage(db, "group-pinned-model");
2545
+ expect(contextSpy).toHaveBeenCalled();
2546
+ usageSpy.mockRestore();
2547
+
2548
+ // Token usage blending with falsy/zero values
2549
+ const usageZeroSpy = jest
2550
+ .spyOn(orchestratorStore, "tokenUsage", "get")
2551
+ .mockReturnValue({
2552
+ inputTokens: 0,
2553
+ cacheReadTokens: 0,
2554
+ outputTokens: 0,
2555
+ totalTokens: 0,
2556
+ cacheCreationTokens: 0,
2557
+ contextLimit: 4096,
2558
+ groupId: "group-provider-default-model",
2559
+ });
2560
+
2561
+ await o.refreshContextUsage(db, "group-provider-default-model");
2562
+ expect(contextSpy).toHaveBeenCalled();
2563
+ usageZeroSpy.mockRestore();
2564
+
2565
+ // Provider default model lookup when pinnedProvider has no pinnedModel
2566
+ await o.refreshContextUsage(db, "group-provider-default-model");
2567
+ // Unknown provider fallback to this.model
2568
+ await o.refreshContextUsage(db, "group-unknown-provider");
2569
+
2570
+ // Active tools undefined fallback to 0 tokens
2571
+ const toolsSpy = jest
2572
+ .spyOn(toolsStore, "enabledTools", "get")
2573
+ .mockReturnValue(undefined as unknown as ToolDefinition[]);
2574
+ await o.refreshContextUsage(db, "group-provider-default-model");
2575
+ toolsSpy.mockRestore();
2576
+
2577
+ // Default groupId argument
2578
+ await o.refreshContextUsage(db);
2579
+
2580
+ const savedLm = (globalThis as Record<string, unknown>).LanguageModel;
2581
+ const savedWinLm = (window as unknown as Record<string, unknown>)
2582
+ .LanguageModel;
2583
+ (globalThis as Record<string, unknown>).LanguageModel = undefined;
2584
+ (window as unknown as Record<string, unknown>).LanguageModel = undefined;
2585
+
2586
+ // Configured fallback model for prompt_api
2587
+ await setConfig(
2588
+ db,
2589
+ CONFIG_KEYS.PROMPT_API_FALLBACK_MODEL,
2590
+ "custom-prompt-fallback",
2591
+ );
2592
+ await o.refreshContextUsage(db, "group-prompt-api");
2593
+ expect(contextSpy).toHaveBeenCalled();
2594
+
2595
+ // Default fallback model for prompt_api when configuredFallback is empty
2596
+ await setConfig(db, CONFIG_KEYS.PROMPT_API_FALLBACK_MODEL, "");
2597
+ await o.refreshContextUsage(db, "group-prompt-api-default");
2598
+ expect(contextSpy).toHaveBeenCalled();
2599
+
2600
+ // Prompt API fallback when this.provider === "prompt_api" and group has no pinned provider
2601
+ o.provider = "prompt_api";
2602
+ o.model = "";
2603
+ await o.refreshContextUsage(db, "group-unknown");
2604
+ expect(contextSpy).toHaveBeenCalled();
2605
+
2606
+ if (savedLm !== undefined) {
2607
+ (globalThis as Record<string, unknown>).LanguageModel = savedLm;
2608
+ }
2609
+ if (savedWinLm !== undefined) {
2610
+ (window as unknown as Record<string, unknown>).LanguageModel =
2611
+ savedWinLm;
2612
+ }
2613
+ });
2614
+
2615
+ it("handles saveSecretConfig encryption failure", async () => {
2616
+ const o = new Orchestrator();
2617
+ const db = await openDatabase();
2618
+ indexedDB.deleteDatabase("shadowclaw-keystore");
2619
+ mockSubtle.generateKey.mockResolvedValue(null as unknown as CryptoKey);
2620
+ await expect(
2621
+ o.saveSecretConfig(db, "secret-key", "secret-value"),
2622
+ ).rejects.toThrow("Failed to encrypt secret config");
2623
+ mockSubtle.generateKey.mockResolvedValue({ type: "secret" });
2624
+ await resetKeystore();
2625
+ });
2626
+ });
1946
2627
  });