shadow-claw 1.23.4 → 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 (255) hide show
  1. package/README.md +42 -5
  2. package/bin/cli.mjs +166 -0
  3. package/bin/cli.test.mjs +60 -0
  4. package/bin/commands/backup.mjs +70 -0
  5. package/bin/commands/clients.mjs +41 -0
  6. package/bin/commands/peer-id.mjs +76 -0
  7. package/bin/commands/peer-id.test.mjs +104 -0
  8. package/bin/commands/send.mjs +51 -0
  9. package/bin/commands/tasks.mjs +59 -0
  10. package/bin/commands/webrtc-listen.mjs +254 -0
  11. package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
  12. package/bin/rolldown.config.test.mjs +401 -8
  13. package/bin/utils/control-client.mjs +243 -0
  14. package/bin/utils/control-client.test.mjs +83 -0
  15. package/bin/utils/ulid.mjs +45 -0
  16. package/bin/utils/webrtc-control-client.mjs +699 -0
  17. package/bin/utils/webrtc-control-client.test.mjs +318 -0
  18. package/dist/electron/main.cjs +1 -1
  19. package/dist/public/AGENTS.md +5 -3
  20. package/dist/public/README.md +42 -5
  21. package/dist/public/agent.worker.js +1 -1
  22. package/dist/public/backup-controller-C_s9in2t.js +1 -0
  23. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
  24. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
  25. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
  26. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  27. package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
  28. package/dist/public/config-CrHchneq.js +1 -0
  29. package/dist/public/{configurePeerJs-B4Ym9VMj.js → configurePeerJs-CxpZeDJJ.js} +1 -1
  30. package/dist/public/{connections-BEn6JYdu.js → connections-DvbgdkaB.js} +1 -1
  31. package/dist/public/{custom-element-security-DdYlUozq.js → custom-element-security-NCqQ5lmC.js} +1 -1
  32. package/dist/public/docs/README.md +3 -1
  33. package/dist/public/docs/architecture/overview.md +12 -12
  34. package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
  35. package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +2 -2
  36. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  37. package/dist/public/docs/example/article/index.html +1 -1
  38. package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
  39. package/dist/public/docs/guides/server-development-configuration.md +24 -0
  40. package/dist/public/docs/publishing/index.html +1 -1
  41. package/dist/public/docs/skill-creator/index.html +1 -1
  42. package/dist/public/docs/subsystems/backup.md +56 -0
  43. package/dist/public/docs/subsystems/cli.md +124 -0
  44. package/dist/public/docs/subsystems/control-plane.md +158 -0
  45. package/dist/public/docs/subsystems/notifications.md +54 -2
  46. package/dist/public/docs/subsystems/reactive-ui.md +1 -0
  47. package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
  48. package/dist/public/e2e/README.md +5 -1
  49. package/dist/public/{e2e-bridge-BQ4KzUt5.js → e2e-bridge-IGWsRl4i.js} +1 -1
  50. package/dist/public/file-viewer-D0eflgt6.js +1 -0
  51. package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
  52. package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
  53. package/dist/public/{git-DEUvQTUA.js → git-7Vq_SJSp.js} +2 -2
  54. package/dist/public/{iframe-sanitizer-0NS4XLOd.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
  55. package/dist/public/index.html +1 -1
  56. package/dist/public/index.js +1 -1
  57. package/dist/public/{initChatSplitResize-llGkSJXg.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
  58. package/dist/public/initControlPlane-C9jpnWev.js +1 -0
  59. package/dist/public/llms.txt +1 -1
  60. package/dist/public/main/index.html +1 -1
  61. package/dist/public/main/memory/index.html +1 -1
  62. package/dist/public/{markdown-CyYR4App.js → markdown-BnpKxSxl.js} +1 -1
  63. package/dist/public/{mcp-reconnect-CSx4aMwy.js → mcp-reconnect-9emsqGRe.js} +1 -1
  64. package/dist/public/{memoryStorage-Dwg8NhAZ.js → memoryStorage-C0KvLNUp.js} +1 -1
  65. package/dist/public/{orchestrator-DRfEudXk.js → orchestrator-3h9xxHUm.js} +676 -676
  66. package/dist/public/orchestrator-nJj59RqD.js +15 -0
  67. package/dist/public/{peerjs-CurBuKvk.js → peerjs-BP4laMXj.js} +1 -1
  68. package/dist/public/{push-client-Dii-vPEh.js → push-client-CrjEXK3U.js} +1 -1
  69. package/dist/public/service-worker/push-handler.js +1 -1
  70. package/dist/public/service-worker.js +1 -1
  71. package/dist/public/{shadow-claw-5IHaDDMJ.js → shadow-claw-CbwETBWf.js} +2 -2
  72. package/dist/public/{shadow-claw-accounts-CZvz4iQz.js → shadow-claw-accounts-B4sBTQEj.js} +1 -1
  73. package/dist/public/{shadow-claw-channels-BKXFyRS3.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
  74. package/dist/public/{shadow-claw-chat-CCLuhWdI.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
  75. package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
  76. package/dist/public/{shadow-claw-conversations-DV-87UUh.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
  77. package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
  78. package/dist/public/{shadow-claw-file-viewer-Dk2dbAUd.js → shadow-claw-file-viewer-Blf6yGVD.js} +1 -1
  79. package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
  80. package/dist/public/{shadow-claw-git-DRA7_qck.js → shadow-claw-git-v8S0JjuR.js} +2 -2
  81. package/dist/public/{shadow-claw-integrations-1Xu5snuV.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
  82. package/dist/public/{shadow-claw-llm-6Aci1BDP.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
  83. package/dist/public/{shadow-claw-mcp-remote-BUjAeVXC.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
  84. package/dist/public/{shadow-claw-networking-FQe4nCp3.js → shadow-claw-networking-qHbP95z9.js} +1 -1
  85. package/dist/public/{shadow-claw-notifications-DV2wibL3.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
  86. package/dist/public/{shadow-claw-page-header-B63S4HTU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
  87. package/dist/public/{shadow-claw-pages-DE3jWj5K.js → shadow-claw-pages-DFZHhzFZ.js} +1 -1
  88. package/dist/public/{shadow-claw-settings-S6DAKfYx.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
  89. package/dist/public/{shadow-claw-storage-DaacA3_K.js → shadow-claw-storage-BFynbP0j.js} +2 -2
  90. package/dist/public/{shadow-claw-task-server-IjKX042p.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
  91. package/dist/public/{shadow-claw-tasks-Cy0JYmGU.js → shadow-claw-tasks-Caxc9atL.js} +1 -1
  92. package/dist/public/{shadow-claw-terminal-znAt7HyP.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
  93. package/dist/public/{shadow-claw-tools-Cutv9Si5.js → shadow-claw-tools-BfJx3A9W.js} +1 -1
  94. package/dist/public/{shadow-claw-webvm-BH8mPDEu.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
  95. package/dist/public/{syncWebMcpRegistration-BqJxaMGR.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
  96. package/dist/server.js +286 -189
  97. package/package.json +2 -1
  98. package/src/__mocks__/jszip.cjs +15 -5
  99. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
  100. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
  101. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
  102. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
  103. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
  104. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
  105. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
  106. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
  107. package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
  108. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
  109. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
  110. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
  111. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
  112. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +76 -0
  113. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
  114. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  115. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
  116. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
  117. package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
  118. package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
  119. package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
  120. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
  121. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
  122. package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
  123. package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
  124. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
  125. package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
  126. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +273 -0
  127. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +408 -1
  128. package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
  129. package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
  130. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
  131. package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
  132. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
  133. package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
  134. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
  135. package/src/config/config.ts +10 -0
  136. package/src/core/backup-controller.test.ts +101 -0
  137. package/src/core/backup-controller.ts +137 -0
  138. package/src/core/control-plane-client.test.ts +408 -0
  139. package/src/core/control-plane-client.ts +425 -0
  140. package/src/core/orchestrator/orchestrator.ts +4 -0
  141. package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
  142. package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
  143. package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
  144. package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
  145. package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
  146. package/src/core/utils/initControlPlane.test.ts +276 -0
  147. package/src/core/utils/initControlPlane.ts +437 -0
  148. package/src/core/utils/initializeApp.ts +18 -0
  149. package/src/db/getSession.test.ts +13 -1
  150. package/src/server/app.test.ts +12 -0
  151. package/src/server/app.ts +18 -8
  152. package/src/server/client-registry.test.ts +250 -0
  153. package/src/server/client-registry.ts +477 -0
  154. package/src/server/config.ts +15 -0
  155. package/src/server/control-plane-types.ts +120 -0
  156. package/src/server/control-plane.test.ts +472 -0
  157. package/src/server/control-plane.ts +581 -0
  158. package/src/server/routes/backup.test.ts +263 -0
  159. package/src/server/routes/backup.ts +262 -0
  160. package/src/server/routes/integrations-email.test.ts +532 -0
  161. package/src/server/routes/llamafile.test.ts +172 -48
  162. package/src/server/routes/mesh-llm.test.ts +192 -0
  163. package/src/server/routes/ollama.test.ts +49 -0
  164. package/src/server/server-peer.test.ts +86 -0
  165. package/src/server/server-peer.ts +330 -0
  166. package/src/server/server.ts +35 -0
  167. package/src/server/services/llamafile-manager.test.ts +230 -6
  168. package/src/server/services/transformers-runtime.test.ts +186 -18
  169. package/src/server/webrtc.test.ts +21 -0
  170. package/src/server/webrtc.ts +72 -0
  171. package/src/service-worker/fetch-proxy.test.ts +64 -1
  172. package/src/service-worker/push-handler.ts +39 -0
  173. package/src/shell/vm.test.ts +744 -12
  174. package/src/storage/ensureMainGroupIndex.test.ts +153 -0
  175. package/src/storage/getGroupFile.test.ts +66 -0
  176. package/src/storage/memoryStorage.test.ts +82 -2
  177. package/src/storage/writeGroupFileBytes.test.ts +143 -0
  178. package/src/stores/chat-ui.test.ts +20 -0
  179. package/src/stores/orchestrator.test.ts +134 -0
  180. package/src/stores/toast.test.ts +50 -0
  181. package/src/stores/tools.test.ts +147 -10
  182. package/src/subsystems/channels/imessage.test.ts +59 -0
  183. package/src/subsystems/channels/peerjs.test.ts +500 -62
  184. package/src/subsystems/channels/peerjs.ts +71 -4
  185. package/src/subsystems/channels/telegram.test.ts +177 -0
  186. package/src/subsystems/email/catalog.test.ts +1 -0
  187. package/src/subsystems/email/connections.test.ts +39 -1
  188. package/src/subsystems/git/credentials.test.ts +160 -825
  189. package/src/subsystems/git/git.test.ts +463 -0
  190. package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
  191. package/src/subsystems/notifications/push-routes.test.ts +33 -0
  192. package/src/subsystems/notifications/push-routes.ts +23 -0
  193. package/src/subsystems/notifications/push-store.ts +1 -1
  194. package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
  195. package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
  196. package/src/subsystems/providers/model-registry.test.ts +115 -0
  197. package/src/subsystems/providers/providers.test.ts +94 -1
  198. package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
  199. package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
  200. package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
  201. package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
  202. package/src/subsystems/skills/activateSkill.test.ts +101 -0
  203. package/src/subsystems/skills/discoverSkills.test.ts +251 -0
  204. package/src/subsystems/tools/cron.test.ts +64 -0
  205. package/src/subsystems/tools/declarative.test.ts +255 -89
  206. package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
  207. package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
  208. package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
  209. package/src/testing/jest-setup.ts +328 -40
  210. package/src/testing/mock-v86.mjs +49 -0
  211. package/src/ui/a2ui/types.ts +4 -3
  212. package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
  213. package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
  214. package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
  215. package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
  216. package/src/worker/tools/email/email.test.ts +193 -8
  217. package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
  218. package/src/worker/tools/git/git.test.ts +214 -3
  219. package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
  220. package/src/worker/tools/rooms/leave-room.test.ts +40 -0
  221. package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
  222. package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
  223. package/src/worker/tools/ui/ask-user.test.ts +50 -0
  224. package/src/worker/tools/ui/clear-chat.test.ts +19 -0
  225. package/src/worker/tools/ui/get-current-time.test.ts +19 -0
  226. package/src/worker/tools/ui/javascript.test.ts +116 -0
  227. package/src/worker/tools/ui/list-components.test.ts +10 -0
  228. package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
  229. package/src/worker/tools/ui/manage-tools.test.ts +34 -0
  230. package/src/worker/tools/ui/render-component.test.ts +346 -0
  231. package/src/worker/tools/ui/send-notification.test.ts +40 -0
  232. package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
  233. package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
  234. package/src/worker/tools/workspace/attach-file.test.ts +84 -0
  235. package/src/worker/tools/workspace/copy-file.test.ts +69 -0
  236. package/src/worker/tools/workspace/create-directory.test.ts +40 -0
  237. package/src/worker/tools/workspace/delete-file.test.ts +73 -0
  238. package/src/worker/tools/workspace/diff-files.test.ts +94 -0
  239. package/src/worker/tools/workspace/list-files.test.ts +36 -0
  240. package/src/worker/tools/workspace/move-file.test.ts +66 -0
  241. package/src/worker/tools/workspace/open-file.test.ts +57 -0
  242. package/src/worker/tools/workspace/patch-file.test.ts +81 -0
  243. package/src/worker/tools/workspace/read-file.test.ts +116 -0
  244. package/src/worker/tools/workspace/send-file.test.ts +82 -0
  245. package/src/worker/tools/workspace/update-memory.test.ts +29 -0
  246. package/src/worker/tools/workspace/write-file.test.ts +28 -0
  247. package/src/worker/utils/rate-limit.test.ts +199 -0
  248. package/src/worker/utils/tool-state.test.ts +29 -0
  249. package/src/worker/utils/transformers-js-utils.test.ts +59 -0
  250. package/dist/public/config-BYAuxx3j.js +0 -1
  251. package/dist/public/downloadGroupFile-Drdvg1Ev.js +0 -1
  252. package/dist/public/file-viewer-CcZYw-PS.js +0 -1
  253. package/dist/public/orchestrator-o9Fj67BN.js +0 -15
  254. package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
  255. package/dist/public/shadow-claw-files-BpfpJIuP.js +0 -840
@@ -67,4 +67,54 @@ describe("ToastStore", () => {
67
67
 
68
68
  expect(store.toasts).toHaveLength(0);
69
69
  });
70
+
71
+ test("clear removes all toasts and timers", () => {
72
+ const store = new ToastStore();
73
+ store.show("toast 1");
74
+ store.show("toast 2");
75
+ expect(store.toasts).toHaveLength(2);
76
+
77
+ store.clear();
78
+ expect(store.toasts).toHaveLength(0);
79
+ });
80
+
81
+ test("caps visible toasts at MAX_VISIBLE_TOASTS", () => {
82
+ const store = new ToastStore();
83
+ for (let i = 1; i <= 7; i++) {
84
+ store.show(`toast ${i}`);
85
+ }
86
+
87
+ expect(store.toasts).toHaveLength(5);
88
+ expect(store.toasts[0].message).toBe("toast 3");
89
+ expect(store.toasts[4].message).toBe("toast 7");
90
+ });
91
+
92
+ test("executes action via runAction", async () => {
93
+ const store = new ToastStore();
94
+ const actionSpy: any = jest.fn();
95
+ const id = store.show("with action", {
96
+ action: { label: "Retry", onClick: actionSpy },
97
+ });
98
+
99
+ await store.runAction(id);
100
+ expect(actionSpy).toHaveBeenCalledTimes(1);
101
+
102
+ // No-op for non-existent or action-less toast
103
+ await store.runAction(999);
104
+ });
105
+
106
+ test("resolvers handle invalid inputs gracefully", () => {
107
+ const store = new ToastStore();
108
+ expect(store.resolveType("invalid" as any)).toBe("info");
109
+ expect(store.resolveType("error")).toBe("error");
110
+
111
+ expect(store.resolveDuration(-10)).toBe(4000);
112
+ expect(store.resolveDuration(NaN)).toBe(4000);
113
+ expect(store.resolveDuration(2500)).toBe(2500);
114
+
115
+ expect(
116
+ store.resolveAction({ label: "", onClick: "not-fn" } as any),
117
+ ).toBeUndefined();
118
+ expect(store.resolveAction(undefined)).toBeUndefined();
119
+ });
70
120
  });
@@ -2,8 +2,11 @@ import { jest } from "@jest/globals";
2
2
 
3
3
  const mockGetConfig = jest.fn();
4
4
  const mockSetConfig = jest.fn();
5
+ const mockLoadDeclarativeTools = jest.fn();
6
+ const mockFindDeclarativeTool = jest.fn();
7
+ const mockParseDeclarativeTool = jest.fn();
5
8
 
6
- jest.unstable_mockModule("../db/setConfig.js", () => ({
9
+ jest.unstable_mockModule("../db/getConfig.js", () => ({
7
10
  getConfig: mockGetConfig,
8
11
  }));
9
12
 
@@ -11,20 +14,24 @@ jest.unstable_mockModule("../db/setConfig.js", () => ({
11
14
  setConfig: mockSetConfig,
12
15
  }));
13
16
 
17
+ jest.unstable_mockModule("../subsystems/tools/declarative.js", () => ({
18
+ loadDeclarativeTools: mockLoadDeclarativeTools,
19
+ findDeclarativeTool: mockFindDeclarativeTool,
20
+ parseDeclarativeTool: mockParseDeclarativeTool,
21
+ }));
22
+
14
23
  const { ToolsStore } = await import("./tools.js");
15
24
  const { DEFAULT_BUILTIN_PROFILE } =
16
- await import("../subsystems/providers/prompt-api-provider.js");
25
+ await import("../subsystems/tools/builtin-profiles.js");
17
26
 
18
27
  const db: any = {} as any;
19
28
 
20
29
  describe("ToolsStore — built-in Nano profile", () => {
21
- let store;
30
+ let store: any;
22
31
 
23
32
  beforeEach(() => {
24
33
  jest.clearAllMocks();
25
-
26
34
  (mockGetConfig as any).mockResolvedValue(undefined);
27
-
28
35
  (mockSetConfig as any).mockResolvedValue(undefined);
29
36
  store = new ToolsStore();
30
37
  });
@@ -53,22 +60,22 @@ describe("ToolsStore — built-in Nano profile", () => {
53
60
 
54
61
  it("refuses to delete the built-in profile", async () => {
55
62
  await store.deleteProfile(db, DEFAULT_BUILTIN_PROFILE.id);
56
- // Should still be in the list
57
63
  expect(
58
- store.profiles.find((p) => p.id === DEFAULT_BUILTIN_PROFILE.id),
64
+ store.profiles.find((p: any) => p.id === DEFAULT_BUILTIN_PROFILE.id),
59
65
  ).toBeTruthy();
60
- // setConfig should NOT have been called
61
66
  expect(mockSetConfig).not.toHaveBeenCalled();
62
67
  });
63
68
 
64
69
  it("findProfilesForProvider returns built-in for prompt_api", () => {
65
70
  const matches = store.findProfilesForProvider("prompt_api");
66
- expect(matches.some((p) => p.id === DEFAULT_BUILTIN_PROFILE.id)).toBe(true);
71
+ expect(matches.some((p: any) => p.id === DEFAULT_BUILTIN_PROFILE.id)).toBe(
72
+ true,
73
+ );
67
74
  });
68
75
 
69
76
  it("does not return built-in for non-matching provider", () => {
70
77
  const matches = store.findProfilesForProvider("openai");
71
- expect(matches.some((p) => p.id === DEFAULT_BUILTIN_PROFILE.id)).toBe(
78
+ expect(matches.some((p: any) => p.id === DEFAULT_BUILTIN_PROFILE.id)).toBe(
72
79
  false,
73
80
  );
74
81
  });
@@ -93,6 +100,119 @@ describe("ToolsStore — built-in Nano profile", () => {
93
100
  });
94
101
  });
95
102
 
103
+ describe("ToolsStore — custom tools, cloning, profiles & web search config", () => {
104
+ let store: any;
105
+
106
+ beforeEach(() => {
107
+ jest.clearAllMocks();
108
+ (mockGetConfig as any).mockResolvedValue(undefined);
109
+ (mockSetConfig as any).mockResolvedValue(undefined);
110
+ store = new ToolsStore();
111
+ });
112
+
113
+ it("adds, removes, and clones custom tools", async () => {
114
+ const customTool = {
115
+ name: "custom_math",
116
+ description: "Perform math",
117
+ input_schema: { type: "object", properties: {} },
118
+ };
119
+
120
+ await store.addCustomTool(db, customTool);
121
+ expect(store.customTools).toHaveLength(1);
122
+ expect(store.enabledToolNames.has("custom_math")).toBe(true);
123
+
124
+ const cloned = await store.cloneTool(
125
+ db,
126
+ "custom_math",
127
+ "custom_math_v2",
128
+ "Perform advanced math",
129
+ );
130
+ expect(cloned).toBe(true);
131
+ expect(store.customTools).toHaveLength(2);
132
+
133
+ // Cannot clone with existing name
134
+ const cloneConflict = await store.cloneTool(
135
+ db,
136
+ "custom_math",
137
+ "custom_math_v2",
138
+ );
139
+ expect(cloneConflict).toBe(false);
140
+
141
+ // Remove custom tool
142
+ await store.removeCustomTool(db, "custom_math");
143
+ expect(store.customTools).toHaveLength(1);
144
+ expect(store.customTools[0].name).toBe("custom_math_v2");
145
+ });
146
+
147
+ it("manages custom tool profiles and updates", async () => {
148
+ const profile = {
149
+ id: "p1",
150
+ name: "Research Profile",
151
+ enabledToolNames: ["web_search", "read_file"],
152
+ customTools: [],
153
+ systemPromptOverride: "You are a researcher",
154
+ };
155
+
156
+ await store.addProfile(db, profile);
157
+ expect(store.profiles.find((p: any) => p.id === "p1")).toBeTruthy();
158
+
159
+ await store.activateProfile(db, "p1");
160
+ expect(store.activeProfileId).toBe("p1");
161
+ expect(store.systemPromptOverride).toBe("You are a researcher");
162
+
163
+ await store.setSystemPromptOverride(db, "Updated Prompt");
164
+ expect(store.systemPromptOverride).toBe("Updated Prompt");
165
+
166
+ await store.saveToActiveProfile(db);
167
+ expect(mockSetConfig).toHaveBeenCalled();
168
+
169
+ await store.deactivateProfile(db);
170
+ expect(store.activeProfileId).toBeNull();
171
+
172
+ await store.deleteProfile(db, "p1");
173
+ expect(store.profiles.find((p: any) => p.id === "p1")).toBeFalsy();
174
+ });
175
+
176
+ it("configures web search and search_files options", async () => {
177
+ await store.setWebSearchUseProxy(db, true);
178
+ expect(store.webSearchUseProxy).toBe(true);
179
+
180
+ await store.setWebSearchProxyUrl(db, "https://proxy.example.com");
181
+ expect(store.webSearchProxyUrl).toBe("https://proxy.example.com");
182
+
183
+ await store.setWebSearchUrl(db, "https://search.example.com?q={query}");
184
+ expect(store.webSearchUrl).toBe("https://search.example.com?q={query}");
185
+
186
+ await store.setSearchFilesMaxFileBytes(db, 1024 * 1024);
187
+ expect(store.searchFilesMaxFileBytes).toBe(1024 * 1024);
188
+
189
+ await store.setSearchFilesMaxFilesVisited(db, 2000);
190
+ expect(store.searchFilesMaxFilesVisited).toBe(2000);
191
+
192
+ await store.setSearchFilesSkipDirs(db, "dist,out,.git");
193
+ expect(store.searchFilesSkipDirs).toBe("dist,out,.git");
194
+ expect(store.searchFilesSkipDirsSet).toEqual(
195
+ new Set(["dist", "out", ".git"]),
196
+ );
197
+ });
198
+
199
+ it("loads config from DB on load()", async () => {
200
+ (mockGetConfig as any).mockImplementation(async (_db: any, key: string) => {
201
+ if (key === "enabled_tools") return ["read_file"];
202
+ if (key === "system_prompt_override") return "Loaded Prompt";
203
+ if (key === "web_search_use_proxy") return "true";
204
+ if (key === "search_files_max_file_bytes") return "2048";
205
+ return null;
206
+ });
207
+
208
+ await store.load(db);
209
+ expect(store.enabledToolNames.has("read_file")).toBe(true);
210
+ expect(store.systemPromptOverride).toBe("Loaded Prompt");
211
+ expect(store.webSearchUseProxy).toBe(true);
212
+ expect(store.searchFilesMaxFileBytes).toBe(2048);
213
+ });
214
+ });
215
+
96
216
  describe("ToolsStore — declarative tools management", () => {
97
217
  let store: any;
98
218
 
@@ -149,4 +269,21 @@ describe("ToolsStore — declarative tools management", () => {
149
269
  expect(newStore.isDeclarativeToolEnabled("tool_x")).toBe(true);
150
270
  expect(newStore.isDeclarativeToolEnabled("tool_y")).toBe(false);
151
271
  });
272
+
273
+ it("refreshes declarative tools from loader", async () => {
274
+ (mockLoadDeclarativeTools as any).mockResolvedValueOnce({
275
+ tools: [
276
+ {
277
+ name: "custom_decl_tool",
278
+ description: "Declarative tool desc",
279
+ execution: { type: "javascript", script: "return 1;" },
280
+ },
281
+ ],
282
+ });
283
+
284
+ const refreshed = await store.refreshDeclarativeTools(db, "br:main");
285
+ expect(refreshed).toHaveLength(1);
286
+ expect(store.declarativeTools).toHaveLength(1);
287
+ expect(store.enabledDeclarativeTools).toHaveLength(1);
288
+ });
152
289
  });
@@ -232,4 +232,63 @@ describe("IMessageChannel", () => {
232
232
  expect(legacySend).toBeDefined();
233
233
  expect(legacySend?.init.body instanceof FormData).toBe(true);
234
234
  });
235
+
236
+ it("handles start, stop, and polling cursor updates", async () => {
237
+ const channel: any = new IMessageChannel();
238
+ channel.configure("https://bridge.example", "secret", ["chat-1"]);
239
+
240
+ const seen: any[] = [];
241
+ channel.onMessage((msg: any) => seen.push(msg));
242
+
243
+ jest.spyOn(channel, "requestJson").mockImplementation(async () => {
244
+ channel.stop();
245
+ return {
246
+ nextCursor: "cursor-1",
247
+ messages: [
248
+ {
249
+ id: "msg-1",
250
+ conversationId: "chat-1",
251
+ sender: "User",
252
+ body: "Polled message",
253
+ },
254
+ ],
255
+ };
256
+ });
257
+
258
+ channel.start();
259
+ await new Promise((r) => setTimeout(r, 20));
260
+
261
+ expect(channel.cursor).toBe("cursor-1");
262
+ expect(seen).toHaveLength(1);
263
+ expect(seen[0].content).toBe("Polled message");
264
+ expect(channel.running).toBe(false);
265
+ });
266
+
267
+ it("sets typing indicators when typing is true", () => {
268
+ const channel = new IMessageChannel();
269
+ channel.configure("https://bridge.example", "secret", []);
270
+ const requestJsonSpy = jest
271
+ .spyOn(channel, "requestJson")
272
+ .mockResolvedValue({ ok: true } as any);
273
+
274
+ channel.setTyping("im:chat-xyz", true);
275
+ expect(requestJsonSpy).toHaveBeenCalledWith("/messages/typing", {
276
+ method: "POST",
277
+ body: JSON.stringify({ chatId: "chat-xyz", typing: true }),
278
+ });
279
+
280
+ channel.setTyping("im:chat-xyz", false);
281
+ expect(requestJsonSpy).toHaveBeenCalledTimes(1);
282
+ });
283
+
284
+ it("tests connection against /v1/info or /messages", async () => {
285
+ const channel = new IMessageChannel();
286
+ channel.configure("https://bridge.example", "secret", []);
287
+
288
+ jest
289
+ .spyOn(channel, "requestJson")
290
+ .mockResolvedValueOnce({ ok: true } as any);
291
+ const mode = await channel.ensureApiMode();
292
+ expect(mode).toBe("beeper-desktop");
293
+ });
235
294
  });