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,782 @@
1
+ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
2
+ import {
3
+ TestEnvironment,
4
+ type ChatSubscription,
5
+ commandMatching,
6
+ commandWith,
7
+ } from '../_helpers/test-environment.js';
8
+
9
+ // Exercises the subagent lifecycle commands (send, wait, stop, delete, list,
10
+ // tail) from a parent agent's perspective, plus the completion-notification
11
+ // paths (sync, wait-before/after completion, parent busy/idle on completion).
12
+ // Spawn + async happy-path is already covered by session-timeout-subagents.test.ts.
13
+
14
+ describe('E2E Subagent Lifecycle', () => {
15
+ let env: TestEnvironment;
16
+ let chat: ChatSubscription | undefined;
17
+
18
+ beforeAll(async () => {
19
+ env = new TestEnvironment('e2e-subagent-lifecycle');
20
+ await env.setup();
21
+ await env.setupSubagentEnv();
22
+ }, 30000);
23
+
24
+ afterAll(() => env.teardown(), 30000);
25
+ afterEach(() => env.disconnectAll());
26
+
27
+ it('send delivers a follow-up message to an existing subagent session', async () => {
28
+ await env.addChat('send-chat', 'debug-agent');
29
+ chat = await env.connect('send-chat');
30
+
31
+ await env.sendMessage(
32
+ 'clawmini-lite.js subagents spawn --id send-sub --async "echo first-msg"',
33
+ { chat: 'send-chat', agent: 'debug-agent' }
34
+ );
35
+ // First message through the subagent runs via `new` (no SESSION_ID yet).
36
+ await chat.waitForMessage(
37
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('[DEBUG] echo first-msg:'))
38
+ );
39
+
40
+ await env.sendMessage('clawmini-lite.js subagents send send-sub --async -p "echo second-msg"', {
41
+ chat: 'send-chat',
42
+ agent: 'debug-agent',
43
+ });
44
+ // Follow-up runs via `append`, so the prefix is `[DEBUG <sessionId>]`.
45
+ const followUp = await chat.waitForMessage(
46
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('echo second-msg'))
47
+ );
48
+ expect(followUp.stdout).toMatch(/\[DEBUG [^\]]+\] echo second-msg:/);
49
+ }, 30000);
50
+
51
+ it('wait returns the completed subagent output to the caller', async () => {
52
+ await env.addChat('wait-chat', 'debug-agent');
53
+ chat = await env.connect('wait-chat');
54
+
55
+ await env.sendMessage(
56
+ 'clawmini-lite.js subagents spawn --id wait-sub --async "echo wait-complete"',
57
+ { chat: 'wait-chat', agent: 'debug-agent' }
58
+ );
59
+ await chat.waitForMessage(
60
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('wait-complete'))
61
+ );
62
+
63
+ await env.sendMessage('clawmini-lite.js subagents wait wait-sub', {
64
+ chat: 'wait-chat',
65
+ agent: 'debug-agent',
66
+ });
67
+
68
+ // `subagents wait` prints the subagent's last agent reply (the debug
69
+ // template's full stdout) back to the parent. That output is wrapped by
70
+ // the parent's own debug invocation in this chat's command log.
71
+ const waitLog = await chat.waitForMessage(
72
+ commandMatching(
73
+ (m) =>
74
+ !m.subagentId &&
75
+ m.stdout.includes('subagents wait wait-sub:') &&
76
+ m.stdout.includes('wait-complete')
77
+ )
78
+ );
79
+ expect(waitLog.exitCode).toBe(0);
80
+ }, 30000);
81
+
82
+ it('stop aborts an active subagent task before it finishes', async () => {
83
+ await env.addChat('stop-chat', 'debug-agent');
84
+ chat = await env.connect('stop-chat');
85
+
86
+ // Long-running subagent command so we can stop it mid-flight.
87
+ await env.sendMessage(
88
+ 'clawmini-lite.js subagents spawn --id stop-sub --async "sleep 30 && echo should-not-print"',
89
+ { chat: 'stop-chat', agent: 'debug-agent' }
90
+ );
91
+ for (let i = 0; i < 50; i++) {
92
+ const settings = env.getChatSettings('stop-chat') as {
93
+ subagents?: Record<string, { status: string }>;
94
+ };
95
+ if (settings.subagents?.['stop-sub']?.status === 'active') break;
96
+ await new Promise((r) => setTimeout(r, 100));
97
+ }
98
+
99
+ await env.sendMessage('clawmini-lite.js subagents stop stop-sub', {
100
+ chat: 'stop-chat',
101
+ agent: 'debug-agent',
102
+ });
103
+ await chat.waitForMessage(commandWith('Subagent stop-sub stopped'));
104
+
105
+ // Stop transiently flips the tracker to 'failed' (router writes it
106
+ // before calling session.stop()), but executeDirectMessage swallows
107
+ // the AbortError and executeSubagent's success path then writes
108
+ // 'completed'. The tracker therefore settles at 'completed' — not
109
+ // 'active' (the sleep would still be running) and not 'failed' (the
110
+ // post-abort update wins the race). Assert the settled value directly
111
+ // so a regression to either other state surfaces.
112
+ let finalStatus: string | undefined;
113
+ for (let i = 0; i < 50; i++) {
114
+ const settings = env.getChatSettings('stop-chat') as {
115
+ subagents?: Record<string, { status: string }>;
116
+ };
117
+ finalStatus = settings.subagents?.['stop-sub']?.status;
118
+ if (finalStatus === 'completed') break;
119
+ await new Promise((r) => setTimeout(r, 100));
120
+ }
121
+ expect(finalStatus).toBe('completed');
122
+
123
+ // The aborted command must never have reached its echo. The literal text
124
+ // "should-not-print" appears in the debug template's prefix echo of the
125
+ // spawn command, so we only count exact standalone-line matches — which
126
+ // only come from the eval output itself.
127
+ expect(
128
+ chat.messageBuffer.some(
129
+ (m) =>
130
+ typeof (m as { stdout?: string }).stdout === 'string' &&
131
+ /^should-not-print$/m.test((m as { stdout?: string }).stdout!)
132
+ )
133
+ ).toBe(false);
134
+ }, 30000);
135
+
136
+ it('delete removes the subagent tracker from chat settings', async () => {
137
+ await env.addChat('delete-chat', 'debug-agent');
138
+ chat = await env.connect('delete-chat');
139
+
140
+ await env.sendMessage(
141
+ 'clawmini-lite.js subagents spawn --id del-sub --async "echo delete-me"',
142
+ { chat: 'delete-chat', agent: 'debug-agent' }
143
+ );
144
+ await chat.waitForMessage(
145
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('delete-me'))
146
+ );
147
+
148
+ let settings = env.getChatSettings('delete-chat') as {
149
+ subagents?: Record<string, unknown>;
150
+ };
151
+ expect(settings.subagents?.['del-sub']).toBeTruthy();
152
+
153
+ await env.sendMessage('clawmini-lite.js subagents delete del-sub', {
154
+ chat: 'delete-chat',
155
+ agent: 'debug-agent',
156
+ });
157
+ await chat.waitForMessage(commandWith('Subagent del-sub deleted'));
158
+
159
+ for (let i = 0; i < 50; i++) {
160
+ settings = env.getChatSettings('delete-chat') as {
161
+ subagents?: Record<string, unknown>;
162
+ };
163
+ if (!settings.subagents?.['del-sub']) break;
164
+ await new Promise((r) => setTimeout(r, 100));
165
+ }
166
+ expect(settings.subagents?.['del-sub']).toBeUndefined();
167
+ }, 30000);
168
+
169
+ it('list returns all subagents spawned by the current agent', async () => {
170
+ await env.addChat('list-chat', 'debug-agent');
171
+ chat = await env.connect('list-chat');
172
+
173
+ await env.sendMessage('clawmini-lite.js subagents spawn --id list-a --async "echo a"', {
174
+ chat: 'list-chat',
175
+ agent: 'debug-agent',
176
+ });
177
+ await chat.waitForMessage(
178
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('[DEBUG] echo a:'))
179
+ );
180
+
181
+ await env.sendMessage('clawmini-lite.js subagents spawn --id list-b --async "echo b"', {
182
+ chat: 'list-chat',
183
+ agent: 'debug-agent',
184
+ });
185
+ await chat.waitForMessage(
186
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('[DEBUG] echo b:'))
187
+ );
188
+
189
+ await env.sendMessage('clawmini-lite.js subagents list --json', {
190
+ chat: 'list-chat',
191
+ agent: 'debug-agent',
192
+ });
193
+ const listLog = await chat.waitForMessage(
194
+ commandMatching(
195
+ (m) =>
196
+ !m.subagentId &&
197
+ m.stdout.includes('subagents list --json:') &&
198
+ m.stdout.includes('"id": "list-a"') &&
199
+ m.stdout.includes('"id": "list-b"')
200
+ )
201
+ );
202
+ expect(listLog.stdout).toContain('"status": "completed"');
203
+ }, 30000);
204
+
205
+ it('tail returns the subagent chat log to the caller', async () => {
206
+ await env.addChat('tail-chat', 'debug-agent');
207
+ chat = await env.connect('tail-chat');
208
+
209
+ await env.sendMessage(
210
+ 'clawmini-lite.js subagents spawn --id tail-sub --async "echo tail-content"',
211
+ { chat: 'tail-chat', agent: 'debug-agent' }
212
+ );
213
+ await chat.waitForMessage(
214
+ commandMatching((m) => !!m.subagentId && m.stdout.includes('tail-content'))
215
+ );
216
+
217
+ await env.sendMessage('clawmini-lite.js subagents tail tail-sub --json', {
218
+ chat: 'tail-chat',
219
+ agent: 'debug-agent',
220
+ });
221
+
222
+ // `subagents tail --json` prints the subagent's chat log as JSONL. The
223
+ // parent's debug wrapper echoes `[DEBUG] <cmd>:` then the command stdout,
224
+ // so the subagent's log messages appear in this chat's command log.
225
+ const tailLog = await chat.waitForMessage(
226
+ commandMatching(
227
+ (m) =>
228
+ !m.subagentId &&
229
+ m.stdout.includes('subagents tail tail-sub --json:') &&
230
+ m.stdout.includes('tail-content')
231
+ )
232
+ );
233
+ expect(tailLog.exitCode).toBe(0);
234
+ // Must include both the user prompt and an agent reply from the subagent's
235
+ // own log, not just an echo of the tail command itself.
236
+ expect(tailLog.stdout).toMatch(/"role":\s*"user"/);
237
+ expect(tailLog.stdout).toMatch(/"role":\s*"agent"/);
238
+ }, 30000);
239
+
240
+ it('list from a subagent returns only its own children, not peers or parents', async () => {
241
+ await env.addChat('nested-list-chat', 'debug-agent');
242
+ chat = await env.connect('nested-list-chat');
243
+
244
+ // Parent spawns outer-sub. outer-sub itself spawns inner-sub (its child)
245
+ // and then calls `subagents list --json`. From outer-sub's perspective,
246
+ // only inner-sub should appear (parentId === outer-sub).
247
+ await env.sendMessage(
248
+ 'clawmini-lite.js subagents spawn --id outer-sub --async "clawmini-lite.js subagents spawn --id inner-sub --async \\"echo inner-done\\" && sleep 1 && clawmini-lite.js subagents list --json"',
249
+ { chat: 'nested-list-chat', agent: 'debug-agent' }
250
+ );
251
+
252
+ // outer-sub's own command log (subagentId=outer-sub) must contain the
253
+ // JSON output of its own `list` call, showing inner-sub.
254
+ const outerLog = await chat.waitForMessage(
255
+ commandMatching(
256
+ (m) => !!m.subagentId && m.stdout.includes('"id": "inner-sub"')
257
+ )
258
+ );
259
+ expect(outerLog.stdout).toContain('"id": "inner-sub"');
260
+ // outer-sub is its own parent, not its own child — must not appear in
261
+ // its own list output.
262
+ expect(outerLog.stdout).not.toMatch(/"id":\s*"outer-sub"/);
263
+
264
+ // Parent's view: list must include outer-sub (direct child) but not
265
+ // inner-sub (grandchild — parentId=outer-sub, not undefined).
266
+ await env.sendMessage('clawmini-lite.js subagents list --json', {
267
+ chat: 'nested-list-chat',
268
+ agent: 'debug-agent',
269
+ });
270
+ const parentLog = await chat.waitForMessage(
271
+ commandMatching(
272
+ (m) =>
273
+ !m.subagentId &&
274
+ m.stdout.includes('subagents list --json:') &&
275
+ m.stdout.includes('"id": "outer-sub"')
276
+ )
277
+ );
278
+ expect(parentLog.stdout).toContain('"id": "outer-sub"');
279
+ expect(parentLog.stdout).not.toMatch(/"id":\s*"inner-sub"/);
280
+ }, 30000);
281
+
282
+ it('sync spawn blocks the caller and returns <subagent_output> inline', async () => {
283
+ await env.addChat('sync-chat', 'debug-agent');
284
+ chat = await env.connect('sync-chat');
285
+
286
+ // The router's default is `isAsync = input.async ?? depth === 0`, so a
287
+ // spawn WITHOUT --async runs sync only when depth > 0. We trigger this
288
+ // by having an outer subagent spawn sync-inner without --async. The
289
+ // inner call's CLI then polls subagentWait and prints the completed
290
+ // output wrapped in <subagent_output>...</subagent_output> tags.
291
+ await env.sendMessage(
292
+ 'clawmini-lite.js subagents spawn --id sync-outer --async "clawmini-lite.js subagents spawn --id sync-inner \\"echo sync-value\\""',
293
+ { chat: 'sync-chat', agent: 'debug-agent' }
294
+ );
295
+
296
+ // sync-outer's stdout (subagentId=sync-outer) should contain both the
297
+ // <subagent_output> wrapper and the inner's echo value.
298
+ const outerLog = await chat.waitForMessage(
299
+ commandMatching(
300
+ (m) =>
301
+ !!m.subagentId &&
302
+ m.stdout.includes('<subagent_output>') &&
303
+ m.stdout.includes('sync-value')
304
+ )
305
+ );
306
+ expect(outerLog.stdout).toContain('</subagent_output>');
307
+ }, 30000);
308
+
309
+ it('wait called before subagent finishes blocks until completion', async () => {
310
+ await env.addChat('wait-before-chat', 'debug-agent');
311
+ chat = await env.connect('wait-before-chat');
312
+
313
+ // Chain spawn && wait in the same shell so `wait` is called while the
314
+ // subagent is still sleeping. This exercises the event-iterator path
315
+ // of subagentWait (not the synchronous early-return hit by the
316
+ // `wait returns the completed subagent output` test above).
317
+ await env.sendMessage(
318
+ 'clawmini-lite.js subagents spawn --id wait-before-sub --async "sleep 3 && echo slow-done" && clawmini-lite.js subagents wait wait-before-sub',
319
+ { chat: 'wait-before-chat', agent: 'debug-agent' }
320
+ );
321
+
322
+ // The parent's wrapped stdout contains the wait-returned subagent
323
+ // output (the debug template's own echo of `sleep 3 && echo slow-done`).
324
+ // That specific prefix only appears when wait actually returned with
325
+ // the subagent's agent-role content — it does not appear in the
326
+ // parent's own debug-echo of the outer command line.
327
+ const waitLog = await chat.waitForMessage(
328
+ commandMatching(
329
+ (m) =>
330
+ !m.subagentId &&
331
+ m.stdout.includes('[DEBUG] sleep 3 && echo slow-done:')
332
+ ),
333
+ 20000
334
+ );
335
+ expect(waitLog.exitCode).toBe(0);
336
+ }, 30000);
337
+
338
+ it('notifies the parent session when an async subagent completes during parent work', async () => {
339
+ await env.addChat('notify-busy-chat', 'debug-agent');
340
+ chat = await env.connect('notify-busy-chat');
341
+
342
+ // Parent spawns a fast subagent then sleeps longer than the subagent
343
+ // takes. The subagent finishes while the parent's shell command is
344
+ // still running and the parent never calls `wait`. Current behavior:
345
+ // executeSubagent injects a <notification> message into the parent's
346
+ // session via executeDirectMessage (subagent-utils.ts:101).
347
+ await env.sendMessage(
348
+ 'clawmini-lite.js subagents spawn --id notify-busy-sub --async "echo notify-done-early" && sleep 2 && echo parent-still-working',
349
+ { chat: 'notify-busy-chat', agent: 'debug-agent' }
350
+ );
351
+
352
+ // The notification text must land in the parent chat regardless of
353
+ // which message role carries it (debug-agent may shell-eval the
354
+ // notification body — see the TODO at subagent-utils.ts:98-100).
355
+ await chat.waitForMessage(
356
+ (m) => JSON.stringify(m).includes('Subagent notify-busy-sub completed'),
357
+ 15000
358
+ );
359
+ }, 30000);
360
+
361
+ it('notifies the parent session when an async subagent completes after parent is idle', async () => {
362
+ await env.addChat('notify-idle-chat', 'debug-agent');
363
+ chat = await env.connect('notify-idle-chat');
364
+
365
+ // Parent spawns a slow subagent and the parent's shell command
366
+ // returns immediately — the parent is idle by the time the subagent
367
+ // finishes ~2s later. The async completion path still injects the
368
+ // notification into the parent session.
369
+ await env.sendMessage(
370
+ 'clawmini-lite.js subagents spawn --id notify-idle-sub --async "sleep 2 && echo late-done"',
371
+ { chat: 'notify-idle-chat', agent: 'debug-agent' }
372
+ );
373
+ // Confirm parent has returned (spawn CLI prints this line immediately
374
+ // after registering the subagent; it does NOT wait for completion).
375
+ await chat.waitForMessage(
376
+ commandMatching(
377
+ (m) =>
378
+ !m.subagentId &&
379
+ m.stdout.includes('Subagent spawned successfully with ID: notify-idle-sub')
380
+ )
381
+ );
382
+
383
+ // Then, once the subagent finishes, the notification arrives.
384
+ await chat.waitForMessage(
385
+ (m) => JSON.stringify(m).includes('Subagent notify-idle-sub completed'),
386
+ 15000
387
+ );
388
+ }, 30000);
389
+
390
+ it('waiting on two subagents sequentially returns each one\'s output inline', async () => {
391
+ await env.addChat('two-wait-chat', 'debug-agent');
392
+ chat = await env.connect('two-wait-chat');
393
+
394
+ // Spawn two async subagents, then wait on each in turn. Each wait
395
+ // call prints its OWN subagent's last agent-role message via the
396
+ // CLI's stdout — so the parent's wrapped stdout must contain both
397
+ // subagents' debug-template echoes, in order.
398
+ await env.sendMessage(
399
+ 'clawmini-lite.js subagents spawn --id two-a --async "echo both-output-a" && clawmini-lite.js subagents spawn --id two-b --async "echo both-output-b" && clawmini-lite.js subagents wait two-a && clawmini-lite.js subagents wait two-b',
400
+ { chat: 'two-wait-chat', agent: 'debug-agent' }
401
+ );
402
+
403
+ // `[DEBUG] echo both-output-a:` only appears inside the subagent's
404
+ // own log (printed back by wait), not in the parent's outer echo of
405
+ // the chained command — same logic as the `wait-before` test above.
406
+ const log = await chat.waitForMessage(
407
+ commandMatching(
408
+ (m) =>
409
+ !m.subagentId &&
410
+ m.stdout.includes('[DEBUG] echo both-output-a:') &&
411
+ m.stdout.includes('[DEBUG] echo both-output-b:')
412
+ ),
413
+ 20000
414
+ );
415
+ const aIdx = log.stdout.indexOf('[DEBUG] echo both-output-a:');
416
+ const bIdx = log.stdout.indexOf('[DEBUG] echo both-output-b:');
417
+ expect(aIdx).toBeGreaterThanOrEqual(0);
418
+ // wait(a) runs before wait(b), so A's wait output appears first.
419
+ expect(bIdx).toBeGreaterThan(aIdx);
420
+ }, 30000);
421
+
422
+ it('list --blocking returns empty for the main agent', async () => {
423
+ await env.addChat('blocking-root-chat', 'debug-agent');
424
+ chat = await env.connect('blocking-root-chat');
425
+
426
+ // Main agent spawns an active subagent, then lists with --blocking.
427
+ // Per the router, main-agent `--blocking` is always empty — blocking
428
+ // is meaningful only for subagents deciding whether to wait for their
429
+ // own children.
430
+ await env.sendMessage(
431
+ 'clawmini-lite.js subagents spawn --id block-root-sub --async "sleep 2 && echo done"',
432
+ { chat: 'blocking-root-chat', agent: 'debug-agent' }
433
+ );
434
+ for (let i = 0; i < 50; i++) {
435
+ const settings = env.getChatSettings('blocking-root-chat') as {
436
+ subagents?: Record<string, { status: string }>;
437
+ };
438
+ if (settings.subagents?.['block-root-sub']?.status === 'active') break;
439
+ await new Promise((r) => setTimeout(r, 100));
440
+ }
441
+
442
+ await env.sendMessage('clawmini-lite.js subagents list --blocking --json', {
443
+ chat: 'blocking-root-chat',
444
+ agent: 'debug-agent',
445
+ });
446
+ const log = await chat.waitForMessage(
447
+ commandMatching(
448
+ (m) => !m.subagentId && m.stdout.includes('subagents list --blocking --json:')
449
+ )
450
+ );
451
+ // Extract the JSON body between the first `[` and last `]` — the CLI
452
+ // prints "[\n ...\n]" for an empty or populated array.
453
+ const jsonStart = log.stdout.indexOf('[', log.stdout.indexOf(':'));
454
+ const jsonEnd = log.stdout.lastIndexOf(']');
455
+ const body = log.stdout.slice(jsonStart, jsonEnd + 1).trim();
456
+ expect(body).toBe('[]');
457
+ }, 30000);
458
+
459
+ it('list --blocking returns only active children when called from a subagent', async () => {
460
+ await env.addChat('blocking-sub-chat', 'debug-agent');
461
+ chat = await env.connect('blocking-sub-chat');
462
+
463
+ // Outer subagent spawns two children: one instant (will be completed),
464
+ // one sleeping (will still be active). `list --blocking --json` from
465
+ // outer must return ONLY block-active, not block-done.
466
+ await env.sendMessage(
467
+ [
468
+ 'clawmini-lite.js subagents spawn --id block-outer --async',
469
+ '"clawmini-lite.js subagents spawn --id block-done --async \\"echo done-fast\\"',
470
+ '&& clawmini-lite.js subagents spawn --id block-active --async \\"sleep 3 && echo late\\"',
471
+ '&& sleep 1',
472
+ '&& clawmini-lite.js subagents list --blocking --json"',
473
+ ].join(' '),
474
+ { chat: 'blocking-sub-chat', agent: 'debug-agent' }
475
+ );
476
+
477
+ const outerLog = await chat.waitForMessage(
478
+ commandMatching(
479
+ (m) =>
480
+ m.subagentId === 'block-outer' && m.stdout.includes('"id": "block-active"')
481
+ ),
482
+ 20000
483
+ );
484
+ expect(outerLog.stdout).toContain('"id": "block-active"');
485
+ expect(outerLog.stdout).not.toMatch(/"id":\s*"block-done"/);
486
+ }, 30000);
487
+
488
+ it('spawn without --id auto-generates a UUID that the CLI reports back', async () => {
489
+ await env.addChat('auto-id-chat', 'debug-agent');
490
+ chat = await env.connect('auto-id-chat');
491
+
492
+ await env.sendMessage(
493
+ 'clawmini-lite.js subagents spawn --async "echo auto-id-output"',
494
+ { chat: 'auto-id-chat', agent: 'debug-agent' }
495
+ );
496
+
497
+ // CLI prints "Subagent spawned successfully with ID: <uuid>" on the
498
+ // parent's stdout. Capture the UUID and verify a subagent tracker
499
+ // exists for it in chat settings.
500
+ const announce = await chat.waitForMessage(
501
+ commandMatching(
502
+ (m) =>
503
+ !m.subagentId && /Subagent spawned successfully with ID: [0-9a-f-]{36}/.test(m.stdout)
504
+ )
505
+ );
506
+ const match = announce.stdout.match(/Subagent spawned successfully with ID: ([0-9a-f-]{36})/);
507
+ expect(match).not.toBeNull();
508
+ const generatedId = match![1]!;
509
+
510
+ // Wait for the subagent's own output to land, then verify its tracker.
511
+ await chat.waitForMessage(
512
+ commandMatching((m) => m.subagentId === generatedId && m.stdout.includes('auto-id-output'))
513
+ );
514
+ const settings = env.getChatSettings('auto-id-chat') as {
515
+ subagents?: Record<string, { id?: string }>;
516
+ };
517
+ expect(settings.subagents?.[generatedId]?.id).toBe(generatedId);
518
+ }, 30000);
519
+
520
+ it('spawn --agent <other> routes the subagent through a different agent', async () => {
521
+ await env.addChat('alt-agent-chat', 'debug-agent');
522
+ chat = await env.connect('alt-agent-chat');
523
+
524
+ // Define a second agent whose template prefixes output with [ALT] so
525
+ // we can verify the spawn actually routed through it rather than
526
+ // inheriting the parent's debug-agent.
527
+ await env.addAgent('alt-agent');
528
+ env.writeAgentSettings('alt-agent', {
529
+ commands: {
530
+ new: 'echo "[ALT] $CLAW_CLI_MESSAGE:" && eval "$CLAW_CLI_MESSAGE"',
531
+ append: 'echo "[ALT] $CLAW_CLI_MESSAGE:" && eval "$CLAW_CLI_MESSAGE"',
532
+ getSessionId: 'node -e "console.log(Math.random().toString(36).slice(2, 10))"',
533
+ },
534
+ });
535
+
536
+ await env.sendMessage(
537
+ 'clawmini-lite.js subagents spawn --id alt-sub --agent alt-agent --async "echo alt-output"',
538
+ { chat: 'alt-agent-chat', agent: 'debug-agent' }
539
+ );
540
+
541
+ // The subagent's command_log should show the [ALT] prefix, proving
542
+ // alt-agent's template ran rather than debug-agent's [DEBUG] one.
543
+ const subLog = await chat.waitForMessage(
544
+ commandMatching(
545
+ (m) => m.subagentId === 'alt-sub' && m.stdout.includes('[ALT] echo alt-output:')
546
+ )
547
+ );
548
+ expect(subLog.stdout).toContain('alt-output');
549
+ expect(subLog.stdout).not.toContain('[DEBUG]');
550
+
551
+ const settings = env.getChatSettings('alt-agent-chat') as {
552
+ subagents?: Record<string, { agentId?: string }>;
553
+ };
554
+ expect(settings.subagents?.['alt-sub']?.agentId).toBe('alt-agent');
555
+ }, 30000);
556
+
557
+ it('spawn with a duplicate --id is rejected', async () => {
558
+ await env.addChat('dup-id-chat', 'debug-agent');
559
+ chat = await env.connect('dup-id-chat');
560
+
561
+ await env.sendMessage(
562
+ 'clawmini-lite.js subagents spawn --id dup-sub --async "echo first"',
563
+ { chat: 'dup-id-chat', agent: 'debug-agent' }
564
+ );
565
+ await chat.waitForMessage(
566
+ commandMatching((m) => m.subagentId === 'dup-sub' && m.stdout.includes('first'))
567
+ );
568
+
569
+ // Second spawn with the same id must hit the router's duplicate-id
570
+ // guard (`Subagent ID already exists`). The CLI surfaces the TRPC
571
+ // error via stderr + exit 1, so we match on the serialized message.
572
+ await env.sendMessage(
573
+ 'clawmini-lite.js subagents spawn --id dup-sub --async "echo second"',
574
+ { chat: 'dup-id-chat', agent: 'debug-agent' }
575
+ );
576
+ await chat.waitForMessage(
577
+ (m) => JSON.stringify(m).includes('Subagent ID already exists'),
578
+ 15000
579
+ );
580
+
581
+ // The duplicate spawn must not have overwritten the first: there's
582
+ // still exactly one 'first'-producing subagent and no 'second' output.
583
+ expect(
584
+ chat.messageBuffer.some(
585
+ (m) =>
586
+ (m as { subagentId?: string }).subagentId === 'dup-sub' &&
587
+ typeof (m as { stdout?: string }).stdout === 'string' &&
588
+ (m as { stdout?: string }).stdout!.includes('second')
589
+ )
590
+ ).toBe(false);
591
+ }, 30000);
592
+
593
+ it('send without --async blocks the caller and returns <subagent_output>', async () => {
594
+ await env.addChat('send-sync-chat', 'debug-agent');
595
+ chat = await env.connect('send-sync-chat');
596
+
597
+ // Spawn a child and let it complete first so the second `send` is
598
+ // exercising the wake-a-completed-child path, not the initial spawn.
599
+ await env.sendMessage(
600
+ 'clawmini-lite.js subagents spawn --id send-sync-sub --async "echo sync-initial"',
601
+ { chat: 'send-sync-chat', agent: 'debug-agent' }
602
+ );
603
+ await chat.waitForMessage(
604
+ commandMatching(
605
+ (m) => m.subagentId === 'send-sync-sub' && m.stdout.includes('sync-initial')
606
+ )
607
+ );
608
+
609
+ // `send` without --async: the CLI polls subagentWait and prints the
610
+ // subagent's agent-role output wrapped in <subagent_output> tags.
611
+ await env.sendMessage(
612
+ "clawmini-lite.js subagents send send-sync-sub -p 'echo sync-send-output'",
613
+ { chat: 'send-sync-chat', agent: 'debug-agent' }
614
+ );
615
+ const log = await chat.waitForMessage(
616
+ commandMatching(
617
+ (m) =>
618
+ !m.subagentId &&
619
+ m.stdout.includes('<subagent_output>') &&
620
+ m.stdout.includes('sync-send-output')
621
+ ),
622
+ 20000
623
+ );
624
+ expect(log.stdout).toContain('</subagent_output>');
625
+ }, 30000);
626
+
627
+ it('send wakes a completed child, flipping status active → completed', async () => {
628
+ await env.addChat('send-wake-chat', 'debug-agent');
629
+ chat = await env.connect('send-wake-chat');
630
+
631
+ await env.sendMessage(
632
+ 'clawmini-lite.js subagents spawn --id wake-sub --async "echo initial"',
633
+ { chat: 'send-wake-chat', agent: 'debug-agent' }
634
+ );
635
+ await chat.waitForMessage(
636
+ commandMatching((m) => m.subagentId === 'wake-sub' && m.stdout.includes('initial'))
637
+ );
638
+ // Child is now completed — confirm before sending.
639
+ for (let i = 0; i < 50; i++) {
640
+ const s = env.getChatSettings('send-wake-chat') as {
641
+ subagents?: Record<string, { status: string }>;
642
+ };
643
+ if (s.subagents?.['wake-sub']?.status === 'completed') break;
644
+ await new Promise((r) => setTimeout(r, 100));
645
+ }
646
+
647
+ await env.sendMessage(
648
+ "clawmini-lite.js subagents send wake-sub --async -p 'echo after-wake'",
649
+ { chat: 'send-wake-chat', agent: 'debug-agent' }
650
+ );
651
+
652
+ // The child's command_log should contain the wake-up output (runs via
653
+ // `append`, so it has the `[DEBUG <sessionId>]` prefix).
654
+ const followUp = await chat.waitForMessage(
655
+ commandMatching((m) => m.subagentId === 'wake-sub' && m.stdout.includes('after-wake'))
656
+ );
657
+ expect(followUp.stdout).toMatch(/\[DEBUG [^\]]+\] echo after-wake:/);
658
+
659
+ // And the tracker eventually settles back on completed.
660
+ let status: string | undefined;
661
+ for (let i = 0; i < 50; i++) {
662
+ const s = env.getChatSettings('send-wake-chat') as {
663
+ subagents?: Record<string, { status: string }>;
664
+ };
665
+ status = s.subagents?.['wake-sub']?.status;
666
+ if (status === 'completed') break;
667
+ await new Promise((r) => setTimeout(r, 100));
668
+ }
669
+ expect(status).toBe('completed');
670
+ }, 30000);
671
+
672
+ it('send queues a second message while the child is still running', async () => {
673
+ await env.addChat('send-queue-chat', 'debug-agent');
674
+ chat = await env.connect('send-queue-chat');
675
+
676
+ // Start a slow initial command so the follow-up `send` lands while
677
+ // the first turn is still in flight. The task scheduler keys queues
678
+ // by `rootChatId:sessionId`, so the second handleMessage call must
679
+ // wait for the first before running.
680
+ await env.sendMessage(
681
+ 'clawmini-lite.js subagents spawn --id queue-sub --async "sleep 2 && echo queued-first"',
682
+ { chat: 'send-queue-chat', agent: 'debug-agent' }
683
+ );
684
+ // Wait for active, then immediately send the follow-up.
685
+ for (let i = 0; i < 50; i++) {
686
+ const s = env.getChatSettings('send-queue-chat') as {
687
+ subagents?: Record<string, { status: string }>;
688
+ };
689
+ if (s.subagents?.['queue-sub']?.status === 'active') break;
690
+ await new Promise((r) => setTimeout(r, 100));
691
+ }
692
+
693
+ await env.sendMessage(
694
+ "clawmini-lite.js subagents send queue-sub --async -p 'echo queued-second'",
695
+ { chat: 'send-queue-chat', agent: 'debug-agent' }
696
+ );
697
+
698
+ // Both outputs must land, and the first must come before the second.
699
+ await chat.waitForMessage(
700
+ commandMatching((m) => m.subagentId === 'queue-sub' && m.stdout.includes('queued-first')),
701
+ 20000
702
+ );
703
+ await chat.waitForMessage(
704
+ commandMatching((m) => m.subagentId === 'queue-sub' && m.stdout.includes('queued-second')),
705
+ 20000
706
+ );
707
+ const firstIdx = chat.messageBuffer.findIndex(
708
+ (m) =>
709
+ (m as { subagentId?: string; stdout?: string }).subagentId === 'queue-sub' &&
710
+ typeof (m as { stdout?: string }).stdout === 'string' &&
711
+ (m as { stdout?: string }).stdout!.includes('queued-first')
712
+ );
713
+ const secondIdx = chat.messageBuffer.findIndex(
714
+ (m) =>
715
+ (m as { subagentId?: string; stdout?: string }).subagentId === 'queue-sub' &&
716
+ typeof (m as { stdout?: string }).stdout === 'string' &&
717
+ (m as { stdout?: string }).stdout!.includes('queued-second')
718
+ );
719
+ expect(firstIdx).toBeGreaterThanOrEqual(0);
720
+ expect(secondIdx).toBeGreaterThan(firstIdx);
721
+ }, 40000);
722
+
723
+ it('async completion notification wakes the parent agent to run a new turn', async () => {
724
+ await env.addChat('wake-parent-chat', 'debug-agent');
725
+ chat = await env.connect('wake-parent-chat');
726
+
727
+ // The parent's shell command is just the spawn — it returns immediately
728
+ // after the subagent is registered. When the subagent finishes later,
729
+ // executeSubagent injects `<notification>Subagent … completed.</notification>`
730
+ // as a new message into the parent's session, which causes the parent's
731
+ // debug-agent command to RUN against that notification content. The
732
+ // result is a fresh command_log (no subagentId) with the [DEBUG …]
733
+ // prefix wrapping the notification text.
734
+ await env.sendMessage(
735
+ 'clawmini-lite.js subagents spawn --id wake-parent-sub --async "echo wake-content"',
736
+ { chat: 'wake-parent-chat', agent: 'debug-agent' }
737
+ );
738
+ await chat.waitForMessage(
739
+ commandMatching(
740
+ (m) => m.subagentId === 'wake-parent-sub' && m.stdout.includes('wake-content')
741
+ )
742
+ );
743
+
744
+ // A command_log with no subagentId and the [DEBUG …] prefix wrapping
745
+ // `<notification>` text is the proof the parent actually ran — the raw
746
+ // system message alone would not carry that prefix.
747
+ const wakeLog = await chat.waitForMessage(
748
+ commandMatching(
749
+ (m) =>
750
+ !m.subagentId &&
751
+ /\[DEBUG[^\]]*\] <notification>Subagent wake-parent-sub completed/.test(m.stdout)
752
+ ),
753
+ 15000
754
+ );
755
+ expect(wakeLog.stdout).toContain('wake-parent-sub completed');
756
+ }, 30000);
757
+
758
+ it('operations on a nonexistent subagent id return NOT_FOUND', async () => {
759
+ await env.addChat('missing-id-chat', 'debug-agent');
760
+ chat = await env.connect('missing-id-chat');
761
+
762
+ // Each of these CLI calls should hit the router and fail with a
763
+ // NOT_FOUND error surfaced back to the caller's stderr. We rely on
764
+ // the serialized message — both 'Subagent not found' and the CLI's
765
+ // 'Error:' prefix land in the command log.
766
+ const ops = [
767
+ 'clawmini-lite.js subagents wait ghost',
768
+ 'clawmini-lite.js subagents stop ghost',
769
+ 'clawmini-lite.js subagents delete ghost',
770
+ 'clawmini-lite.js subagents tail ghost --json',
771
+ "clawmini-lite.js subagents send ghost --async -p 'echo x'",
772
+ ];
773
+ for (const op of ops) {
774
+ await env.sendMessage(op, { chat: 'missing-id-chat', agent: 'debug-agent' });
775
+ const log = await chat.waitForMessage(
776
+ commandMatching((m) => !m.subagentId && m.stdout.includes(`${op}:`)),
777
+ 15000
778
+ );
779
+ expect(JSON.stringify(log)).toMatch(/Subagent not found/);
780
+ }
781
+ }, 60000);
782
+ });