shadow-claw 1.23.3 → 1.24.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/README.md +42 -5
  2. package/bin/assert-version-bump.mjs +60 -38
  3. package/bin/assert-version-bump.test.mjs +74 -11
  4. package/bin/cli.mjs +166 -0
  5. package/bin/cli.test.mjs +60 -0
  6. package/bin/commands/backup.mjs +70 -0
  7. package/bin/commands/clients.mjs +41 -0
  8. package/bin/commands/peer-id.mjs +76 -0
  9. package/bin/commands/peer-id.test.mjs +104 -0
  10. package/bin/commands/send.mjs +51 -0
  11. package/bin/commands/tasks.mjs +59 -0
  12. package/bin/commands/webrtc-listen.mjs +254 -0
  13. package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
  14. package/bin/rolldown.config.test.mjs +401 -8
  15. package/bin/utils/control-client.mjs +243 -0
  16. package/bin/utils/control-client.test.mjs +83 -0
  17. package/bin/utils/ulid.mjs +45 -0
  18. package/bin/utils/webrtc-control-client.mjs +699 -0
  19. package/bin/utils/webrtc-control-client.test.mjs +318 -0
  20. package/dist/electron/main.cjs +1 -1
  21. package/dist/public/AGENTS.md +5 -3
  22. package/dist/public/README.md +42 -5
  23. package/dist/public/agent.worker.js +6 -6
  24. package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
  25. package/dist/public/backup-controller-C_s9in2t.js +1 -0
  26. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
  27. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
  28. package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
  29. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
  30. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  31. package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
  32. package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
  33. package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
  34. package/dist/public/config-CrHchneq.js +1 -0
  35. package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
  36. package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
  37. package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
  38. package/dist/public/docs/README.md +3 -1
  39. package/dist/public/docs/architecture/overview.md +12 -12
  40. package/dist/public/docs/architecture/storage.md +5 -2
  41. package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
  42. package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
  43. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  44. package/dist/public/docs/example/article/index.html +2 -2
  45. package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
  46. package/dist/public/docs/guides/server-development-configuration.md +24 -0
  47. package/dist/public/docs/publishing/index.html +19 -76
  48. package/dist/public/docs/skill-creator/index.html +2 -2
  49. package/dist/public/docs/subsystems/backup.md +56 -0
  50. package/dist/public/docs/subsystems/cli.md +124 -0
  51. package/dist/public/docs/subsystems/control-plane.md +158 -0
  52. package/dist/public/docs/subsystems/notifications.md +54 -2
  53. package/dist/public/docs/subsystems/reactive-ui.md +1 -0
  54. package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
  55. package/dist/public/e2e/README.md +5 -1
  56. package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
  57. package/dist/public/file-viewer-D0eflgt6.js +1 -0
  58. package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
  59. package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
  60. package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
  61. package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
  62. package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
  63. package/dist/public/index.html +2 -2
  64. package/dist/public/index.js +1 -1
  65. package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
  66. package/dist/public/initControlPlane-C9jpnWev.js +1 -0
  67. package/dist/public/llms.txt +1 -1
  68. package/dist/public/main/index.html +2 -2
  69. package/dist/public/main/memory/index.html +2 -2
  70. package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
  71. package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
  72. package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
  73. package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
  74. package/dist/public/orchestrator-nJj59RqD.js +15 -0
  75. package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
  76. package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
  77. package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
  78. package/dist/public/service-worker/fetch-proxy.js +1 -1
  79. package/dist/public/service-worker/push-handler.js +1 -1
  80. package/dist/public/service-worker.js +1 -1
  81. package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
  82. package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
  83. package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
  84. package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
  85. package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
  86. package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
  87. package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
  88. package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
  89. package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
  90. package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
  91. package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
  92. package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
  93. package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
  94. package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
  95. package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
  96. package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
  97. package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
  98. package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
  99. package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
  100. package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
  101. package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
  102. package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
  103. package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
  104. package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
  105. package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
  106. package/dist/public/static-main-manifest.json +1 -1
  107. package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
  108. package/dist/server.js +286 -189
  109. package/package.json +2 -1
  110. package/src/__mocks__/jszip.cjs +15 -5
  111. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
  112. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
  113. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
  114. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
  115. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
  116. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
  117. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
  118. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
  119. package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
  120. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
  121. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
  122. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
  123. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
  124. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
  125. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
  126. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
  127. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
  128. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  129. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
  130. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
  131. package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
  132. package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
  133. package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
  134. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
  135. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
  136. package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
  137. package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
  138. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
  139. package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
  140. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
  141. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
  142. package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
  143. package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
  144. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
  145. package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
  146. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
  147. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
  148. package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
  149. package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
  150. package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
  151. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
  152. package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
  153. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
  154. package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
  155. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
  156. package/src/config/config.test.ts +9 -0
  157. package/src/config/config.ts +15 -0
  158. package/src/core/app-routes.test.ts +56 -0
  159. package/src/core/app-routes.ts +8 -1
  160. package/src/core/backup-controller.test.ts +101 -0
  161. package/src/core/backup-controller.ts +137 -0
  162. package/src/core/control-plane-client.test.ts +408 -0
  163. package/src/core/control-plane-client.ts +425 -0
  164. package/src/core/orchestrator/orchestrator.ts +4 -0
  165. package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
  166. package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
  167. package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
  168. package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
  169. package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
  170. package/src/core/utils/initControlPlane.test.ts +276 -0
  171. package/src/core/utils/initControlPlane.ts +437 -0
  172. package/src/core/utils/initializeApp.ts +18 -0
  173. package/src/db/getSession.test.ts +13 -1
  174. package/src/db/openDatabase.test.ts +10 -1
  175. package/src/db/openDatabase.ts +3 -1
  176. package/src/server/app.test.ts +12 -0
  177. package/src/server/app.ts +18 -8
  178. package/src/server/client-registry.test.ts +250 -0
  179. package/src/server/client-registry.ts +477 -0
  180. package/src/server/config.ts +15 -0
  181. package/src/server/control-plane-types.ts +120 -0
  182. package/src/server/control-plane.test.ts +472 -0
  183. package/src/server/control-plane.ts +581 -0
  184. package/src/server/routes/backup.test.ts +263 -0
  185. package/src/server/routes/backup.ts +262 -0
  186. package/src/server/routes/integrations-email.test.ts +532 -0
  187. package/src/server/routes/llamafile.test.ts +172 -48
  188. package/src/server/routes/mesh-llm.test.ts +192 -0
  189. package/src/server/routes/ollama.test.ts +49 -0
  190. package/src/server/server-peer.test.ts +86 -0
  191. package/src/server/server-peer.ts +330 -0
  192. package/src/server/server.ts +35 -0
  193. package/src/server/services/llamafile-manager.test.ts +230 -6
  194. package/src/server/services/transformers-runtime.test.ts +186 -18
  195. package/src/server/webrtc.test.ts +21 -0
  196. package/src/server/webrtc.ts +72 -0
  197. package/src/service-worker/fetch-proxy.test.ts +64 -1
  198. package/src/service-worker/push-handler.ts +39 -0
  199. package/src/shell/vm.test.ts +744 -12
  200. package/src/storage/ensureMainGroupIndex.test.ts +153 -0
  201. package/src/storage/getGroupFile.test.ts +66 -0
  202. package/src/storage/memoryStorage.test.ts +82 -2
  203. package/src/storage/migrateLegacyOpfs.test.ts +327 -0
  204. package/src/storage/migrateLegacyOpfs.ts +117 -0
  205. package/src/storage/writeGroupFileBytes.test.ts +143 -0
  206. package/src/stores/chat-ui.test.ts +20 -0
  207. package/src/stores/orchestrator.test.ts +134 -0
  208. package/src/stores/toast.test.ts +50 -0
  209. package/src/stores/tools.test.ts +147 -10
  210. package/src/subsystems/channels/imessage.test.ts +59 -0
  211. package/src/subsystems/channels/peerjs.test.ts +500 -62
  212. package/src/subsystems/channels/peerjs.ts +71 -4
  213. package/src/subsystems/channels/telegram.test.ts +177 -0
  214. package/src/subsystems/email/catalog.test.ts +1 -0
  215. package/src/subsystems/email/connections.test.ts +39 -1
  216. package/src/subsystems/git/credentials.test.ts +160 -825
  217. package/src/subsystems/git/git.test.ts +463 -0
  218. package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
  219. package/src/subsystems/notifications/push-routes.test.ts +33 -0
  220. package/src/subsystems/notifications/push-routes.ts +23 -0
  221. package/src/subsystems/notifications/push-store.ts +1 -1
  222. package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
  223. package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
  224. package/src/subsystems/providers/model-registry.test.ts +115 -0
  225. package/src/subsystems/providers/providers.test.ts +94 -1
  226. package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
  227. package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
  228. package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
  229. package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
  230. package/src/subsystems/skills/activateSkill.test.ts +101 -0
  231. package/src/subsystems/skills/discoverSkills.test.ts +251 -0
  232. package/src/subsystems/tools/cron.test.ts +64 -0
  233. package/src/subsystems/tools/declarative.test.ts +255 -89
  234. package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
  235. package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
  236. package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
  237. package/src/testing/jest-setup.ts +328 -40
  238. package/src/testing/mock-v86.mjs +49 -0
  239. package/src/ui/a2ui/types.ts +4 -3
  240. package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
  241. package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
  242. package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
  243. package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
  244. package/src/worker/tools/email/email.test.ts +193 -8
  245. package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
  246. package/src/worker/tools/git/git.test.ts +214 -3
  247. package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
  248. package/src/worker/tools/rooms/leave-room.test.ts +40 -0
  249. package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
  250. package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
  251. package/src/worker/tools/ui/ask-user.test.ts +50 -0
  252. package/src/worker/tools/ui/clear-chat.test.ts +19 -0
  253. package/src/worker/tools/ui/get-current-time.test.ts +19 -0
  254. package/src/worker/tools/ui/javascript.test.ts +116 -0
  255. package/src/worker/tools/ui/list-components.test.ts +10 -0
  256. package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
  257. package/src/worker/tools/ui/manage-tools.test.ts +34 -0
  258. package/src/worker/tools/ui/render-component.test.ts +346 -0
  259. package/src/worker/tools/ui/send-notification.test.ts +40 -0
  260. package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
  261. package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
  262. package/src/worker/tools/workspace/attach-file.test.ts +84 -0
  263. package/src/worker/tools/workspace/copy-file.test.ts +69 -0
  264. package/src/worker/tools/workspace/create-directory.test.ts +40 -0
  265. package/src/worker/tools/workspace/delete-file.test.ts +73 -0
  266. package/src/worker/tools/workspace/diff-files.test.ts +94 -0
  267. package/src/worker/tools/workspace/list-files.test.ts +36 -0
  268. package/src/worker/tools/workspace/move-file.test.ts +66 -0
  269. package/src/worker/tools/workspace/open-file.test.ts +57 -0
  270. package/src/worker/tools/workspace/patch-file.test.ts +81 -0
  271. package/src/worker/tools/workspace/read-file.test.ts +116 -0
  272. package/src/worker/tools/workspace/send-file.test.ts +82 -0
  273. package/src/worker/tools/workspace/update-memory.test.ts +29 -0
  274. package/src/worker/tools/workspace/write-file.test.ts +28 -0
  275. package/src/worker/utils/rate-limit.test.ts +199 -0
  276. package/src/worker/utils/tool-state.test.ts +29 -0
  277. package/src/worker/utils/transformers-js-utils.test.ts +59 -0
  278. package/dist/public/config-AL77QAx4.js +0 -1
  279. package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
  280. package/dist/public/file-viewer-CCSABMsE.js +0 -1
  281. package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
  282. package/dist/public/orchestrator-4su7iyWj.js +0 -15
  283. package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
  284. package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
  285. package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
  286. package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
@@ -1,25 +1,187 @@
1
- import { getReasoningConfig, getTransformersStatusUrl } from "./provider.js";
1
+ import { jest } from "@jest/globals";
2
+
3
+ import {
4
+ getApiKeyForHeaders,
5
+ getApiKeyForRequest,
6
+ getLlamafileSettings,
7
+ getMeshLlmSettings,
8
+ getBedrockSettings,
9
+ getAvailableProviders,
10
+ getReasoningConfig,
11
+ getProviderRuntimeHeaders,
12
+ applyLlamafileHeaders,
13
+ applyMeshLlmHeaders,
14
+ getTransformersStatusUrl,
15
+ setAssistantName,
16
+ setBedrockSettings,
17
+ setLlamafileSettings,
18
+ setMeshLlmSettings,
19
+ setModel,
20
+ setPeerjsMyAlias,
21
+ setPeerjsPeerAliases,
22
+ setProvider,
23
+ pollTransformersProgress,
24
+ startTransformersProgressPolling,
25
+ stopTransformersProgressPolling,
26
+ cancelLlamafileRequest,
27
+ } from "./provider.js";
2
28
 
3
29
  import type { OrchestratorState } from "../../orchestrator-state.js";
4
30
 
5
31
  function makeState(overrides: Partial<OrchestratorState> = {}) {
6
32
  return {
7
33
  reasoningEffort: "none",
8
- providerConfig: { baseUrl: "http://api/chat/completions" } as any,
34
+ provider: "openrouter",
35
+ providerConfig: {
36
+ id: "openrouter",
37
+ baseUrl: "http://api/chat/completions",
38
+ } as any,
39
+ llamafileMode: "server",
40
+ llamafileHost: "127.0.0.1",
41
+ llamafilePort: 8080,
42
+ llamafileOffline: false,
43
+ meshLlmHost: "https://public.meshllm.cloud",
44
+ bedrockAuthMode: "provider_chain",
45
+ bedrockProfileFallback: "default",
46
+ bedrockRegionFallback: "us-east-1",
47
+ transformersProgressPollers: new Map(),
9
48
  ...overrides,
10
49
  } as unknown as OrchestratorState;
11
50
  }
12
51
 
13
- describe("provider configuration", () => {
14
- it("getReasoningConfig returns correctly", () => {
52
+ describe("provider operations", () => {
53
+ it("getApiKeyForHeaders and getApiKeyForRequest return correct values", async () => {
54
+ const orchestratorWithKey: any = {
55
+ getApiKey: jest.fn().mockResolvedValue("sk-secret-123" as never),
56
+ };
57
+ expect(await getApiKeyForHeaders(orchestratorWithKey)).toBe(
58
+ "sk-secret-123",
59
+ );
60
+ expect(await getApiKeyForRequest(orchestratorWithKey)).toBe(
61
+ "sk-secret-123",
62
+ );
63
+
64
+ const orchestratorNoKey: any = {
65
+ getApiKey: jest.fn().mockResolvedValue(null as never),
66
+ };
67
+ expect(await getApiKeyForHeaders(orchestratorNoKey)).toBeUndefined();
68
+ expect(await getApiKeyForRequest(orchestratorNoKey)).toBe("");
69
+ });
70
+
71
+ it("retrieves llamafile, meshllm, and bedrock settings", () => {
72
+ const state = makeState();
73
+ expect(getLlamafileSettings(state)).toEqual({
74
+ mode: "server",
75
+ host: "127.0.0.1",
76
+ port: 8080,
77
+ offline: false,
78
+ });
79
+
80
+ expect(getMeshLlmSettings(state)).toEqual({
81
+ host: "https://public.meshllm.cloud",
82
+ });
83
+
84
+ expect(getBedrockSettings(state)).toEqual({
85
+ authMode: "provider_chain",
86
+ profile: "default",
87
+ region: "us-east-1",
88
+ });
89
+ });
90
+
91
+ it("getAvailableProviders returns non-empty list of providers", () => {
92
+ const providers = getAvailableProviders();
93
+ expect(providers.length).toBeGreaterThan(0);
94
+ expect(providers.some((p) => p.id === "openrouter")).toBe(true);
95
+ });
96
+
97
+ it("getReasoningConfig returns effort or undefined", () => {
15
98
  const state1 = makeState({ reasoningEffort: "high" });
16
99
  expect(getReasoningConfig(state1)).toEqual({ effort: "high" });
17
100
 
18
101
  const state2 = makeState({ reasoningEffort: "none" });
19
102
  expect(getReasoningConfig(state2)).toBeUndefined();
103
+
104
+ const state3 = makeState({ reasoningEffort: "" as any });
105
+ expect(getReasoningConfig(state3)).toBeUndefined();
106
+ });
107
+
108
+ it("getProviderRuntimeHeaders returns correct headers for llamafile and bedrock", () => {
109
+ const state = makeState();
110
+
111
+ // Llamafile without overrides
112
+ const llamaHeaders = getProviderRuntimeHeaders(state, "llamafile", "req-1");
113
+ expect(llamaHeaders["x-llamafile-mode"]).toBe("server");
114
+ expect(llamaHeaders["x-llamafile-host"]).toBe("127.0.0.1");
115
+ expect(llamaHeaders["x-shadowclaw-request-id"]).toBe("req-1");
116
+
117
+ // Llamafile with overrides
118
+ const llamaOverrideHeaders = getProviderRuntimeHeaders(
119
+ state,
120
+ "llamafile",
121
+ "",
122
+ {
123
+ llamafile: { mode: "cli", host: "10.0.0.1", port: 9000, offline: true },
124
+ },
125
+ );
126
+ expect(llamaOverrideHeaders["x-llamafile-mode"]).toBe("cli");
127
+ expect(llamaOverrideHeaders["x-llamafile-host"]).toBe("10.0.0.1");
128
+ expect(llamaOverrideHeaders["x-llamafile-port"]).toBe("9000");
129
+ expect(llamaOverrideHeaders["x-llamafile-offline"]).toBe("true");
130
+
131
+ // Bedrock without overrides
132
+ const bedrockHeaders = getProviderRuntimeHeaders(state, "bedrock_proxy");
133
+ expect(bedrockHeaders["x-bedrock-region"]).toBe("us-east-1");
134
+ expect(bedrockHeaders["x-bedrock-profile"]).toBe("default");
135
+ expect(bedrockHeaders["x-bedrock-auth-mode"]).toBe("provider_chain");
136
+
137
+ // Bedrock with overrides
138
+ const bedrockOverrideHeaders = getProviderRuntimeHeaders(
139
+ state,
140
+ "bedrock_proxy",
141
+ "",
142
+ {
143
+ bedrock_proxy: {
144
+ region: "eu-west-1",
145
+ profile: "custom",
146
+ authMode: "sso",
147
+ },
148
+ },
149
+ );
150
+ expect(bedrockOverrideHeaders["x-bedrock-region"]).toBe("eu-west-1");
151
+ expect(bedrockOverrideHeaders["x-bedrock-profile"]).toBe("custom");
152
+ expect(bedrockOverrideHeaders["x-bedrock-auth-mode"]).toBe("sso");
153
+
154
+ // Other provider
155
+ expect(getProviderRuntimeHeaders(state, "openrouter")).toEqual({});
20
156
  });
21
157
 
22
- it("getTransformersStatusUrl computes correctly", () => {
158
+ it("applyLlamafileHeaders and applyMeshLlmHeaders update providerConfig headers", () => {
159
+ const llamaState = makeState({
160
+ providerConfig: { id: "llamafile", headers: {} } as any,
161
+ });
162
+ applyLlamafileHeaders(llamaState);
163
+ expect(llamaState.providerConfig?.headers?.["x-llamafile-mode"]).toBe(
164
+ "server",
165
+ );
166
+
167
+ const meshState = makeState({
168
+ providerConfig: { id: "mesh-llm", headers: {} } as any,
169
+ meshLlmHost: "https://mesh.custom.io",
170
+ });
171
+ applyMeshLlmHeaders(meshState);
172
+ expect(meshState.providerConfig?.headers?.["x-mesh-llm-host"]).toBe(
173
+ "https://mesh.custom.io",
174
+ );
175
+
176
+ // Non-matching providerConfig does not throw
177
+ const otherState = makeState({
178
+ providerConfig: { id: "anthropic" } as any,
179
+ });
180
+ applyLlamafileHeaders(otherState);
181
+ applyMeshLlmHeaders(otherState);
182
+ });
183
+
184
+ it("getTransformersStatusUrl computes status URL correctly", () => {
23
185
  const state1 = makeState({
24
186
  providerConfig: {
25
187
  id: "transformers_js_local",
@@ -28,47 +190,177 @@ describe("provider configuration", () => {
28
190
  });
29
191
  expect(getTransformersStatusUrl(state1)).toBe("http://api/status");
30
192
 
31
- const state2 = makeState({
193
+ const inFlightMap = new Map();
194
+ inFlightMap.set("group-1", {
195
+ providerId: "transformers_js_local",
32
196
  providerConfig: {
33
197
  id: "transformers_js_local",
34
- baseUrl: "http://other",
198
+ baseUrl: "http://custom-host/chat/completions",
35
199
  } as any,
36
200
  });
37
- expect(getTransformersStatusUrl(state2)).toBe(
38
- "http://localhost:8888/transformers-js-proxy/status",
201
+ const state2 = makeState({
202
+ inFlightEffectiveProviderByGroup: inFlightMap,
203
+ });
204
+ expect(getTransformersStatusUrl(state2, "group-1")).toBe(
205
+ "http://custom-host/status",
39
206
  );
40
207
  });
41
208
 
42
- it("getTransformersStatusUrl ignores remote provider like openrouter and falls back to transformers_js_local", () => {
43
- const state = makeState({
44
- providerConfig: {
45
- id: "openrouter",
46
- baseUrl: "https://openrouter.ai/api/v1/chat/completions",
47
- } as any,
48
- });
49
- expect(getTransformersStatusUrl(state)).toBe(
50
- "http://localhost:8888/transformers-js-proxy/status",
209
+ it("async setters update state and call setConfig", async () => {
210
+ const state = makeState();
211
+ const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
212
+
213
+ await setAssistantName(
214
+ state,
215
+ {} as any,
216
+ "NewAssistant",
217
+ mockSetConfig as any,
218
+ );
219
+ expect(state.assistantName).toBe("NewAssistant");
220
+ expect(mockSetConfig).toHaveBeenCalledWith(
221
+ {},
222
+ "assistant_name",
223
+ "NewAssistant",
51
224
  );
225
+
226
+ await setBedrockSettings(
227
+ state,
228
+ {} as any,
229
+ { authMode: "sso", profile: "prof1", region: "us-west-2" },
230
+ mockSetConfig as any,
231
+ );
232
+ expect(state.bedrockRegionFallback).toBe("us-west-2");
233
+ expect(state.bedrockProfileFallback).toBe("prof1");
234
+ expect(state.bedrockAuthMode).toBe("sso");
235
+
236
+ await setLlamafileSettings(
237
+ state,
238
+ {} as any,
239
+ { host: "192.168.1.1", mode: "cli", offline: true, port: 9999 },
240
+ mockSetConfig as any,
241
+ );
242
+ expect(state.llamafileMode).toBe("cli");
243
+ expect(state.llamafileHost).toBe("192.168.1.1");
244
+ expect(state.llamafilePort).toBe(9999);
245
+ expect(state.llamafileOffline).toBe(true);
246
+
247
+ await setMeshLlmSettings(
248
+ state,
249
+ {} as any,
250
+ { host: "https://mesh.new.host" },
251
+ mockSetConfig as any,
252
+ );
253
+ expect(state.meshLlmHost).toBe("https://mesh.new.host");
254
+
255
+ await setModel(state, {} as any, "openrouter/free", mockSetConfig as any);
256
+ expect(state.model).toBe("openrouter/free");
257
+
258
+ await setPeerjsMyAlias(state, {} as any, "my-alias", mockSetConfig as any);
259
+ expect(state.peerjsMyAlias).toBe("my-alias");
260
+
261
+ await setPeerjsPeerAliases(
262
+ state,
263
+ {} as any,
264
+ { peer1: "Alias 1" },
265
+ mockSetConfig as any,
266
+ );
267
+ expect(state.peerjsPeerAliases).toEqual({ peer1: "Alias 1" });
52
268
  });
53
269
 
54
- it("getTransformersStatusUrl uses inFlightEffectiveProviderByGroup if present", () => {
55
- const inFlightMap = new Map();
56
- inFlightMap.set("group-123", {
57
- providerId: "transformers_js_local",
58
- providerConfig: {
59
- id: "transformers_js_local",
60
- baseUrl: "http://custom-local-host/chat/completions",
61
- } as any,
270
+ it("setProvider switches provider, loads key, and updates config", async () => {
271
+ const state = makeState();
272
+ const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
273
+ const mockLoadKey = jest.fn().mockResolvedValue(undefined as never);
274
+ const mockGetKey = jest.fn().mockResolvedValue("key123" as never);
275
+
276
+ await setProvider(
277
+ state,
278
+ {} as any,
279
+ "openrouter",
280
+ {
281
+ loadApiKeyForProvider: mockLoadKey as any,
282
+ getApiKeyForHeaders: mockGetKey as any,
283
+ },
284
+ mockSetConfig as any,
285
+ );
286
+
287
+ expect(state.provider).toBe("openrouter");
288
+ expect(mockLoadKey).toHaveBeenCalledWith({}, "openrouter");
289
+ expect(mockSetConfig).toHaveBeenCalledWith({}, "provider", "openrouter");
290
+ });
291
+
292
+ it("polls transformers progress and emits events", async () => {
293
+ const state = makeState();
294
+ const mockEvents: any = { emit: jest.fn() };
295
+ const mockStopPolling = jest.fn();
296
+
297
+ (globalThis as any).fetch = (jest.fn() as any).mockResolvedValue({
298
+ ok: true,
299
+ json: async () => ({
300
+ progress: 75,
301
+ message: "Downloading weights...",
302
+ status: "running",
303
+ }),
62
304
  });
63
- const state = makeState({
64
- providerConfig: {
65
- id: "openrouter",
66
- baseUrl: "https://openrouter.ai/api/v1/chat/completions",
67
- } as any,
68
- inFlightEffectiveProviderByGroup: inFlightMap,
305
+
306
+ await pollTransformersProgress(
307
+ state,
308
+ mockEvents,
309
+ "group-p",
310
+ mockStopPolling,
311
+ );
312
+
313
+ expect(mockEvents.emit).toHaveBeenCalledWith("model-download-progress", {
314
+ groupId: "group-p",
315
+ message: "Downloading weights...",
316
+ progress: 0.75,
317
+ status: "running",
318
+ });
319
+ expect(mockStopPolling).not.toHaveBeenCalled();
320
+
321
+ // When status is 'done', stops polling
322
+ (globalThis as any).fetch = (jest.fn() as any).mockResolvedValue({
323
+ ok: true,
324
+ json: async () => ({
325
+ progress: 100,
326
+ status: "done",
327
+ }),
69
328
  });
70
- expect(getTransformersStatusUrl(state, "group-123")).toBe(
71
- "http://custom-local-host/status",
329
+
330
+ await pollTransformersProgress(
331
+ state,
332
+ mockEvents,
333
+ "group-p",
334
+ mockStopPolling,
335
+ );
336
+ expect(mockStopPolling).toHaveBeenCalledWith("group-p");
337
+ });
338
+
339
+ it("starts and stops transformers progress polling", () => {
340
+ jest.useFakeTimers();
341
+ const state = makeState();
342
+ const mockEvents: any = { emit: jest.fn() };
343
+
344
+ startTransformersProgressPolling(state, mockEvents, "group-timer");
345
+ expect(state.transformersProgressPollers.has("group-timer")).toBe(true);
346
+
347
+ stopTransformersProgressPolling(state, "group-timer");
348
+ expect(state.transformersProgressPollers.has("group-timer")).toBe(false);
349
+ jest.useRealTimers();
350
+ });
351
+
352
+ it("cancels llamafile request via fetch", async () => {
353
+ (globalThis as any).fetch = (jest.fn() as any).mockResolvedValue({
354
+ ok: true,
355
+ });
356
+
357
+ await cancelLlamafileRequest("req-cancel-1");
358
+ expect(globalThis.fetch).toHaveBeenCalledWith(
359
+ expect.stringContaining("/cancel"),
360
+ expect.objectContaining({
361
+ method: "POST",
362
+ body: JSON.stringify({ requestId: "req-cancel-1" }),
363
+ }),
72
364
  );
73
365
  });
74
366
  });
@@ -1,9 +1,16 @@
1
1
  import { jest } from "@jest/globals";
2
+ import { DEFAULT_GROUP_ID } from "../../../../config/config.js";
2
3
 
3
4
  import {
4
5
  closeTerminalSession,
5
6
  openTerminalSession,
6
7
  sendTerminalInput,
8
+ syncTerminalWorkspace,
9
+ flushTerminalWorkspace,
10
+ answerUserPrompt,
11
+ setVMBootMode,
12
+ setVMBootHost,
13
+ setVMNetworkRelayURL,
7
14
  } from "./vm.js";
8
15
 
9
16
  import type { OrchestratorState } from "../../orchestrator-state.js";
@@ -11,26 +18,39 @@ import type { OrchestratorState } from "../../orchestrator-state.js";
11
18
  function makeState() {
12
19
  return {
13
20
  agentWorker: { postMessage: jest.fn() } as any,
21
+ vmBootMode: "auto",
14
22
  } as unknown as OrchestratorState;
15
23
  }
16
24
 
17
25
  describe("vm operations", () => {
18
- it("openTerminalSession sends open message", () => {
26
+ it("openTerminalSession sends open message with default and custom groupId", () => {
19
27
  const state = makeState();
20
28
  openTerminalSession(state, "group1");
21
29
  expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
22
30
  payload: { groupId: "group1" },
23
31
  type: "vm-terminal-open",
24
32
  });
33
+
34
+ openTerminalSession(state);
35
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
36
+ payload: { groupId: DEFAULT_GROUP_ID },
37
+ type: "vm-terminal-open",
38
+ });
25
39
  });
26
40
 
27
- it("closeTerminalSession sends close message", () => {
41
+ it("closeTerminalSession sends close message with default and custom groupId", () => {
28
42
  const state = makeState();
29
43
  closeTerminalSession(state, "group1");
30
44
  expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
31
45
  payload: { groupId: "group1" },
32
46
  type: "vm-terminal-close",
33
47
  });
48
+
49
+ closeTerminalSession(state);
50
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
51
+ payload: { groupId: DEFAULT_GROUP_ID },
52
+ type: "vm-terminal-close",
53
+ });
34
54
  });
35
55
 
36
56
  it("sendTerminalInput sends input message", () => {
@@ -41,4 +61,96 @@ describe("vm operations", () => {
41
61
  type: "vm-terminal-input",
42
62
  });
43
63
  });
64
+
65
+ it("syncTerminalWorkspace sends sync message", () => {
66
+ const state = makeState();
67
+ syncTerminalWorkspace(state, "group-sync");
68
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
69
+ payload: { groupId: "group-sync" },
70
+ type: "vm-workspace-sync",
71
+ });
72
+ });
73
+
74
+ it("flushTerminalWorkspace sends flush message", () => {
75
+ const state = makeState();
76
+ flushTerminalWorkspace(state, "group-flush");
77
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
78
+ payload: { groupId: "group-flush" },
79
+ type: "vm-workspace-flush",
80
+ });
81
+ });
82
+
83
+ it("answerUserPrompt sends ask-user-response message", () => {
84
+ const state = makeState();
85
+ answerUserPrompt(state, "prompt-1", "user accepted");
86
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
87
+ payload: { id: "prompt-1", response: "user accepted" },
88
+ type: "ask-user-response",
89
+ });
90
+ });
91
+
92
+ it("setVMBootMode sets mode, updates config and notifies worker", async () => {
93
+ const state = makeState();
94
+ const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
95
+
96
+ await setVMBootMode(state, {} as any, "9p", mockSetConfig as any);
97
+ expect(state.vmBootMode).toBe("9p");
98
+ expect(mockSetConfig).toHaveBeenCalledWith({}, "vm_boot_mode", "9p");
99
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
100
+ payload: { mode: "9p" },
101
+ type: "set-vm-mode",
102
+ });
103
+
104
+ // Test invalid mode normalisation to 'disabled'
105
+ await setVMBootMode(
106
+ state,
107
+ {} as any,
108
+ "invalid-mode" as any,
109
+ mockSetConfig as any,
110
+ );
111
+ expect(state.vmBootMode).toBe("disabled");
112
+ expect(mockSetConfig).toHaveBeenCalledWith({}, "vm_boot_mode", "disabled");
113
+ });
114
+
115
+ it("setVMBootHost sets boot host, updates config and notifies worker", async () => {
116
+ const state = makeState();
117
+ const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
118
+
119
+ await setVMBootHost(
120
+ state,
121
+ {} as any,
122
+ " https://v86.example.com ",
123
+ mockSetConfig as any,
124
+ );
125
+ expect(mockSetConfig).toHaveBeenCalledWith(
126
+ {},
127
+ "vm_boot_host",
128
+ "https://v86.example.com",
129
+ );
130
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
131
+ payload: { bootHost: "https://v86.example.com" },
132
+ type: "set-vm-mode",
133
+ });
134
+ });
135
+
136
+ it("setVMNetworkRelayURL sets relay URL, updates config and notifies worker", async () => {
137
+ const state = makeState();
138
+ const mockSetConfig = jest.fn().mockResolvedValue(undefined as never);
139
+
140
+ await setVMNetworkRelayURL(
141
+ state,
142
+ {} as any,
143
+ " wss://relay.example.com ",
144
+ mockSetConfig as any,
145
+ );
146
+ expect(mockSetConfig).toHaveBeenCalledWith(
147
+ {},
148
+ "vm_network_relay_url",
149
+ "wss://relay.example.com",
150
+ );
151
+ expect(state.agentWorker?.postMessage).toHaveBeenCalledWith({
152
+ payload: { networkRelayUrl: "wss://relay.example.com" },
153
+ type: "set-vm-mode",
154
+ });
155
+ });
44
156
  });
@@ -0,0 +1,60 @@
1
+ import { jest } from "@jest/globals";
2
+ import { syncProxyConfigToServiceWorker } from "./syncProxyConfigToServiceWorker.js";
3
+
4
+ describe("syncProxyConfigToServiceWorker", () => {
5
+ let originalServiceWorker: any;
6
+
7
+ beforeEach(() => {
8
+ originalServiceWorker = (navigator as any).serviceWorker;
9
+ });
10
+
11
+ afterEach(() => {
12
+ Object.defineProperty(navigator, "serviceWorker", {
13
+ value: originalServiceWorker,
14
+ configurable: true,
15
+ writable: true,
16
+ });
17
+ });
18
+
19
+ it("posts proxy config to active service worker controller", () => {
20
+ const mockPostMessage = jest.fn();
21
+ Object.defineProperty(navigator, "serviceWorker", {
22
+ value: {
23
+ controller: {
24
+ postMessage: mockPostMessage,
25
+ },
26
+ },
27
+ configurable: true,
28
+ writable: true,
29
+ });
30
+
31
+ syncProxyConfigToServiceWorker({
32
+ useProxy: true,
33
+ proxyUrl: "http://localhost:8888/proxy",
34
+ });
35
+
36
+ expect(mockPostMessage).toHaveBeenCalledWith({
37
+ type: "set-proxy-config",
38
+ payload: {
39
+ useProxy: true,
40
+ proxyUrl: "http://localhost:8888/proxy",
41
+ },
42
+ });
43
+ });
44
+
45
+ it("does nothing when service worker controller is missing", () => {
46
+ Object.defineProperty(navigator, "serviceWorker", {
47
+ value: {
48
+ controller: null,
49
+ },
50
+ configurable: true,
51
+ writable: true,
52
+ });
53
+
54
+ // Should not throw
55
+ syncProxyConfigToServiceWorker({
56
+ useProxy: false,
57
+ proxyUrl: "",
58
+ });
59
+ });
60
+ });