clawmini 0.0.7 → 0.0.9

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 (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.Dr0ot9sV.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +42 -13
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -176
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.Dr0ot9sV.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  328. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  329. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  331. package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { TestEnvironment } from '../_helpers/test-environment.js';
4
+
5
+ async function waitUntil(
6
+ cond: () => boolean,
7
+ timeoutMs: number,
8
+ intervalMs = 100
9
+ ): Promise<boolean> {
10
+ const deadline = Date.now() + timeoutMs;
11
+ while (Date.now() < deadline) {
12
+ if (cond()) return true;
13
+ await new Promise((r) => setTimeout(r, intervalMs));
14
+ }
15
+ return cond();
16
+ }
17
+
18
+ describe('clawmini serve (detached) + down', () => {
19
+ let env: TestEnvironment;
20
+
21
+ beforeAll(async () => {
22
+ env = new TestEnvironment('e2e-tmp-serve');
23
+ await env.setup();
24
+ await env.init();
25
+ }, 30000);
26
+
27
+ afterAll(() => env.teardown(), 30000);
28
+
29
+ it('starts the supervisor in the background and writes a pid file', async () => {
30
+ const { code, stdout } = await env.runCli(['serve', '--detach']);
31
+ expect(code).toBe(0);
32
+ expect(stdout).toContain('Started clawmini supervisor in background');
33
+
34
+ const pidPath = env.getClawminiPath('supervisor.pid');
35
+ expect(await waitUntil(() => fs.existsSync(pidPath), 5000)).toBe(true);
36
+
37
+ const socketPath = env.getClawminiPath('daemon.sock');
38
+ expect(await waitUntil(() => fs.existsSync(socketPath), 10000)).toBe(true);
39
+ }, 30000);
40
+
41
+ it('refuses to start a second supervisor while one is running', async () => {
42
+ const { code, stderr } = await env.runCli(['serve', '--detach']);
43
+ expect(code).toBe(1);
44
+ expect(stderr).toContain('already running');
45
+ }, 15000);
46
+
47
+ it('clawmini down stops the supervisor and clears the socket + pid file', async () => {
48
+ const { code } = await env.runCli(['down']);
49
+ expect(code).toBe(0);
50
+
51
+ const pidPath = env.getClawminiPath('supervisor.pid');
52
+ const socketPath = env.getClawminiPath('daemon.sock');
53
+ expect(await waitUntil(() => !fs.existsSync(pidPath), 10000)).toBe(true);
54
+ expect(await waitUntil(() => !fs.existsSync(socketPath), 10000)).toBe(true);
55
+ }, 30000);
56
+
57
+ it('writes daemon output to .clawmini/logs/daemon.log', () => {
58
+ const logPath = env.getClawminiPath('logs', 'daemon.log');
59
+ expect(fs.existsSync(logPath)).toBe(true);
60
+ const content = fs.readFileSync(logPath, 'utf-8');
61
+ expect(content).toContain('Daemon initialized and listening');
62
+ });
63
+
64
+ it('clawmini logs --service daemon prints prefixed output', async () => {
65
+ const { code, stdout } = await env.runCli(['logs', '--service', 'daemon']);
66
+ expect(code).toBe(0);
67
+ expect(stdout).toContain('[daemon]');
68
+ expect(stdout).toContain('Daemon initialized and listening');
69
+ });
70
+
71
+ it('clawmini down is a no-op when nothing is running', async () => {
72
+ const { stdout, code } = await env.runCli(['down']);
73
+ expect(code).toBe(0);
74
+ expect(stdout).toContain('Daemon is not running');
75
+ });
76
+ });
@@ -1,18 +1,19 @@
1
1
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { createE2EContext } from './utils.js';
3
-
4
- const { runCli, setupE2E, teardownE2E } = createE2EContext('e2e-tmp-skills');
2
+ import { TestEnvironment } from '../_helpers/test-environment.js';
5
3
 
6
4
  describe('E2E Skills Tests', () => {
5
+ let env: TestEnvironment;
6
+
7
7
  beforeAll(async () => {
8
- await setupE2E();
9
- await runCli(['init', '--agent', 'test-agent']);
8
+ env = new TestEnvironment('e2e-tmp-skills');
9
+ await env.setup();
10
+ await env.runCli(['init', '--agent', 'test-agent']);
10
11
  }, 30000);
11
12
 
12
- afterAll(teardownE2E, 30000);
13
+ afterAll(() => env.teardown(), 30000);
13
14
 
14
15
  it('should list available template skills', async () => {
15
- const { stdout, code, stderr } = await runCli(['skills', 'list']);
16
+ const { stdout, code, stderr } = await env.runCli(['skills', 'list']);
16
17
  expect(code).toBe(0);
17
18
  // As it reads from the internal templates/skills directory,
18
19
  // it should at least output one of the default template skills, or "No skills found."
@@ -22,7 +23,7 @@ describe('E2E Skills Tests', () => {
22
23
  });
23
24
 
24
25
  it('should add a specific skill', async () => {
25
- const { stdout, code, stderr } = await runCli([
26
+ const { stdout, code, stderr } = await env.runCli([
26
27
  'skills',
27
28
  'add',
28
29
  'skill-creator',
@@ -35,14 +36,14 @@ describe('E2E Skills Tests', () => {
35
36
  });
36
37
 
37
38
  it('should add all skills when no skill name is provided', async () => {
38
- const { stdout, code, stderr } = await runCli(['skills', 'add', '--agent', 'test-agent']);
39
+ const { stdout, code, stderr } = await env.runCli(['skills', 'add', '--agent', 'test-agent']);
39
40
  expect(stderr).toBe('');
40
41
  expect(code).toBe(0);
41
42
  expect(stdout).toContain('Successfully added all skills to agent');
42
43
  });
43
44
 
44
45
  it('should handle adding an invalid skill gracefully', async () => {
45
- const { code, stderr } = await runCli([
46
+ const { code, stderr } = await env.runCli([
46
47
  'skills',
47
48
  'add',
48
49
  'invalid-skill-name-123',
@@ -1,22 +1,30 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
2
  import { spawn } from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  import fs from 'node:fs';
5
- import { getSocketPath } from '../../shared/workspace.js';
6
- import { createE2EContext } from './utils.js';
7
-
8
- const { runCli, e2eDir, binPath, setupE2E, teardownE2E } = createE2EContext('e2e-tmp-daemon');
5
+ import { getSocketPath } from '../../src/shared/workspace.js';
6
+ import {
7
+ TestEnvironment,
8
+ findFreePort,
9
+ type ChatSubscription,
10
+ } from '../_helpers/test-environment.js';
11
+ import { commandWith } from '../_helpers/test-environment.js';
9
12
 
10
13
  describe('E2E Daemon and Web Tests', () => {
14
+ let env: TestEnvironment;
15
+ let chat: ChatSubscription | undefined;
16
+
11
17
  beforeAll(async () => {
12
- await setupE2E();
13
- await runCli(['init']);
18
+ env = new TestEnvironment('e2e-daemon');
19
+ await env.setup();
20
+ await env.init();
14
21
  }, 30000);
15
22
 
16
- afterAll(teardownE2E, 30000);
23
+ afterAll(() => env.teardown(), 30000);
24
+ afterEach(() => env.disconnectAll());
17
25
 
18
26
  it('should explicitly start the daemon via up command', async () => {
19
- const { stdout, code } = await runCli(['up']);
27
+ const { stdout, code } = await env.runCli(['up']);
20
28
  expect(code).toBe(0);
21
29
  // Since the daemon is likely running from previous tests or init, it should say so
22
30
  // or it will start successfully.
@@ -24,29 +32,29 @@ describe('E2E Daemon and Web Tests', () => {
24
32
  });
25
33
 
26
34
  it('should successfully shut down the daemon', async () => {
27
- const { stdout, code } = await runCli(['down']);
35
+ const { stdout, code } = await env.runCli(['down']);
28
36
 
29
37
  expect(code).toBe(0);
30
38
  expect(stdout).toContain('Successfully shut down clawmini daemon.');
31
39
 
32
40
  await new Promise((resolve) => setTimeout(resolve, 500));
33
41
 
34
- const socketPath = getSocketPath(e2eDir);
42
+ const socketPath = getSocketPath(env.e2eDir);
35
43
  expect(fs.existsSync(socketPath)).toBe(false);
36
44
 
37
- const { stdout: stdoutAgain, code: codeAgain } = await runCli(['down']);
45
+ const { stdout: stdoutAgain, code: codeAgain } = await env.runCli(['down']);
38
46
  expect(codeAgain).toBe(0);
39
47
  expect(stdoutAgain).toContain('Daemon is not running.');
40
48
 
41
- const { stdout: stdoutUp, code: codeUp } = await runCli(['up']);
49
+ const { stdout: stdoutUp, code: codeUp } = await env.runCli(['up']);
42
50
  expect(codeUp).toBe(0);
43
51
  expect(stdoutUp).toContain('Successfully started clawmini daemon.');
44
52
  }, 15000);
45
53
 
46
54
  it('should run web command and serve static files', async () => {
47
- const webPort = 8081;
48
- const child = spawn('node', [binPath, 'web', '--port', webPort.toString()], {
49
- cwd: e2eDir,
55
+ const webPort = await findFreePort();
56
+ const child = spawn('node', [env.binPath, 'web', '--port', webPort.toString()], {
57
+ cwd: env.e2eDir,
50
58
  env: { ...process.env },
51
59
  });
52
60
 
@@ -83,7 +91,7 @@ describe('E2E Daemon and Web Tests', () => {
83
91
  const html404 = await res404.text();
84
92
  expect(html404.toLowerCase()).toContain('<!doctype html>');
85
93
 
86
- await runCli(['chats', 'add', 'api-test-chat']);
94
+ await env.addChat('api-test-chat');
87
95
 
88
96
  const resChats = await fetch(`http://127.0.0.1:${webPort}/api/chats`);
89
97
  expect(resChats.status).toBe(200);
@@ -144,7 +152,7 @@ describe('E2E Daemon and Web Tests', () => {
144
152
  const reader = sseResponse.body.getReader();
145
153
  const decoder = new TextDecoder();
146
154
 
147
- const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/api-test-chat/chat.jsonl');
155
+ const chatLogPath = path.resolve(env.e2eDir, '.clawmini/chats/api-test-chat/chat.jsonl');
148
156
  const mockMessage = {
149
157
  id: 'mock-1',
150
158
  role: 'user',
@@ -220,203 +228,57 @@ describe('E2E Daemon and Web Tests', () => {
220
228
  }, 30000);
221
229
 
222
230
  it('should optionally start an HTTP API server for the daemon when configured', async () => {
223
- await runCli(['down']);
224
-
225
- const settingsPath = path.resolve(e2eDir, '.clawmini/settings.json');
226
- let originalSettings = '{}';
227
- if (fs.existsSync(settingsPath)) {
228
- originalSettings = fs.readFileSync(settingsPath, 'utf8');
229
- }
231
+ await env.runCli(['down']);
232
+ const originalSettings = env.getSettings();
230
233
 
231
- fs.writeFileSync(
232
- settingsPath,
233
- JSON.stringify({
234
- ...JSON.parse(originalSettings),
235
- api: { host: '127.0.0.1', port: 3005 },
236
- })
237
- );
234
+ const apiPort = await findFreePort();
235
+ env.writeSettings({ ...originalSettings, api: { host: '127.0.0.1', port: apiPort } });
238
236
 
239
- const { stdout, code } = await runCli(['up']);
237
+ const { stdout, code } = await env.runCli(['up']);
240
238
  expect(code).toBe(0);
241
239
  expect(stdout).toContain('Successfully started clawmini daemon.');
242
240
 
243
241
  await new Promise((resolve) => setTimeout(resolve, 500));
244
242
 
245
- const res = await fetch('http://127.0.0.1:3005/ping');
243
+ const res = await fetch(`http://127.0.0.1:${apiPort}/ping`);
246
244
  expect(res.status).toBe(200);
247
245
  const data = (await res.json()) as { result: { data: { status: string } } };
248
246
  expect(data.result.data.status).toBe('ok');
249
247
 
250
- await runCli(['down']);
251
- fs.writeFileSync(settingsPath, originalSettings);
252
- await runCli(['up']);
248
+ await env.runCli(['down']);
249
+ env.writeSettings(originalSettings);
250
+ await env.runCli(['up']);
253
251
  }, 15000);
254
252
 
255
253
  it('should inject CLAW_API_URL and CLAW_API_TOKEN into spawned agents when API is enabled', async () => {
256
- await runCli(['down']);
257
- const settingsPath = path.resolve(e2eDir, '.clawmini/settings.json');
258
- let originalSettings = '{}';
259
- if (fs.existsSync(settingsPath)) {
260
- originalSettings = fs.readFileSync(settingsPath, 'utf8');
261
- }
262
- fs.writeFileSync(
263
- settingsPath,
264
- JSON.stringify({
265
- ...JSON.parse(originalSettings),
266
- api: { host: '127.0.0.1', port: 3006 },
267
- })
268
- );
269
- await runCli(['up']);
270
-
271
- await runCli(['agents', 'add', 'env-dumper', '--dir', 'env-dumper']);
272
- const envDumperSettingsPath = path.resolve(e2eDir, '.clawmini/agents/env-dumper/settings.json');
273
- fs.mkdirSync(path.dirname(envDumperSettingsPath), { recursive: true });
254
+ await env.runCli(['down']);
255
+ const originalSettings = env.getSettings();
274
256
 
275
- // Create the actual agent working directory so spawn doesn't fail with ENOENT
276
- const agentWorkingDir = path.resolve(e2eDir, 'env-dumper');
277
- fs.mkdirSync(agentWorkingDir, { recursive: true });
278
-
279
- fs.writeFileSync(
280
- envDumperSettingsPath,
281
- JSON.stringify({
282
- commands: {
283
- new: process.platform === 'win32' ? 'set' : 'env',
284
- },
285
- })
286
- );
287
-
288
- await runCli(['chats', 'add', 'env-chat']);
289
- const { stdout, stderr, code } = await runCli([
290
- 'messages',
291
- 'send',
292
- 'dump it',
293
- '--chat',
294
- 'env-chat',
295
- '--agent',
296
- 'env-dumper',
297
- ]);
298
- if (code !== 0) {
299
- console.error('send failed:', stdout, stderr);
300
- }
301
-
302
- await new Promise((resolve) => setTimeout(resolve, 2000));
303
-
304
- const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/env-chat/chat.jsonl');
305
- expect(fs.existsSync(chatLogPath)).toBe(true);
306
- const chatLogContent = fs.readFileSync(chatLogPath, 'utf8');
307
-
308
- if (!chatLogContent.includes('CLAW_API_URL')) {
309
- console.error('CHAT LOG:', chatLogContent);
310
- }
311
-
312
- expect(chatLogContent).toContain('CLAW_API_URL=http://127.0.0.1:3006');
313
- expect(chatLogContent).toContain('CLAW_API_TOKEN=');
314
-
315
- await runCli(['down']);
316
- fs.writeFileSync(settingsPath, originalSettings);
317
- await runCli(['up']);
318
- }, 15000);
319
-
320
- it('should inject custom env vars and pointers when configured, and lite should work', async () => {
321
- await runCli(['down']);
322
- const settingsPath = path.resolve(e2eDir, '.clawmini/settings.json');
323
- let originalSettings = '{}';
324
- if (fs.existsSync(settingsPath)) {
325
- originalSettings = fs.readFileSync(settingsPath, 'utf8');
326
- }
327
- fs.writeFileSync(
328
- settingsPath,
329
- JSON.stringify({
330
- ...JSON.parse(originalSettings),
331
- api: { host: '127.0.0.1', port: 3006 },
332
- })
333
- );
334
- await runCli(['up']);
335
-
336
- await runCli(['agents', 'add', 'custom-env-dumper', '--dir', 'custom-env-dumper']);
337
- const agentSettingsPath = path.resolve(
338
- e2eDir,
339
- '.clawmini/agents/custom-env-dumper/settings.json'
340
- );
341
- fs.mkdirSync(path.dirname(agentSettingsPath), { recursive: true });
257
+ const apiPort = await findFreePort();
258
+ env.writeSettings({ ...originalSettings, api: { host: '127.0.0.1', port: apiPort } });
259
+ await env.runCli(['up']);
342
260
 
261
+ await env.runCli(['agents', 'add', 'env-dumper', '--dir', 'env-dumper']);
343
262
  // Create the actual agent working directory so spawn doesn't fail with ENOENT
344
- const agentWorkingDir = path.resolve(e2eDir, 'custom-env-dumper');
345
- fs.mkdirSync(agentWorkingDir, { recursive: true });
346
-
347
- const dumperScript = process.platform === 'win32' ? 'set > env.txt' : 'env > env.txt';
348
-
349
- fs.writeFileSync(
350
- agentSettingsPath,
351
- JSON.stringify({
352
- apiTokenEnvVar: 'MY_CUSTOM_TOKEN',
353
- apiUrlEnvVar: 'MY_CUSTOM_URL',
354
- commands: {
355
- new: dumperScript,
356
- },
357
- })
358
- );
359
-
360
- await runCli(['chats', 'add', 'custom-env-chat']);
361
- const { stdout, stderr, code } = await runCli([
362
- 'messages',
363
- 'send',
364
- 'dump custom',
365
- '--chat',
366
- 'custom-env-chat',
367
- '--agent',
368
- 'custom-env-dumper',
369
- ]);
370
- if (code !== 0) {
371
- console.error('send failed:', stdout, stderr);
372
- }
373
-
374
- await new Promise((resolve) => setTimeout(resolve, 2000));
375
-
376
- const chatLogPath = path.resolve(e2eDir, '.clawmini/chats/custom-env-chat/chat.jsonl');
377
- expect(fs.existsSync(chatLogPath)).toBe(true);
378
-
379
- const envTxtPath = path.resolve(agentWorkingDir, 'env.txt');
380
- expect(fs.existsSync(envTxtPath)).toBe(true);
381
- const envContent = fs.readFileSync(envTxtPath, 'utf8');
382
-
383
- expect(envContent).toContain('CLAW_LITE_API_VAR=MY_CUSTOM_TOKEN');
384
- expect(envContent).toContain('CLAW_LITE_URL_VAR=MY_CUSTOM_URL');
385
- expect(envContent).toContain('MY_CUSTOM_URL=http://127.0.0.1:3006');
386
- expect(envContent).toContain('MY_CUSTOM_TOKEN=');
263
+ fs.mkdirSync(path.resolve(env.e2eDir, 'env-dumper'), { recursive: true });
264
+ env.writeAgentSettings('env-dumper', {
265
+ commands: {
266
+ new: process.platform === 'win32' ? 'set' : 'env',
267
+ },
268
+ });
387
269
 
388
- // Verify lite works with the dynamically configured vars
389
- const urlMatch = envContent.match(/MY_CUSTOM_URL=(.+)/);
390
- const tokenMatch = envContent.match(/MY_CUSTOM_TOKEN=(.+)/);
270
+ await env.addChat('env-chat');
271
+ chat = await env.connect('env-chat');
272
+ await env.sendMessage('dump it', { chat: 'env-chat', agent: 'env-dumper' });
391
273
 
392
- expect(urlMatch).toBeTruthy();
393
- expect(tokenMatch).toBeTruthy();
274
+ const log = await chat.waitForMessage(commandWith('CLAW_API_URL='));
275
+ expect(log.stdout).toContain(`CLAW_API_URL=http://127.0.0.1:${apiPort}`);
276
+ expect(log.stdout).toMatch(/CLAW_API_TOKEN=.+/);
394
277
 
395
- const envUrl = urlMatch![1]!.trim();
396
- const envToken = tokenMatch![1]!.trim();
278
+ await env.disconnectAll();
397
279
 
398
- const litePath = path.resolve(__dirname, '../../../dist/cli/lite.mjs');
399
- const replyProcess = spawn('node', [litePath, 'reply', 'hello from custom env'], {
400
- env: {
401
- ...process.env,
402
- MY_CUSTOM_URL: envUrl,
403
- MY_CUSTOM_TOKEN: envToken,
404
- CLAW_LITE_URL_VAR: 'MY_CUSTOM_URL',
405
- CLAW_LITE_API_VAR: 'MY_CUSTOM_TOKEN',
406
- },
407
- cwd: agentWorkingDir,
408
- });
409
- let replyStdout = '';
410
- replyProcess.stdout.on('data', (d) => (replyStdout += d.toString()));
411
- replyProcess.stderr.on('data', (d) => (replyStdout += d.toString()));
412
- await new Promise((resolve) => replyProcess.on('close', resolve));
413
- expect(replyStdout).toContain('Reply message appended');
414
-
415
- const chatLogContentReply = fs.readFileSync(chatLogPath, 'utf8');
416
- expect(chatLogContentReply).toContain('hello from custom env');
417
-
418
- await runCli(['down']);
419
- fs.writeFileSync(settingsPath, originalSettings);
420
- await runCli(['up']);
280
+ await env.runCli(['down']);
281
+ env.writeSettings(originalSettings);
282
+ await env.runCli(['up']);
421
283
  }, 15000);
422
284
  });
@@ -0,0 +1,216 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { TestEnvironment, commandWith } from '../_helpers/test-environment.js';
3
+
4
+ describe('E2E Agent Jobs (Lite)', () => {
5
+ let env: TestEnvironment;
6
+ let envUrl = '';
7
+ let envToken = '';
8
+
9
+ beforeAll(async () => {
10
+ env = new TestEnvironment('e2e-tmp-agent-jobs');
11
+ await env.setup();
12
+ await env.setupSubagentEnv();
13
+ ({ url: envUrl, token: envToken } = await env.getAgentCredentials());
14
+ }, 30000);
15
+
16
+ afterAll(() => env.teardown(), 30000);
17
+
18
+ // Filter out the session-timeout fixture job scheduled automatically by the
19
+ // debug-agent's router pipeline so assertions focus on user-added jobs.
20
+ async function listUserJobs(): Promise<Array<Record<string, unknown>>> {
21
+ const { stdout, code } = await env.runLite(['jobs', 'list']);
22
+ expect(code).toBe(0);
23
+ const all = JSON.parse(stdout) as Array<Record<string, unknown>>;
24
+ return all.filter(
25
+ (j) => !(typeof j.id === 'string' && j.id.startsWith('__session_timeout__'))
26
+ );
27
+ }
28
+
29
+ it('should return a parseable JSON array from list', async () => {
30
+ const { stdout, code } = await env.runLite(['jobs', 'list']);
31
+ expect(code).toBe(0);
32
+ expect(Array.isArray(JSON.parse(stdout))).toBe(true);
33
+ });
34
+
35
+ it('should add a job with the allowed flags and stamp agentId from the token', async () => {
36
+ const { stdout, code } = await env.runLite([
37
+ 'jobs', 'add', 'agent-job-1',
38
+ '--every', '999h',
39
+ '--message', 'hello from agent',
40
+ '--reply', 'queued',
41
+ '--session', 'new',
42
+ ]);
43
+ expect(code).toBe(0);
44
+ expect(stdout).toContain("Job 'agent-job-1' created successfully.");
45
+
46
+ const jobs = await listUserJobs();
47
+ const job = jobs.find((j) => j.id === 'agent-job-1');
48
+ expect(job).toMatchObject({
49
+ id: 'agent-job-1',
50
+ message: 'hello from agent',
51
+ reply: 'queued',
52
+ // Server stamps agentId from the token rather than accepting it as input.
53
+ agentId: 'debug-agent',
54
+ session: { type: 'new' },
55
+ schedule: { every: '999h' },
56
+ });
57
+ // Internal-only fields must never leak in from agent input.
58
+ expect(job).not.toHaveProperty('env');
59
+ expect(job).not.toHaveProperty('action');
60
+ expect(job).not.toHaveProperty('nextSessionId');
61
+ });
62
+
63
+ it('should reject job input containing internal-only fields', async () => {
64
+ const attempts = [
65
+ { id: 'bad-env', schedule: { every: '999h' }, env: { FOO: 'BAR' } },
66
+ { id: 'bad-action', schedule: { every: '999h' }, action: 'stop' },
67
+ { id: 'bad-agent', schedule: { every: '999h' }, agentId: 'someone-else' },
68
+ { id: 'bad-next', schedule: { every: '999h' }, nextSessionId: 'abc' },
69
+ ];
70
+
71
+ for (const job of attempts) {
72
+ const res = await fetch(`${envUrl}/addCronJob`, {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ Authorization: `Bearer ${envToken}`,
77
+ },
78
+ body: JSON.stringify({ job }),
79
+ });
80
+ expect(res.status).toBe(400);
81
+ const body = (await res.json()) as { error?: { message?: string } };
82
+ expect(body.error?.message ?? '').toMatch(/unrecognized|invalid/i);
83
+ }
84
+
85
+ const jobs = await listUserJobs();
86
+ for (const attempt of attempts) {
87
+ expect(jobs.find((j) => j.id === attempt.id)).toBeUndefined();
88
+ }
89
+ });
90
+
91
+ it('should add a job with --cron schedule', async () => {
92
+ const { code } = await env.runLite([
93
+ 'jobs', 'add', 'agent-job-cron',
94
+ '--cron', '0 0 * * *',
95
+ '--message', 'nightly',
96
+ ]);
97
+ expect(code).toBe(0);
98
+
99
+ const jobs = await listUserJobs();
100
+ const job = jobs.find((j) => j.id === 'agent-job-cron');
101
+ expect(job).toMatchObject({ schedule: { cron: '0 0 * * *' }, message: 'nightly' });
102
+ });
103
+
104
+ it('should add a job with --at schedule (interval), execute it, and auto-delete it', async () => {
105
+ // Jobs created via agent credentials run in the __creds__ chat (the chat
106
+ // the agent token is bound to); subscribe there to observe execution.
107
+ const chat = await env.connect('__creds__');
108
+ try {
109
+ const { code, stdout } = await env.runLite([
110
+ 'jobs', 'add', 'agent-job-at-interval',
111
+ '--at', '2s',
112
+ '--message', 'echo job-ran-interval',
113
+ '--reply', 'queued',
114
+ '--session', 'new',
115
+ ]);
116
+ expect(code).toBe(0);
117
+ expect(stdout).toContain("Job 'agent-job-at-interval' created successfully.");
118
+
119
+ const jobsBefore = await listUserJobs();
120
+ const jobBefore = jobsBefore.find((j) => j.id === 'agent-job-at-interval');
121
+ expect(jobBefore).toMatchObject({
122
+ schedule: { at: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/) },
123
+ message: 'echo job-ran-interval',
124
+ });
125
+
126
+ // Wait for the debug agent to echo the job message — proves the job
127
+ // actually fired end-to-end, not just that it was scheduled.
128
+ await chat.waitForMessage(commandWith('[DEBUG] echo job-ran-interval'), 8000);
129
+
130
+ const jobsAfter = await listUserJobs();
131
+ expect(jobsAfter.find((j) => j.id === 'agent-job-at-interval')).toBeUndefined();
132
+ } finally {
133
+ await chat.disconnect();
134
+ }
135
+ }, 15000);
136
+
137
+ it('should add a job with --at schedule (timestamp), execute it, and auto-delete it', async () => {
138
+ const chat = await env.connect('__creds__');
139
+ try {
140
+ const futureTime = new Date(Date.now() + 2000).toISOString();
141
+ const { code, stdout } = await env.runLite([
142
+ 'jobs', 'add', 'agent-job-at-timestamp',
143
+ '--at', futureTime,
144
+ '--message', 'echo job-ran-timestamp',
145
+ '--reply', 'queued',
146
+ '--session', 'new',
147
+ ]);
148
+ expect(code).toBe(0);
149
+ expect(stdout).toContain("Job 'agent-job-at-timestamp' created successfully.");
150
+
151
+ const jobsBefore = await listUserJobs();
152
+ const jobBefore = jobsBefore.find((j) => j.id === 'agent-job-at-timestamp');
153
+ expect(jobBefore).toMatchObject({
154
+ schedule: { at: futureTime },
155
+ message: 'echo job-ran-timestamp',
156
+ });
157
+
158
+ await chat.waitForMessage(commandWith('[DEBUG] echo job-ran-timestamp'), 8000);
159
+
160
+ const jobsAfter = await listUserJobs();
161
+ expect(jobsAfter.find((j) => j.id === 'agent-job-at-timestamp')).toBeUndefined();
162
+ } finally {
163
+ await chat.disconnect();
164
+ }
165
+ }, 15000);
166
+
167
+ it('should reject jobs with no schedule flag', async () => {
168
+ const { stderr, code } = await env.runLite(['jobs', 'add', 'no-sched', '--message', 'x']);
169
+ expect(code).toBe(1);
170
+ expect(stderr).toContain('A schedule must be specified');
171
+ });
172
+
173
+ it('should reject --session values other than "new"', async () => {
174
+ const { stderr, code } = await env.runLite([
175
+ 'jobs', 'add', 'bad-session',
176
+ '--every', '999h',
177
+ '--session', 'bogus',
178
+ ]);
179
+ expect(code).toBe(1);
180
+ expect(stderr).toContain('Only "new" session type is supported');
181
+ });
182
+
183
+ it('should replace a job when adding the same id twice', async () => {
184
+ await env.runLite(['jobs', 'add', 'dup-job', '--every', '999h', '--message', 'first']);
185
+ const { code } = await env.runLite([
186
+ 'jobs', 'add', 'dup-job',
187
+ '--every', '888h',
188
+ '--message', 'second',
189
+ ]);
190
+ expect(code).toBe(0);
191
+
192
+ const jobs = await listUserJobs();
193
+ const matches = jobs.filter((j) => j.id === 'dup-job');
194
+ expect(matches).toHaveLength(1);
195
+ expect(matches[0]).toMatchObject({
196
+ message: 'second',
197
+ schedule: { every: '888h' },
198
+ });
199
+ });
200
+
201
+ it('should delete an existing job', async () => {
202
+ await env.runLite(['jobs', 'add', 'del-me', '--every', '999h']);
203
+ const { stdout, code } = await env.runLite(['jobs', 'delete', 'del-me']);
204
+ expect(code).toBe(0);
205
+ expect(stdout).toContain("Job 'del-me' deleted successfully.");
206
+
207
+ const jobs = await listUserJobs();
208
+ expect(jobs.find((j) => j.id === 'del-me')).toBeUndefined();
209
+ });
210
+
211
+ it('should report "not found" when deleting a missing job', async () => {
212
+ const { stdout, code } = await env.runLite(['jobs', 'delete', 'does-not-exist']);
213
+ expect(code).toBe(0);
214
+ expect(stdout).toContain("Job 'does-not-exist' not found.");
215
+ });
216
+ });