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
@@ -0,0 +1,64 @@
1
+ import { matchesCron } from "./cron.js";
2
+
3
+ describe("matchesCron", () => {
4
+ it("returns false for invalid or non-5-part expressions", () => {
5
+ expect(matchesCron("", new Date())).toBe(false);
6
+ expect(matchesCron(null as any, new Date())).toBe(false);
7
+ expect(matchesCron("* * *", new Date())).toBe(false);
8
+ expect(matchesCron("* * * * * *", new Date())).toBe(false);
9
+ });
10
+
11
+ it("matches wildcard expression", () => {
12
+ expect(matchesCron("* * * * *", new Date())).toBe(true);
13
+ });
14
+
15
+ it("matches exact numbers", () => {
16
+ // 2026-08-30 14:15:00, Sunday (day 0), August (month 8), day of month 30
17
+ const testDate = new Date(2026, 7, 30, 14, 15, 0); // Month is 0-indexed in JS Date: 7 = August
18
+
19
+ expect(matchesCron("15 14 30 8 0", testDate)).toBe(true);
20
+ expect(matchesCron("16 14 30 8 0", testDate)).toBe(false);
21
+ });
22
+
23
+ it("matches lists of numbers", () => {
24
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
25
+
26
+ expect(matchesCron("10,15,20 * * * *", testDate)).toBe(true);
27
+ expect(matchesCron("10,20,30 * * * *", testDate)).toBe(false);
28
+ });
29
+
30
+ it("matches ranges (N-M)", () => {
31
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
32
+
33
+ expect(matchesCron("10-20 * * * *", testDate)).toBe(true);
34
+ expect(matchesCron("20-30 * * * *", testDate)).toBe(false);
35
+ });
36
+
37
+ it("matches steps (*/N)", () => {
38
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
39
+
40
+ expect(matchesCron("*/5 * * * *", testDate)).toBe(true);
41
+ expect(matchesCron("*/7 * * * *", testDate)).toBe(false);
42
+ });
43
+
44
+ it("matches range with step (N-M/S)", () => {
45
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
46
+
47
+ expect(matchesCron("10-20/5 * * * *", testDate)).toBe(true); // 10, 15, 20 -> 15 matches
48
+ expect(matchesCron("10-20/4 * * * *", testDate)).toBe(false); // 10, 14, 18
49
+ });
50
+
51
+ it("matches start with step (N/S)", () => {
52
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
53
+
54
+ expect(matchesCron("5/10 * * * *", testDate)).toBe(true); // 5, 15, 25... -> 15 matches
55
+ expect(matchesCron("6/10 * * * *", testDate)).toBe(false);
56
+ });
57
+
58
+ it("handles invalid step strings", () => {
59
+ const testDate = new Date(2026, 7, 30, 14, 15, 0);
60
+
61
+ expect(matchesCron("*/0 * * * *", testDate)).toBe(false);
62
+ expect(matchesCron("*/abc * * * *", testDate)).toBe(false);
63
+ });
64
+ });
@@ -1,11 +1,45 @@
1
- import { describe, expect, it } from "@jest/globals";
1
+ import { describe, expect, it, jest, beforeEach } from "@jest/globals";
2
2
 
3
- import { parseDeclarativeTool } from "./declarative.js";
3
+ jest.unstable_mockModule("../../db/getConfig.js", () => ({
4
+ getConfig: jest.fn(),
5
+ }));
4
6
 
5
- describe("parseDeclarativeTool", () => {
6
- it("parses a sandboxed javascript tool definition", () => {
7
- expect(
8
- parseDeclarativeTool(".agents/tools/main/echo.json", {
7
+ jest.unstable_mockModule("../../storage/getGroupDir.js", () => ({
8
+ getGroupDir: jest.fn(),
9
+ }));
10
+
11
+ jest.unstable_mockModule("../../storage/readGroupFile.js", () => ({
12
+ readGroupFile: jest.fn(),
13
+ }));
14
+
15
+ const { parseDeclarativeTool, loadDeclarativeTools, findDeclarativeTool } =
16
+ await import("./declarative.js");
17
+ const { getConfig } = await import("../../db/getConfig.js");
18
+ const { getGroupDir } = await import("../../storage/getGroupDir.js");
19
+ const { readGroupFile } = await import("../../storage/readGroupFile.js");
20
+
21
+ describe("declarative tools", () => {
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ describe("parseDeclarativeTool", () => {
27
+ it("parses a sandboxed javascript tool definition", () => {
28
+ expect(
29
+ parseDeclarativeTool(".agents/tools/main/echo.json", {
30
+ name: "echo",
31
+ description: "Echo structured input.",
32
+ input_schema: {
33
+ type: "object",
34
+ properties: { value: { type: "string" } },
35
+ required: ["value"],
36
+ },
37
+ execution: {
38
+ type: "javascript",
39
+ code: "return JSON.parse(data).value;",
40
+ },
41
+ }),
42
+ ).toEqual({
9
43
  name: "echo",
10
44
  description: "Echo structured input.",
11
45
  input_schema: {
@@ -17,101 +51,233 @@ describe("parseDeclarativeTool", () => {
17
51
  type: "javascript",
18
52
  code: "return JSON.parse(data).value;",
19
53
  },
20
- }),
21
- ).toEqual({
22
- name: "echo",
23
- description: "Echo structured input.",
24
- input_schema: {
25
- type: "object",
26
- properties: { value: { type: "string" } },
27
- required: ["value"],
28
- },
29
- execution: {
30
- type: "javascript",
31
- code: "return JSON.parse(data).value;",
32
- },
33
- path: ".agents/tools/main/echo.json",
54
+ path: ".agents/tools/main/echo.json",
55
+ });
34
56
  });
35
- });
36
57
 
37
- it("rejects tools without a sandbox execution recipe", () => {
38
- expect(() =>
39
- parseDeclarativeTool(".agents/tools/main/echo.json", {
40
- name: "echo",
41
- description: "Echo structured input.",
58
+ it("parses a bash tool definition", () => {
59
+ const tool = parseDeclarativeTool(".agents/tools/main/run_sh.json", {
60
+ name: "run_sh",
61
+ description: "Run bash command.",
42
62
  input_schema: { type: "object" },
43
- }),
44
- ).toThrow("execution");
45
- });
63
+ execution: {
64
+ type: "bash",
65
+ command: "echo hello",
66
+ },
67
+ });
46
68
 
47
- it("parses a tool executor that delegates to an existing tool", () => {
48
- expect(
49
- parseDeclarativeTool(".agents/tools/main/repo_status.json", {
69
+ expect(tool.execution).toEqual({
70
+ type: "bash",
71
+ command: "echo hello",
72
+ });
73
+ });
74
+
75
+ it("parses a tool executor that delegates to an existing tool with inputs", () => {
76
+ const tool = parseDeclarativeTool(".agents/tools/main/repo_status.json", {
50
77
  name: "repo_status",
51
78
  description: "Show repository status.",
52
79
  input_schema: { type: "object" },
53
- execution: { type: "tool", name: "git_status" },
54
- }).execution,
55
- ).toEqual({ type: "tool", name: "git_status" });
80
+ execution: {
81
+ type: "tool",
82
+ name: "git_status",
83
+ input: { verbose: true },
84
+ },
85
+ });
86
+
87
+ expect(tool.execution).toEqual({
88
+ type: "tool",
89
+ name: "git_status",
90
+ input: { verbose: true },
91
+ });
92
+ });
93
+
94
+ it("rejects invalid value shapes", () => {
95
+ expect(() => parseDeclarativeTool("test.json", null)).toThrow(
96
+ "must be a JSON object",
97
+ );
98
+ expect(() => parseDeclarativeTool("test.json", "string")).toThrow(
99
+ "must be a JSON object",
100
+ );
101
+ });
102
+
103
+ it("rejects invalid tool names", () => {
104
+ expect(() =>
105
+ parseDeclarativeTool("test.json", {
106
+ name: "Invalid-Name!",
107
+ description: "desc",
108
+ input_schema: {},
109
+ execution: {},
110
+ }),
111
+ ).toThrow("Tool name is invalid");
112
+
113
+ expect(() =>
114
+ parseDeclarativeTool("test.json", {
115
+ name: "",
116
+ description: "desc",
117
+ input_schema: {},
118
+ execution: {},
119
+ }),
120
+ ).toThrow("Tool name is invalid");
121
+ });
122
+
123
+ it("rejects missing description or input schema", () => {
124
+ expect(() =>
125
+ parseDeclarativeTool("test.json", {
126
+ name: "valid_name",
127
+ description: "",
128
+ input_schema: {},
129
+ execution: {},
130
+ }),
131
+ ).toThrow("requires a description");
132
+
133
+ expect(() =>
134
+ parseDeclarativeTool("test.json", {
135
+ name: "valid_name",
136
+ description: "desc",
137
+ input_schema: null,
138
+ execution: {},
139
+ }),
140
+ ).toThrow("requires input_schema");
141
+ });
142
+
143
+ it("rejects invalid execution specifications", () => {
144
+ expect(() =>
145
+ parseDeclarativeTool("test.json", {
146
+ name: "valid_name",
147
+ description: "desc",
148
+ input_schema: { type: "object" },
149
+ execution: null,
150
+ }),
151
+ ).toThrow("requires execution");
152
+
153
+ expect(() =>
154
+ parseDeclarativeTool("test.json", {
155
+ name: "valid_name",
156
+ description: "desc",
157
+ input_schema: { type: "object" },
158
+ execution: { type: "unsupported" },
159
+ }),
160
+ ).toThrow("execution type must be bash, javascript, or tool");
161
+
162
+ expect(() =>
163
+ parseDeclarativeTool("test.json", {
164
+ name: "valid_name",
165
+ description: "desc",
166
+ input_schema: { type: "object" },
167
+ execution: { type: "tool", name: "INVALID-DELEGATE!" },
168
+ }),
169
+ ).toThrow("execution requires a valid tool name");
170
+
171
+ expect(() =>
172
+ parseDeclarativeTool("test.json", {
173
+ name: "valid_name",
174
+ description: "desc",
175
+ input_schema: { type: "object" },
176
+ execution: { type: "bash", command: " " },
177
+ }),
178
+ ).toThrow("execution requires command");
179
+ });
56
180
  });
57
181
 
58
- it("parses generate_random_number declarative tool definition", () => {
59
- const raw = {
60
- name: "generate_random_number",
61
- description:
62
- "Generate a random integer within a specified range (min through max, inclusive).",
63
- input_schema: {
64
- type: "object",
65
- properties: {
66
- min: { type: "number", description: "Minimum integer value" },
67
- max: { type: "number", description: "Maximum integer value" },
182
+ describe("loadDeclarativeTools & findDeclarativeTool", () => {
183
+ it("loads declarative tools from directory tree", async () => {
184
+ const mockFileHandle = { kind: "file" };
185
+ const mockToolsDir = {
186
+ entries: async function* () {
187
+ yield ["calc.json", mockFileHandle];
188
+ yield ["invalid.json", mockFileHandle];
189
+ },
190
+ };
191
+ const mockAgentsDir = {
192
+ getDirectoryHandle: jest.fn().mockImplementation(async (name) => {
193
+ if (name === "tools") return mockToolsDir;
194
+ throw new Error("Not found");
195
+ }),
196
+ };
197
+ const mockRoot = {
198
+ getDirectoryHandle: jest.fn().mockImplementation(async (name) => {
199
+ if (name === ".agents") return mockAgentsDir;
200
+ throw new Error("Not found");
201
+ }),
202
+ };
203
+
204
+ (getGroupDir as jest.Mock<any>).mockResolvedValue(mockRoot);
205
+ (readGroupFile as jest.Mock<any>).mockImplementation(
206
+ async (_db: any, _gid: any, path: any) => {
207
+ if (path === ".agents/tools/calc.json") {
208
+ return JSON.stringify({
209
+ name: "calc",
210
+ description: "Calculate value",
211
+ input_schema: { type: "object" },
212
+ execution: { type: "javascript", code: "return 1;" },
213
+ });
214
+ }
215
+ return "{ invalid json }";
68
216
  },
69
- },
70
- execution: {
71
- type: "javascript",
72
- code: "const input = data ? JSON.parse(data) : {}; const min = typeof input.min === 'number' ? input.min : 1; const max = typeof input.max === 'number' ? input.max : 1000000; return Math.floor(Math.random() * (max - min + 1)) + min;",
73
- },
74
- };
75
-
76
- expect(
77
- parseDeclarativeTool(
78
- ".agents/tools/main/generate_random_number.json",
79
- raw,
80
- ),
81
- ).toEqual({
82
- ...raw,
83
- path: ".agents/tools/main/generate_random_number.json",
217
+ );
218
+
219
+ const { tools, diagnostics } = await loadDeclarativeTools({} as any);
220
+ expect(tools).toHaveLength(1);
221
+ expect(tools[0].name).toBe("calc");
222
+ expect(diagnostics).toHaveLength(1);
84
223
  });
85
- });
86
224
 
87
- it("parses tools using parameters and evaluation expression aliases", () => {
88
- const raw = {
89
- name: "calculate_ore_depth",
90
- description: "Calculates ore depth.",
91
- parameters: {
92
- type: "object",
93
- properties: { y: { type: "number" } },
94
- },
95
- evaluation: {
96
- type: "javascript",
97
- expression: "return { y: data ? JSON.parse(data).y : 0 };",
98
- },
99
- };
100
-
101
- expect(
102
- parseDeclarativeTool(".agents/tools/main/calculate_ore_depth.json", raw),
103
- ).toEqual({
104
- name: "calculate_ore_depth",
105
- description: "Calculates ore depth.",
106
- input_schema: {
107
- type: "object",
108
- properties: { y: { type: "number" } },
109
- },
110
- execution: {
111
- type: "javascript",
112
- code: "return { y: data ? JSON.parse(data).y : 0 };",
113
- },
114
- path: ".agents/tools/main/calculate_ore_depth.json",
225
+ it("returns empty tools when agents directory does not exist", async () => {
226
+ (getGroupDir as jest.Mock<any>).mockResolvedValue({
227
+ getDirectoryHandle: (jest.fn() as any).mockRejectedValue(
228
+ new Error("No .agents dir"),
229
+ ),
230
+ });
231
+
232
+ const { tools, diagnostics } = await loadDeclarativeTools({} as any);
233
+ expect(tools).toEqual([]);
234
+ expect(diagnostics).toEqual([]);
235
+ });
236
+
237
+ it("finds declarative tool by name when enabled", async () => {
238
+ const mockFileHandle = { kind: "file" };
239
+ const mockToolsDir = {
240
+ entries: async function* () {
241
+ yield ["my_tool.json", mockFileHandle];
242
+ },
243
+ };
244
+ const mockAgentsDir = {
245
+ getDirectoryHandle: (jest.fn() as any).mockResolvedValue(mockToolsDir),
246
+ };
247
+ const mockRoot = {
248
+ getDirectoryHandle: (jest.fn() as any).mockResolvedValue(mockAgentsDir),
249
+ };
250
+
251
+ (getGroupDir as jest.Mock<any>).mockResolvedValue(mockRoot);
252
+ (readGroupFile as jest.Mock<any>).mockResolvedValue(
253
+ JSON.stringify({
254
+ name: "my_tool",
255
+ description: "My custom tool",
256
+ input_schema: { type: "object" },
257
+ execution: { type: "javascript", code: "return true;" },
258
+ }),
259
+ );
260
+ (getConfig as jest.Mock<any>).mockResolvedValue(["my_tool"]);
261
+
262
+ const tool = await findDeclarativeTool({} as any, "main", "my_tool");
263
+ expect(tool?.name).toBe("my_tool");
264
+
265
+ // When tool is not in enabled list
266
+ (getConfig as jest.Mock<any>).mockResolvedValue(["other_tool"]);
267
+ const disabledTool = await findDeclarativeTool(
268
+ {} as any,
269
+ "main",
270
+ "my_tool",
271
+ );
272
+ expect(disabledTool).toBeNull();
273
+
274
+ // When searching for non-existent tool
275
+ const missing = await findDeclarativeTool(
276
+ {} as any,
277
+ "main",
278
+ "non_existent",
279
+ );
280
+ expect(missing).toBeNull();
115
281
  });
116
282
  });
117
283
  });
@@ -0,0 +1,63 @@
1
+ export class V86 {
2
+ constructor(options) {
3
+ this.options = options;
4
+ this.listeners = new Map();
5
+ this.serial0_send = (text) => {
6
+ if (text === "root\n") {
7
+ setTimeout(() => {
8
+ this.emitSerial("# ");
9
+ }, 5);
10
+ } else if (text && text.includes("sleep")) {
11
+ // Do not auto-respond to sleep commands to test timeout recovery
12
+ return;
13
+ } else if (text && text.includes("__BCDONE_")) {
14
+ const match = /printf "\\n(__BCDONE_[^%]+)%s\\n"/.exec(text);
15
+ if (match) {
16
+ setTimeout(() => {
17
+ this.emitSerial(`\n${match[1]}0\n`);
18
+ }, 5);
19
+ }
20
+ } else if (text && text.includes("\u0003")) {
21
+ setTimeout(() => {
22
+ this.emitSerial("# ");
23
+ }, 5);
24
+ }
25
+ };
26
+ this.destroy = () => Promise.resolve();
27
+ this.fs9p = {
28
+ read_dir: () => [],
29
+ SearchPath: (path) => ({ id: path === "/home/user" ? 1 : -1 }),
30
+ IsDirectory: () => true,
31
+ };
32
+ this.create_file = () => Promise.resolve();
33
+ this.read_file = () => Promise.resolve(new Uint8Array([1, 2, 3]));
34
+
35
+ // Auto-emit boot sequence
36
+ setTimeout(() => {
37
+ this.emitSerial("Booting Linux on ttyS0\nlogin: ");
38
+ }, 5);
39
+ }
40
+
41
+ add_listener(event, listener) {
42
+ if (!this.listeners.has(event)) {
43
+ this.listeners.set(event, new Set());
44
+ }
45
+ this.listeners.get(event).add(listener);
46
+ }
47
+
48
+ remove_listener(event, listener) {
49
+ this.listeners.get(event)?.delete(listener);
50
+ }
51
+
52
+ emitSerial(text) {
53
+ const serialListeners = this.listeners.get("serial0-output-byte");
54
+ if (!serialListeners) return;
55
+ for (const ch of text) {
56
+ for (const listener of serialListeners) {
57
+ listener(ch.charCodeAt(0));
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ export default V86;
@@ -0,0 +1,3 @@
1
+ import V86 from "../v86.9pfs/libv86.mjs";
2
+ export { V86 };
3
+ export default V86;
@@ -0,0 +1 @@
1
+ export const notV86 = true;