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
@@ -171,6 +171,7 @@ describe('Daemon TRPC Router', () => {
171
171
  undefined,
172
172
  false,
173
173
  undefined,
174
+ undefined,
174
175
  undefined
175
176
  );
176
177
  expect((fs as any).default.mkdir).not.toHaveBeenCalled();
@@ -11,7 +11,9 @@ vi.mock('../../shared/chats.js', () => ({
11
11
  vi.mock('../../shared/workspace.js', () => ({
12
12
  getWorkspaceRoot: vi.fn().mockReturnValue('/mock/workspace'),
13
13
  getClawminiDir: vi.fn().mockReturnValue('/mock/.clawmini'),
14
- readPolicies: vi.fn().mockResolvedValue({
14
+ getActiveEnvironmentInfo: vi.fn().mockResolvedValue(null),
15
+ readEnvironment: vi.fn().mockResolvedValue(null),
16
+ readPoliciesForPath: vi.fn().mockResolvedValue({
15
17
  policies: {
16
18
  'test-cmd': {
17
19
  command: 'echo',
@@ -30,7 +32,7 @@ vi.mock('../policy-request-service.js', () => {
30
32
  PolicyRequestService: class {
31
33
  async createRequest() {
32
34
  return {
33
- id: 'req-123',
35
+ id: 'REQ-123',
34
36
  commandName: 'test-cmd',
35
37
  args: ['arg1', 'arg2'],
36
38
  fileMappings: {
@@ -101,7 +103,7 @@ describe('createPolicyRequest preview message', () => {
101
103
  },
102
104
  });
103
105
 
104
- expect(result.id).toBe('req-123');
106
+ expect(result.id).toBe('REQ-123');
105
107
 
106
108
  expect(chats.appendMessage).toHaveBeenCalledTimes(1);
107
109
  const callArgs = vi.mocked(chats.appendMessage).mock.calls[0]!;
@@ -114,14 +116,14 @@ describe('createPolicyRequest preview message', () => {
114
116
  // Assert preview content format
115
117
  const content = logMsg.content;
116
118
  expect(content).toContain('Sandbox Policy Request: test-cmd');
117
- expect(content).toContain('ID: req-123');
119
+ expect(content).toContain('ID: REQ-123');
118
120
  expect(content).toContain('Args: arg1 arg2');
119
121
 
120
122
  expect(content).toContain('File [file1]:\n' + shortContent);
121
123
 
122
124
  // The long file should be truncated to 500 chars + suffix
123
125
  expect(content).toContain('File [file2]:\n' + 'A'.repeat(500) + '\n... (truncated)');
124
- expect(content).toContain('Use /approve req-123 or /reject req-123 [reason]');
126
+ expect(content).toContain('Use /approve REQ-123 or /reject REQ-123 [reason]');
125
127
  });
126
128
 
127
129
  it('should create an auto-approved request and execute it immediately', async () => {
@@ -8,7 +8,7 @@ import {
8
8
  readChatSettings,
9
9
  writeChatSettings,
10
10
  } from '../../shared/workspace.js';
11
- import { cronManager } from '../cron.js';
11
+ import { cronManager, normalizeJob } from '../cron.js';
12
12
  import type { z } from 'zod';
13
13
  import type { CronJobSchema } from '../../shared/config.js';
14
14
 
@@ -129,17 +129,18 @@ export async function listCronJobsShared(chatId: string) {
129
129
  }
130
130
 
131
131
  export async function addCronJobShared(chatId: string, job: z.infer<typeof CronJobSchema>) {
132
+ const normalized = normalizeJob(job);
132
133
  const settings = (await readChatSettings(chatId)) || {};
133
134
  const cronJobs = settings.jobs ?? [];
134
- const existingIndex = cronJobs.findIndex((j) => j.id === job.id);
135
+ const existingIndex = cronJobs.findIndex((j) => j.id === normalized.id);
135
136
  if (existingIndex >= 0) {
136
- cronJobs[existingIndex] = job;
137
+ cronJobs[existingIndex] = normalized;
137
138
  } else {
138
- cronJobs.push(job);
139
+ cronJobs.push(normalized);
139
140
  }
140
141
  settings.jobs = cronJobs;
141
142
  await writeChatSettings(chatId, settings);
142
- cronManager.scheduleJob(chatId, job);
143
+ cronManager.scheduleJob(chatId, normalized);
143
144
  return { success: true };
144
145
  }
145
146
 
@@ -8,10 +8,24 @@ import { on } from 'node:events';
8
8
  import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED } from '../events.js';
9
9
  import { createAgentSession } from '../agent/agent-session.js';
10
10
  import { executeSubagent, getSubagentDepth } from './subagent-utils.js';
11
- import type { SubagentTracker } from '../../shared/config.js';
11
+ import { incrementSubagent, decrementSubagent } from '../agent/turn-registry.js';
12
+ import type { ChatSettings, SubagentTracker } from '../../shared/config.js';
12
13
 
13
14
  const MAX_SUBAGENT_DEPTH = 2;
14
15
 
16
+ function assertSubagentAccess(
17
+ settings: ChatSettings | null | undefined,
18
+ subagentId: string,
19
+ callerSubagentId: string | undefined
20
+ ): SubagentTracker {
21
+ const sub = settings?.subagents?.[subagentId];
22
+ if (!sub) throw new TRPCError({ code: 'NOT_FOUND', message: 'Subagent not found' });
23
+ if (sub.parentId !== callerSubagentId) {
24
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'Subagent is not a child of the caller' });
25
+ }
26
+ return sub;
27
+ }
28
+
15
29
  export const subagentSpawn = apiProcedure
16
30
  .input(
17
31
  z.object({
@@ -26,54 +40,65 @@ export const subagentSpawn = apiProcedure
26
40
  const chatId = ctx.tokenPayload.chatId;
27
41
  const parentAgentId = ctx.tokenPayload.agentId;
28
42
  const parentId = ctx.tokenPayload.subagentId;
43
+ const parentTurnId = ctx.tokenPayload.turnId;
29
44
 
30
45
  const id = input.subagentId || randomUUID();
31
46
  const sessionId = randomUUID();
32
47
  const agentId = input.targetAgentId || parentAgentId;
33
48
  let depth = 0;
34
49
 
35
- await updateChatSettings(chatId, (settings) => {
36
- settings.subagents = settings.subagents || {};
50
+ // Increment synchronously before any await so a sibling subagent's
51
+ // completion cannot decrement the parent's counter to zero (firing
52
+ // turnEnded) during the window before executeSubagent is called.
53
+ incrementSubagent(parentTurnId);
54
+ let handedOff = false;
55
+ try {
56
+ await updateChatSettings(chatId, (settings) => {
57
+ settings.subagents = settings.subagents || {};
37
58
 
38
- depth = getSubagentDepth(settings, parentId);
39
- if (depth >= MAX_SUBAGENT_DEPTH) {
40
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'Max subagent depth reached' });
41
- }
59
+ depth = getSubagentDepth(settings, parentId);
60
+ if (depth >= MAX_SUBAGENT_DEPTH) {
61
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Max subagent depth reached' });
62
+ }
42
63
 
43
- // Make sure the id does not already exist
44
- if (settings.subagents[id]) {
45
- throw new TRPCError({ code: 'BAD_REQUEST', message: 'Subagent ID already exists' });
46
- }
64
+ // Make sure the id does not already exist
65
+ if (settings.subagents[id]) {
66
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'Subagent ID already exists' });
67
+ }
47
68
 
48
- settings.subagents[id] = {
49
- id,
50
- agentId,
51
- sessionId,
52
- createdAt: new Date().toISOString(),
53
- status: 'active',
54
- parentId,
55
- };
69
+ settings.subagents[id] = {
70
+ id,
71
+ agentId,
72
+ sessionId,
73
+ createdAt: new Date().toISOString(),
74
+ status: 'active',
75
+ parentId,
76
+ };
56
77
 
57
- return settings;
58
- });
78
+ return settings;
79
+ });
59
80
 
60
- const workspaceRoot = getWorkspaceRoot(process.cwd());
81
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
61
82
 
62
- const isAsync = input.async ?? depth === 0;
83
+ const isAsync = input.async ?? depth === 0;
63
84
 
64
- // Execute asynchronously
65
- executeSubagent(
66
- chatId,
67
- id,
68
- agentId,
69
- sessionId,
70
- input.prompt,
71
- isAsync,
72
- ctx.tokenPayload,
73
- workspaceRoot
74
- );
85
+ // Execute asynchronously — executeSubagent's finally decrements.
86
+ handedOff = true;
87
+ executeSubagent(
88
+ chatId,
89
+ id,
90
+ agentId,
91
+ sessionId,
92
+ input.prompt,
93
+ isAsync,
94
+ ctx.tokenPayload,
95
+ workspaceRoot
96
+ );
75
97
 
76
- return { id, depth, isAsync };
98
+ return { id, depth, isAsync };
99
+ } finally {
100
+ if (!handedOff) decrementSubagent(parentTurnId);
101
+ }
77
102
  });
78
103
 
79
104
  export const subagentSend = apiProcedure
@@ -87,40 +112,46 @@ export const subagentSend = apiProcedure
87
112
  .mutation(async ({ input, ctx }) => {
88
113
  if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
89
114
  const chatId = ctx.tokenPayload.chatId;
115
+ const parentTurnId = ctx.tokenPayload.turnId;
90
116
 
91
117
  let sub: SubagentTracker | undefined;
92
118
 
93
- await updateChatSettings(chatId, (settings) => {
94
- if (!settings.subagents?.[input.subagentId]) {
95
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Subagent not found' });
96
- }
97
-
98
- sub = settings.subagents[input.subagentId];
99
- sub!.status = 'active';
100
- return settings;
101
- });
102
-
103
- const workspaceRoot = getWorkspaceRoot(process.cwd());
119
+ incrementSubagent(parentTurnId);
120
+ let handedOff = false;
121
+ try {
122
+ await updateChatSettings(chatId, (settings) => {
123
+ sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
124
+ sub.status = 'active';
125
+ return settings;
126
+ });
104
127
 
105
- // Execute asynchronously
106
- executeSubagent(
107
- chatId,
108
- sub!.id,
109
- sub!.agentId || 'default',
110
- sub!.sessionId || 'default',
111
- input.prompt,
112
- input.async,
113
- ctx.tokenPayload,
114
- workspaceRoot
115
- );
128
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
116
129
 
117
- return { success: true };
130
+ handedOff = true;
131
+ executeSubagent(
132
+ chatId,
133
+ sub!.id,
134
+ sub!.agentId || 'default',
135
+ sub!.sessionId || 'default',
136
+ input.prompt,
137
+ input.async,
138
+ ctx.tokenPayload,
139
+ workspaceRoot
140
+ );
141
+
142
+ return { success: true };
143
+ } finally {
144
+ if (!handedOff) decrementSubagent(parentTurnId);
145
+ }
118
146
  });
119
147
 
120
- async function checkSubagentStatus(chatId: string, subagentId: string) {
148
+ async function checkSubagentStatus(
149
+ chatId: string,
150
+ subagentId: string,
151
+ callerSubagentId: string | undefined
152
+ ) {
121
153
  const settings = await readChatSettings(chatId);
122
- const sub = settings?.subagents?.[subagentId];
123
- if (!sub) throw new TRPCError({ code: 'NOT_FOUND', message: 'Subagent not found' });
154
+ const sub = assertSubagentAccess(settings, subagentId, callerSubagentId);
124
155
 
125
156
  if (sub.status === 'completed' || sub.status === 'failed') {
126
157
  let outputContent: string | undefined;
@@ -160,7 +191,11 @@ export const subagentWait = apiProcedure
160
191
 
161
192
  try {
162
193
  // Check status immediately before listening, but after event iterator is buffering
163
- const initialStatus = await checkSubagentStatus(chatId, input.subagentId);
194
+ const initialStatus = await checkSubagentStatus(
195
+ chatId,
196
+ input.subagentId,
197
+ ctx.tokenPayload.subagentId
198
+ );
164
199
  if (initialStatus) {
165
200
  clearTimeout(timeout);
166
201
  if (signal) signal.removeEventListener('abort', onAbort);
@@ -171,7 +206,11 @@ export const subagentWait = apiProcedure
171
206
  if (event.chatId === chatId && event.message?.subagentId === input.subagentId) {
172
207
  const msg = event.message;
173
208
  if (msg.role === 'subagent_status') {
174
- const status = await checkSubagentStatus(chatId, input.subagentId);
209
+ const status = await checkSubagentStatus(
210
+ chatId,
211
+ input.subagentId,
212
+ ctx.tokenPayload.subagentId
213
+ );
175
214
  if (status) {
176
215
  clearTimeout(timeout);
177
216
  if (signal) signal.removeEventListener('abort', onAbort);
@@ -203,13 +242,9 @@ export const subagentStop = apiProcedure
203
242
  let subToStop: SubagentTracker | undefined;
204
243
 
205
244
  await updateChatSettings(chatId, (settings) => {
206
- if (settings.subagents) {
207
- const sub = settings.subagents[input.subagentId];
208
- if (sub) {
209
- sub.status = 'failed';
210
- subToStop = sub;
211
- }
212
- }
245
+ const sub = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
246
+ sub.status = 'failed';
247
+ subToStop = sub;
213
248
  return settings;
214
249
  });
215
250
 
@@ -236,10 +271,8 @@ export const subagentDelete = apiProcedure
236
271
  let subToDelete: SubagentTracker | undefined;
237
272
 
238
273
  await updateChatSettings(chatId, (settings) => {
239
- if (settings.subagents && settings.subagents[input.subagentId]) {
240
- subToDelete = settings.subagents[input.subagentId]!;
241
- delete settings.subagents[input.subagentId];
242
- }
274
+ subToDelete = assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload!.subagentId);
275
+ delete settings.subagents![input.subagentId];
243
276
  return settings;
244
277
  });
245
278
 
@@ -289,6 +322,9 @@ export const subagentTail = apiProcedure
289
322
  if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
290
323
  const chatId = ctx.tokenPayload.chatId;
291
324
 
325
+ const settings = await readChatSettings(chatId);
326
+ assertSubagentAccess(settings, input.subagentId, ctx.tokenPayload.subagentId);
327
+
292
328
  const logger = createChatLogger(chatId, input.subagentId);
293
329
  const messages = await logger.getMessages(input.limit);
294
330
 
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { executeSubagent } from './subagent-utils.js';
3
3
  import * as workspace from '../../shared/workspace.js';
4
4
  import { taskScheduler } from '../agent/task-scheduler.js';
5
+ import * as turnRegistry from '../agent/turn-registry.js';
5
6
 
6
7
  vi.mock('../../shared/workspace.js', () => ({
7
8
  updateChatSettings: vi.fn(),
@@ -22,6 +23,7 @@ vi.mock('../agent/chat-logger.js', () => ({
22
23
  createChatLogger: vi.fn(() => ({
23
24
  findLastMessage: vi.fn().mockResolvedValue(null),
24
25
  logSystemEvent: vi.fn(),
26
+ logSubagentStatus: vi.fn(),
25
27
  })),
26
28
  }));
27
29
 
@@ -31,6 +33,11 @@ vi.mock('../agent/task-scheduler.js', () => ({
31
33
  },
32
34
  }));
33
35
 
36
+ vi.mock('../agent/turn-registry.js', () => ({
37
+ incrementSubagent: vi.fn(),
38
+ decrementSubagent: vi.fn(),
39
+ }));
40
+
34
41
  describe('executeSubagent', () => {
35
42
  beforeEach(() => {
36
43
  vi.clearAllMocks();
@@ -53,4 +60,57 @@ describe('executeSubagent', () => {
53
60
  // Should NOT call updateChatSettings to set status to completed
54
61
  expect(workspace.updateChatSettings).not.toHaveBeenCalled();
55
62
  });
63
+
64
+ it('should not increment the parent turn counter — that is the caller-s responsibility', async () => {
65
+ vi.mocked(taskScheduler.hasTasks).mockReturnValue(false);
66
+
67
+ await executeSubagent(
68
+ 'chat-1',
69
+ 'sub-1',
70
+ 'agent-1',
71
+ 'session-1',
72
+ 'hello',
73
+ false,
74
+ { agentId: 'parent-agent', turnId: 'turn-1' },
75
+ '/workspace'
76
+ );
77
+
78
+ expect(turnRegistry.incrementSubagent).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('should decrement the parent turn counter exactly once on early return', async () => {
82
+ vi.mocked(taskScheduler.hasTasks).mockReturnValue(true);
83
+
84
+ await executeSubagent(
85
+ 'chat-1',
86
+ 'sub-1',
87
+ 'agent-1',
88
+ 'session-1',
89
+ 'hello',
90
+ false,
91
+ { agentId: 'parent-agent', turnId: 'turn-1' },
92
+ '/workspace'
93
+ );
94
+
95
+ expect(turnRegistry.decrementSubagent).toHaveBeenCalledTimes(1);
96
+ expect(turnRegistry.decrementSubagent).toHaveBeenCalledWith('turn-1');
97
+ });
98
+
99
+ it('should decrement the parent turn counter exactly once on normal completion', async () => {
100
+ vi.mocked(taskScheduler.hasTasks).mockReturnValue(false);
101
+
102
+ await executeSubagent(
103
+ 'chat-1',
104
+ 'sub-1',
105
+ 'agent-1',
106
+ 'session-1',
107
+ 'hello',
108
+ false,
109
+ { agentId: 'parent-agent', turnId: 'turn-1' },
110
+ '/workspace'
111
+ );
112
+
113
+ expect(turnRegistry.decrementSubagent).toHaveBeenCalledTimes(1);
114
+ expect(turnRegistry.decrementSubagent).toHaveBeenCalledWith('turn-1');
115
+ });
56
116
  });
@@ -6,6 +6,7 @@ import type { RouterState } from '../routers/types.js';
6
6
  import { createChatLogger } from '../agent/chat-logger.js';
7
7
  import type { ChatSettings } from '../../shared/config.js';
8
8
  import { taskScheduler } from '../agent/task-scheduler.js';
9
+ import { decrementSubagent } from '../agent/turn-registry.js';
9
10
 
10
11
  export function getSubagentDepth(settings: ChatSettings, parentId: string | undefined): number {
11
12
  let depth = 0;
@@ -17,6 +18,13 @@ export function getSubagentDepth(settings: ChatSettings, parentId: string | unde
17
18
  return depth;
18
19
  }
19
20
 
21
+ /**
22
+ * Executes a subagent. Callers MUST have already called `incrementSubagent`
23
+ * for the parent's turn synchronously before any `await` — this function's
24
+ * `finally` block decrements, and we need the caller to increment earlier
25
+ * so that a sibling's completing task cannot decrement the parent's counter
26
+ * to zero (firing `turnEnded`) before this call's task is enqueued.
27
+ */
20
28
  export async function executeSubagent(
21
29
  chatId: string,
22
30
  subagentId: string,
@@ -24,107 +32,125 @@ export async function executeSubagent(
24
32
  sessionId: string,
25
33
  prompt: string,
26
34
  isAsync: boolean | undefined,
27
- parentTokenPayload: { agentId?: string; subagentId?: string; sessionId?: string },
35
+ parentTokenPayload: {
36
+ agentId?: string;
37
+ subagentId?: string;
38
+ sessionId?: string;
39
+ turnId?: string;
40
+ },
28
41
  workspaceRoot: string
29
42
  ) {
43
+ const parentTurnId = parentTokenPayload?.turnId;
30
44
  try {
31
- const settings = (await readChatSettings(chatId)) || {};
32
- const routers = settings.routers ?? [];
33
- const resolvedRouters = resolveRouters(routers, false);
45
+ try {
46
+ const settings = (await readChatSettings(chatId)) || {};
47
+ const routers = settings.routers ?? [];
48
+ const resolvedRouters = resolveRouters(routers, false);
34
49
 
35
- let routerState: RouterState = {
36
- messageId: randomUUID(),
37
- message: prompt,
38
- chatId,
39
- agentId,
40
- sessionId,
41
- env: {},
42
- };
43
-
44
- const initialState = { ...routerState };
45
- routerState = await executeRouterPipeline(routerState, resolvedRouters);
50
+ let routerState: RouterState = {
51
+ messageId: randomUUID(),
52
+ message: prompt,
53
+ chatId,
54
+ agentId,
55
+ sessionId,
56
+ subagentId,
57
+ env: {},
58
+ };
46
59
 
47
- await applyRouterStateUpdates(
48
- chatId,
49
- workspaceRoot,
50
- routerState,
51
- settings,
52
- initialState.agentId
53
- );
60
+ const initialState = { ...routerState };
61
+ routerState = await executeRouterPipeline(routerState, resolvedRouters);
54
62
 
55
- await executeDirectMessage(
56
- chatId,
57
- routerState,
58
- undefined, // settings
59
- workspaceRoot,
60
- false, // noWait
61
- undefined, // userMessageContent
62
- subagentId // subagentId
63
- );
63
+ await applyRouterStateUpdates(
64
+ chatId,
65
+ workspaceRoot,
66
+ routerState,
67
+ settings,
68
+ initialState.agentId
69
+ );
64
70
 
65
- if (taskScheduler.hasTasks(sessionId)) {
66
- return;
67
- }
71
+ await executeDirectMessage(
72
+ chatId,
73
+ routerState,
74
+ undefined, // settings
75
+ workspaceRoot,
76
+ false, // noWait
77
+ undefined, // userMessageContent
78
+ subagentId, // subagentId
79
+ undefined, // systemEvent
80
+ undefined, // displayRole
81
+ parentTurnId // parentTurnId — inherit from parent agent's turn
82
+ );
68
83
 
69
- // Update status
70
- await updateChatSettings(chatId, (finalSettings) => {
71
- if (finalSettings.subagents?.[subagentId]) {
72
- finalSettings.subagents[subagentId]!.status = 'completed';
84
+ if (taskScheduler.hasTasks(sessionId)) {
85
+ return;
73
86
  }
74
- return finalSettings;
75
- });
76
87
 
77
- const logger = createChatLogger(chatId, subagentId);
88
+ // Update status
89
+ await updateChatSettings(chatId, (finalSettings) => {
90
+ if (finalSettings.subagents?.[subagentId]) {
91
+ finalSettings.subagents[subagentId]!.status = 'completed';
92
+ }
93
+ return finalSettings;
94
+ });
78
95
 
79
- // Emit debug message to wake up waiters
80
- await logger.logSubagentStatus({ subagentId, status: 'completed' });
96
+ const logger = createChatLogger(chatId, subagentId, sessionId, parentTurnId);
81
97
 
82
- if (isAsync) {
83
- const lastLogMessage = await logger.findLastMessage(
84
- (m) => m.role === 'agent' || m.displayRole === 'agent'
85
- );
86
- let outputContent = '';
87
- if (lastLogMessage && 'content' in lastLogMessage) {
88
- outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
89
- }
98
+ // Emit debug message to wake up waiters
99
+ await logger.logSubagentStatus({ subagentId, status: 'completed' });
90
100
 
91
- console.log(
92
- 'Notifying parent',
93
- chatId,
94
- parentTokenPayload?.agentId,
95
- parentTokenPayload?.subagentId
96
- );
97
- // TODO: We need to overhaul the log system in general, and should not try to do it in this PR.
98
- // Currently, if the parent is the root agent, this notification is logged as a normal user message
99
- // and appears in the chat UI, violating the PRD requirement to hide orchestration.
100
- await executeDirectMessage(
101
- chatId,
102
- {
103
- messageId: randomUUID(),
104
- message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
101
+ if (isAsync) {
102
+ const lastLogMessage = await logger.findLastMessage(
103
+ (m) => m.role === 'agent' || m.displayRole === 'agent'
104
+ );
105
+ let outputContent = '';
106
+ if (lastLogMessage && 'content' in lastLogMessage) {
107
+ outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
108
+ }
109
+
110
+ console.log(
111
+ 'Notifying parent',
105
112
  chatId,
106
- agentId: parentTokenPayload?.agentId || 'default',
107
- ...(parentTokenPayload?.subagentId ? { subagentId: parentTokenPayload.subagentId } : {}),
108
- sessionId: parentTokenPayload?.sessionId || 'default',
109
- env: {},
110
- },
111
- undefined,
112
- workspaceRoot,
113
- true,
114
- undefined,
115
- parentTokenPayload?.subagentId,
116
- 'subagent_update'
117
- );
118
- }
119
- } catch {
120
- // TODO: Wrap this in a safe try-catch to prevent unhandled promise rejections crashing the daemon if disk errors occur
121
- await updateChatSettings(chatId, (errSettings) => {
122
- if (errSettings.subagents?.[subagentId]) {
123
- errSettings.subagents[subagentId]!.status = 'failed';
113
+ parentTokenPayload?.agentId,
114
+ parentTokenPayload?.subagentId
115
+ );
116
+ // TODO: We need to overhaul the log system in general, and should not try to do it in this PR.
117
+ // Currently, if the parent is the root agent, this notification is logged as a normal user message
118
+ // and appears in the chat UI, violating the PRD requirement to hide orchestration.
119
+ await executeDirectMessage(
120
+ chatId,
121
+ {
122
+ messageId: randomUUID(),
123
+ message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
124
+ chatId,
125
+ agentId: parentTokenPayload?.agentId || 'default',
126
+ ...(parentTokenPayload?.subagentId
127
+ ? { subagentId: parentTokenPayload.subagentId }
128
+ : {}),
129
+ sessionId: parentTokenPayload?.sessionId || 'default',
130
+ env: {},
131
+ },
132
+ undefined,
133
+ workspaceRoot,
134
+ true,
135
+ undefined,
136
+ parentTokenPayload?.subagentId,
137
+ 'subagent_update',
138
+ undefined,
139
+ parentTurnId
140
+ );
124
141
  }
125
- return errSettings;
126
- });
127
- const logger = createChatLogger(chatId, subagentId);
128
- await logger.logSubagentStatus({ subagentId, status: 'failed' });
142
+ } catch {
143
+ // TODO: Wrap this in a safe try-catch to prevent unhandled promise rejections crashing the daemon if disk errors occur
144
+ await updateChatSettings(chatId, (errSettings) => {
145
+ if (errSettings.subagents?.[subagentId]) {
146
+ errSettings.subagents[subagentId]!.status = 'failed';
147
+ }
148
+ return errSettings;
149
+ });
150
+ const logger = createChatLogger(chatId, subagentId, sessionId, parentTurnId);
151
+ await logger.logSubagentStatus({ subagentId, status: 'failed' });
152
+ }
153
+ } finally {
154
+ decrementSubagent(parentTurnId);
129
155
  }
130
156
  }