clawmini 0.0.3 → 0.0.5

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 (333) hide show
  1. package/README.md +19 -0
  2. package/dist/adapter-discord/index.d.mts.map +1 -1
  3. package/dist/adapter-discord/index.mjs +398 -193
  4. package/dist/adapter-discord/index.mjs.map +1 -1
  5. package/dist/adapter-google-chat/index.d.mts +5 -0
  6. package/dist/adapter-google-chat/index.d.mts.map +1 -0
  7. package/dist/adapter-google-chat/index.mjs +1077 -0
  8. package/dist/adapter-google-chat/index.mjs.map +1 -0
  9. package/dist/cli/index.mjs +107 -14
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/lite.mjs +175 -16
  12. package/dist/cli/lite.mjs.map +1 -1
  13. package/dist/cli/propose-policy.d.mts +1 -0
  14. package/dist/cli/propose-policy.mjs +7159 -0
  15. package/dist/cli/propose-policy.mjs.map +1 -0
  16. package/dist/daemon/index.d.mts.map +1 -1
  17. package/dist/daemon/index.mjs +1427 -513
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/{lite-oSYSvaOr.mjs → lite-CBxOT1y5.mjs} +101 -24
  20. package/dist/lite-CBxOT1y5.mjs.map +1 -0
  21. package/dist/routing-D8rTxtaV.mjs +245 -0
  22. package/dist/routing-D8rTxtaV.mjs.map +1 -0
  23. package/dist/web/_app/immutable/assets/0.C-4eziNy.css +1 -0
  24. package/dist/web/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
  25. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +1 -0
  26. package/dist/web/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
  27. package/{web/.svelte-kit/output/client/_app/immutable/chunks/8YNcRyEk.js → dist/web/_app/immutable/chunks/C20lZMGz.js} +1 -1
  28. package/dist/web/_app/immutable/chunks/C9lbZ-kT.js +1 -0
  29. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +2 -0
  30. package/dist/web/_app/immutable/chunks/CME08kGM.js +1 -0
  31. package/dist/web/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +1 -0
  33. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +2 -0
  34. package/dist/web/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
  36. package/dist/web/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
  37. package/dist/web/_app/immutable/chunks/DsIToJCP.js +1 -0
  38. package/dist/web/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
  39. package/{web/.svelte-kit/output/client/_app/immutable/entry/app.DO5eYwVz.js → dist/web/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
  40. package/dist/web/_app/immutable/entry/start.DuxJo6av.js +1 -0
  41. package/dist/web/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
  42. package/dist/web/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
  43. package/dist/web/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
  44. package/dist/web/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
  45. package/dist/web/_app/immutable/nodes/4.DLarELN4.js +60 -0
  46. package/dist/web/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
  47. package/dist/web/_app/version.json +1 -1
  48. package/dist/web/index.html +12 -12
  49. package/dist/{workspace-DjoNjhW0.mjs → workspace-BJmJBfKi.mjs} +103 -11
  50. package/dist/workspace-BJmJBfKi.mjs.map +1 -0
  51. package/docs/14_google_chat_adapter/development_log.md +40 -0
  52. package/docs/14_google_chat_adapter/notes.md +28 -0
  53. package/docs/14_google_chat_adapter/prd.md +35 -0
  54. package/docs/14_google_chat_adapter/questions.md +9 -0
  55. package/docs/14_google_chat_adapter/tickets.md +117 -0
  56. package/docs/15_sandbox_policies/tickets.md +33 -0
  57. package/docs/16_session_timeout/development_log.md +20 -0
  58. package/docs/16_session_timeout/notes.md +44 -0
  59. package/docs/16_session_timeout/prd.md +106 -0
  60. package/docs/16_session_timeout/questions.md +10 -0
  61. package/docs/16_session_timeout/tickets.md +64 -0
  62. package/docs/17_auto_approve_policy/development_log.md +29 -0
  63. package/docs/17_auto_approve_policy/notes.md +25 -0
  64. package/docs/17_auto_approve_policy/prd.md +34 -0
  65. package/docs/17_auto_approve_policy/questions.md +10 -0
  66. package/docs/17_auto_approve_policy/tickets.md +11 -0
  67. package/docs/18_clawmini_skills/development_log.md +36 -0
  68. package/docs/18_clawmini_skills/notes.md +8 -0
  69. package/docs/18_clawmini_skills/prd.md +45 -0
  70. package/docs/18_clawmini_skills/questions.md +10 -0
  71. package/docs/18_clawmini_skills/tickets.md +55 -0
  72. package/docs/19_subagents/development_log.md +69 -0
  73. package/docs/19_subagents/notes.md +18 -0
  74. package/docs/19_subagents/prd.md +156 -0
  75. package/docs/19_subagents/questions.md +13 -0
  76. package/docs/19_subagents/tickets.md +113 -0
  77. package/docs/20_chat_logs_cleanup/development_log.md +50 -0
  78. package/docs/20_chat_logs_cleanup/notes.md +43 -0
  79. package/docs/20_chat_logs_cleanup/prd.md +232 -0
  80. package/docs/20_chat_logs_cleanup/questions.md +2 -0
  81. package/docs/20_chat_logs_cleanup/tickets.md +98 -0
  82. package/docs/20_webui_markdown/development_log.md +36 -0
  83. package/docs/20_webui_markdown/notes.md +23 -0
  84. package/docs/20_webui_markdown/prd.md +49 -0
  85. package/docs/20_webui_markdown/questions.md +10 -0
  86. package/docs/20_webui_markdown/tickets.md +55 -0
  87. package/docs/21_adapter_filtering/development_log.md +29 -0
  88. package/docs/21_adapter_filtering/notes.md +25 -0
  89. package/docs/21_adapter_filtering/prd.md +44 -0
  90. package/docs/21_adapter_filtering/questions.md +12 -0
  91. package/docs/21_adapter_filtering/tickets.md +38 -0
  92. package/docs/21_built_in_routers/development_log.md +17 -0
  93. package/docs/21_built_in_routers/notes.md +27 -0
  94. package/docs/21_built_in_routers/prd.md +34 -0
  95. package/docs/21_built_in_routers/questions.md +4 -0
  96. package/docs/21_built_in_routers/tickets.md +25 -0
  97. package/docs/21_fancy_policies/development_log.md +38 -0
  98. package/docs/21_fancy_policies/notes.md +27 -0
  99. package/docs/21_fancy_policies/prd.md +58 -0
  100. package/docs/21_fancy_policies/questions.md +6 -0
  101. package/docs/21_fancy_policies/tickets.md +48 -0
  102. package/docs/22_adapter_multi_chat/development_log.md +76 -0
  103. package/docs/22_adapter_multi_chat/notes.md +42 -0
  104. package/docs/22_adapter_multi_chat/prd.md +76 -0
  105. package/docs/22_adapter_multi_chat/questions.md +16 -0
  106. package/docs/22_adapter_multi_chat/tickets.md +164 -0
  107. package/docs/23_custom_token_env/development_log.md +31 -0
  108. package/docs/23_custom_token_env/notes.md +16 -0
  109. package/docs/23_custom_token_env/prd.md +42 -0
  110. package/docs/23_custom_token_env/questions.md +8 -0
  111. package/docs/23_custom_token_env/tickets.md +54 -0
  112. package/docs/guides/discord_adapter_setup.md +15 -2
  113. package/docs/guides/google_chat_adapter_setup.md +145 -0
  114. package/napkin.md +5 -0
  115. package/package.json +7 -2
  116. package/src/adapter-discord/config.test.ts +27 -8
  117. package/src/adapter-discord/config.ts +6 -8
  118. package/src/adapter-discord/forwarder.test.ts +307 -114
  119. package/src/adapter-discord/forwarder.ts +260 -75
  120. package/src/adapter-discord/index.test.ts +278 -0
  121. package/src/adapter-discord/index.ts +160 -30
  122. package/src/adapter-discord/interactions.test.ts +96 -0
  123. package/src/adapter-discord/interactions.ts +156 -0
  124. package/src/adapter-discord/state.test.ts +9 -8
  125. package/src/adapter-discord/state.ts +51 -8
  126. package/src/adapter-google-chat/auth.test.ts +87 -0
  127. package/src/adapter-google-chat/auth.ts +132 -0
  128. package/src/adapter-google-chat/cards.ts +71 -0
  129. package/src/adapter-google-chat/client.test.ts +561 -0
  130. package/src/adapter-google-chat/client.ts +430 -0
  131. package/src/adapter-google-chat/config.test.ts +187 -0
  132. package/src/adapter-google-chat/config.ts +82 -0
  133. package/src/adapter-google-chat/cron.test.ts +143 -0
  134. package/src/adapter-google-chat/cron.ts +81 -0
  135. package/src/adapter-google-chat/forwarder.test.ts +537 -0
  136. package/src/adapter-google-chat/forwarder.ts +349 -0
  137. package/src/adapter-google-chat/index.test.ts +62 -0
  138. package/src/adapter-google-chat/index.ts +61 -0
  139. package/src/adapter-google-chat/state.test.ts +96 -0
  140. package/src/adapter-google-chat/state.ts +85 -0
  141. package/src/adapter-google-chat/subscriptions.ts +124 -0
  142. package/src/adapter-google-chat/upload.ts +88 -0
  143. package/src/adapter-google-chat/utils.test.ts +111 -0
  144. package/src/adapter-google-chat/utils.ts +133 -0
  145. package/src/cli/commands/init.ts +0 -7
  146. package/src/cli/commands/messages.ts +18 -3
  147. package/src/cli/commands/policies.ts +70 -0
  148. package/src/cli/commands/skills.ts +71 -0
  149. package/src/cli/commands/web-api/chats.ts +5 -1
  150. package/src/cli/e2e/basic.test.ts +1 -1
  151. package/src/cli/e2e/cron.test.ts +1 -1
  152. package/src/cli/e2e/daemon.test.ts +132 -4
  153. package/src/cli/e2e/export-lite-func.test.ts +54 -31
  154. package/src/cli/e2e/fallbacks.test.ts +8 -6
  155. package/src/cli/e2e/init.test.ts +7 -0
  156. package/src/cli/e2e/messages.test.ts +90 -55
  157. package/src/cli/e2e/propose-policy.test.ts +203 -0
  158. package/src/cli/e2e/requests.test.ts +15 -0
  159. package/src/cli/e2e/session-timeout.test.ts +192 -0
  160. package/src/cli/e2e/skills.test.ts +55 -0
  161. package/src/cli/e2e/slash-new.test.ts +93 -0
  162. package/src/cli/e2e/subagents.test.ts +106 -0
  163. package/src/cli/index.ts +4 -0
  164. package/src/cli/lite.ts +51 -11
  165. package/src/cli/propose-policy.ts +91 -0
  166. package/src/cli/subagent-commands.ts +215 -0
  167. package/src/daemon/agent/agent-context.ts +89 -0
  168. package/src/daemon/agent/agent-extractors.ts +68 -0
  169. package/src/daemon/agent/agent-runner.ts +153 -0
  170. package/src/daemon/agent/agent-session.ts +261 -0
  171. package/src/daemon/agent/chat-logger.test.ts +158 -0
  172. package/src/daemon/agent/chat-logger.ts +188 -0
  173. package/src/daemon/agent/task-scheduler.test.ts +202 -0
  174. package/src/daemon/agent/task-scheduler.ts +276 -0
  175. package/src/daemon/agent/types.ts +84 -0
  176. package/src/daemon/agent/utils.ts +7 -0
  177. package/src/daemon/api/agent-router.ts +166 -18
  178. package/src/daemon/api/index.test.ts +50 -18
  179. package/src/daemon/api/policy-request.test.ts +39 -2
  180. package/src/daemon/api/subagent-router.test.ts +108 -0
  181. package/src/daemon/api/subagent-router.ts +296 -0
  182. package/src/daemon/api/subagent-utils.test.ts +56 -0
  183. package/src/daemon/api/subagent-utils.ts +130 -0
  184. package/src/daemon/api/user-router.ts +30 -13
  185. package/src/daemon/auth.ts +1 -0
  186. package/src/daemon/chats.ts +6 -0
  187. package/src/daemon/cron.test.ts +66 -1
  188. package/src/daemon/cron.ts +35 -8
  189. package/src/daemon/index.ts +23 -0
  190. package/src/daemon/message-agent.test.ts +11 -25
  191. package/src/daemon/message-extraction.test.ts +10 -27
  192. package/src/daemon/message-fallbacks.test.ts +13 -35
  193. package/src/daemon/message-interruption.test.ts +70 -53
  194. package/src/daemon/message-jobs.test.ts +138 -0
  195. package/src/daemon/message-queue.test.ts +30 -43
  196. package/src/daemon/message-router.test.ts +12 -11
  197. package/src/daemon/message-session.test.ts +41 -28
  198. package/src/daemon/message-typing.test.ts +19 -6
  199. package/src/daemon/message.ts +103 -515
  200. package/src/daemon/policy-request-service.ts +8 -3
  201. package/src/daemon/policy-utils.ts +19 -1
  202. package/src/daemon/queue.ts +16 -0
  203. package/src/daemon/request-store.test.ts +4 -0
  204. package/src/daemon/routers/session-timeout.test.ts +122 -0
  205. package/src/daemon/routers/session-timeout.ts +71 -0
  206. package/src/daemon/routers/slash-new.ts +3 -1
  207. package/src/daemon/routers/slash-policies.test.ts +26 -13
  208. package/src/daemon/routers/slash-policies.ts +39 -29
  209. package/src/daemon/routers/types.ts +8 -0
  210. package/src/daemon/routers.ts +64 -2
  211. package/src/daemon/utils/spawn.ts +6 -8
  212. package/src/shared/adapters/commands.test.ts +155 -0
  213. package/src/shared/adapters/commands.ts +125 -0
  214. package/src/shared/adapters/filtering.test.ts +111 -0
  215. package/src/shared/adapters/filtering.ts +57 -0
  216. package/src/shared/adapters/routing.test.ts +144 -0
  217. package/src/shared/adapters/routing.ts +109 -0
  218. package/src/shared/agent-utils.ts +10 -0
  219. package/src/shared/chats.test.ts +145 -3
  220. package/src/shared/chats.ts +215 -18
  221. package/src/shared/config.ts +67 -15
  222. package/src/shared/lite.ts +22 -18
  223. package/src/shared/policies.ts +7 -0
  224. package/src/shared/workspace.test.ts +45 -1
  225. package/src/shared/workspace.ts +119 -6
  226. package/templates/debug/settings.json +5 -2
  227. package/templates/environments/cladding/env.json +2 -2
  228. package/templates/gemini/.gemini/hooks/check-subagents.mjs +23 -0
  229. package/templates/gemini/.gemini/hooks/clawmini-logging.sh +17 -0
  230. package/templates/gemini/.gemini/hooks/insert-pending.sh +9 -0
  231. package/templates/gemini/.gemini/settings.json +50 -0
  232. package/templates/gemini/settings.json +22 -8
  233. package/templates/gemini-claw/.gemini/base-system.md +100 -0
  234. package/templates/gemini-claw/.gemini/hooks/check-subagents.mjs +23 -0
  235. package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +1 -1
  236. package/templates/gemini-claw/.gemini/settings.json +13 -0
  237. package/templates/gemini-claw/.gemini/subagent-system.md +7 -0
  238. package/templates/gemini-claw/.gemini/system.md +3 -99
  239. package/templates/gemini-claw/settings.json +27 -22
  240. package/templates/skills/clawmini-requests/SKILL.md +92 -0
  241. package/templates/skills/clawmini-subagents/SKILL.md +79 -0
  242. package/templates/skills/skill-creator/SKILL.md +60 -0
  243. package/tsdown.config.ts +10 -1
  244. package/web/.svelte-kit/generated/server/internal.js +2 -1
  245. package/web/.svelte-kit/non-ambient.d.ts +2 -0
  246. package/web/.svelte-kit/output/client/.vite/manifest.json +141 -138
  247. package/web/.svelte-kit/output/client/_app/immutable/assets/0.C-4eziNy.css +1 -0
  248. package/web/.svelte-kit/output/client/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
  249. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +1 -0
  250. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
  251. package/{dist/web/_app/immutable/chunks/8YNcRyEk.js → web/.svelte-kit/output/client/_app/immutable/chunks/C20lZMGz.js} +1 -1
  252. package/web/.svelte-kit/output/client/_app/immutable/chunks/C9lbZ-kT.js +1 -0
  253. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +2 -0
  254. package/web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js +1 -0
  255. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
  256. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +1 -0
  257. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +2 -0
  258. package/web/.svelte-kit/output/client/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
  259. package/web/.svelte-kit/output/client/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
  260. package/web/.svelte-kit/output/client/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
  261. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +1 -0
  262. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
  263. package/{dist/web/_app/immutable/entry/app.DO5eYwVz.js → web/.svelte-kit/output/client/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
  264. package/web/.svelte-kit/output/client/_app/immutable/entry/start.DuxJo6av.js +1 -0
  265. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
  266. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
  267. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
  268. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
  269. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.DLarELN4.js +60 -0
  270. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
  271. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  272. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -3
  273. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.C-4eziNy.css +1 -0
  274. package/web/.svelte-kit/output/server/_app/immutable/assets/_page.Cc_xwLNl.css +1 -0
  275. package/web/.svelte-kit/output/server/chunks/app-state.svelte.js +5 -0
  276. package/web/.svelte-kit/output/server/chunks/bot.js +4 -4
  277. package/web/.svelte-kit/output/server/chunks/client.js +2 -1
  278. package/web/.svelte-kit/output/server/chunks/exports.js +0 -1
  279. package/web/.svelte-kit/output/server/chunks/internal.js +2 -1
  280. package/web/.svelte-kit/output/server/chunks/root.js +482 -392
  281. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +57 -7
  282. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +234 -9
  283. package/web/.svelte-kit/output/server/index.js +82 -10
  284. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  285. package/web/.svelte-kit/output/server/manifest.js +1 -1
  286. package/web/.svelte-kit/output/server/nodes/0.js +2 -2
  287. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  288. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  289. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  290. package/web/.svelte-kit/output/server/nodes/4.js +2 -2
  291. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  292. package/web/.svelte-kit/types/src/routes/$types.d.ts +1 -2
  293. package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +1 -2
  294. package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +1 -2
  295. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +1 -2
  296. package/web/package.json +8 -0
  297. package/web/src/lib/app-state.svelte.ts +5 -1
  298. package/web/src/lib/components/app/markdown-renderer.svelte +56 -0
  299. package/web/src/lib/components/app/markdown-renderer.svelte.spec.ts +44 -0
  300. package/web/src/lib/components/app/message-content.svelte +16 -0
  301. package/web/src/lib/types.ts +67 -3
  302. package/web/src/routes/+layout.svelte +31 -1
  303. package/web/src/routes/chats/[id]/+page.svelte +167 -18
  304. package/web/src/routes/chats/[id]/page.svelte.spec.ts +58 -7
  305. package/dist/lite-oSYSvaOr.mjs.map +0 -1
  306. package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +0 -1
  307. package/dist/web/_app/immutable/chunks/B5abRDXp.js +0 -1
  308. package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
  309. package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +0 -2
  310. package/dist/web/_app/immutable/chunks/C3k55nDF.js +0 -1
  311. package/dist/web/_app/immutable/chunks/CpaGRn9L.js +0 -1
  312. package/dist/web/_app/immutable/chunks/DG5RZBw-.js +0 -2
  313. package/dist/web/_app/immutable/chunks/DQoygso7.js +0 -1
  314. package/dist/web/_app/immutable/entry/start.D48mVn1m.js +0 -1
  315. package/dist/web/_app/immutable/nodes/0.B-0CcADM.js +0 -1
  316. package/dist/web/_app/immutable/nodes/1.FixKgvRO.js +0 -1
  317. package/dist/web/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
  318. package/dist/workspace-DjoNjhW0.mjs.map +0 -1
  319. package/src/daemon/message-verbosity.test.ts +0 -127
  320. package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +0 -1
  321. package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +0 -1
  322. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
  323. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +0 -2
  324. package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +0 -1
  325. package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +0 -1
  326. package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +0 -2
  327. package/web/.svelte-kit/output/client/_app/immutable/chunks/DQoygso7.js +0 -1
  328. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +0 -1
  329. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B-0CcADM.js +0 -1
  330. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.FixKgvRO.js +0 -1
  331. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
  332. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +0 -1
  333. /package/templates/{gemini-claw/.gemini/skills → skills}/clawmini-jobs/SKILL.md +0 -0
@@ -1,5 +1,5 @@
1
- import { C as CronJobSchema, S as pathIsInsideDir, _ as readSettings, a as getAgent, b as writeChatSettings, c as getSettingsPath, g as readPolicies, h as readEnvironment, i as getActiveEnvironmentInfo, l as getSocketPath, m as readChatSettings, o as getClawminiDir, p as readAgentSessionSettings, s as getEnvironmentPath, u as getWorkspaceRoot, v as writeAgentSessionSettings, w as SettingsSchema } from "../workspace-DjoNjhW0.mjs";
2
- import { d as getMessages$1, n as exportLiteToEnvironment, o as appendMessage$1, p as listChats, u as getDefaultChatId } from "../lite-oSYSvaOr.mjs";
1
+ import { C as writeAgentSessionSettings, D as pathIsInsideDir, O as CronJobSchema, S as updateChatSettings, T as writeChatSettings, _ as readEnvironment, b as resolveAgentWorkDir, c as getClawminiDir, d as getSocketPath, f as getWorkspaceRoot, g as readChatSettings, h as readAgentSessionSettings, k as SettingsSchema, l as getEnvironmentPath, m as listAgents, o as getActiveEnvironmentInfo, s as getAgent, u as getSettingsPath, v as readPolicies, y as readSettings } from "../workspace-BJmJBfKi.mjs";
2
+ import { c as createChat, f as getDefaultChatId, h as listChats, n as exportLiteToEnvironment, p as getMessages$1, s as appendMessage$1, u as findLastMessage } from "../lite-CBxOT1y5.mjs";
3
3
  import fs, { constants } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { execSync, spawn } from "node:child_process";
@@ -10,11 +10,11 @@ import net from "node:net";
10
10
  import { createHTTPHandler } from "@trpc/server/adapters/standalone";
11
11
  import { TRPCError, initTRPC } from "@trpc/server";
12
12
  import schedule from "node-schedule";
13
- import { EventEmitter, on } from "node:events";
14
13
  import crypto$1, { randomBytes, randomUUID } from "node:crypto";
15
14
  import fs$2 from "fs/promises";
16
15
  import path$1 from "path";
17
16
  import { randomInt } from "crypto";
17
+ import { EventEmitter, on } from "node:events";
18
18
 
19
19
  //#region src/daemon/api/trpc.ts
20
20
  const t = initTRPC.context().create();
@@ -34,107 +34,17 @@ const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
34
34
  });
35
35
  const apiProcedure = t.procedure.use(apiAuthMiddleware);
36
36
 
37
- //#endregion
38
- //#region src/daemon/events.ts
39
- const daemonEvents = new EventEmitter();
40
- const DAEMON_EVENT_MESSAGE_APPENDED = "message-appended";
41
- const DAEMON_EVENT_TYPING = "typing";
42
- function emitMessageAppended(chatId, message) {
43
- daemonEvents.emit(DAEMON_EVENT_MESSAGE_APPENDED, {
44
- chatId,
45
- message
46
- });
47
- }
48
- function emitTyping(chatId) {
49
- daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId });
50
- }
51
-
52
- //#endregion
53
- //#region src/daemon/chats.ts
54
- async function appendMessage(id, message, startDir = process.cwd()) {
55
- await appendMessage$1(id, message, startDir);
56
- emitMessageAppended(id, message);
57
- }
58
-
59
- //#endregion
60
- //#region src/daemon/queue.ts
61
- var Queue = class {
62
- pending = [];
63
- isRunning = false;
64
- currentController = null;
65
- currentPayload;
66
- enqueue(task, payload) {
67
- return new Promise((resolve, reject) => {
68
- this.pending.push({
69
- task,
70
- payload,
71
- resolve,
72
- reject
73
- });
74
- this.processNext().catch(() => {});
75
- });
76
- }
77
- async processNext() {
78
- if (this.isRunning || this.pending.length === 0) return;
79
- this.isRunning = true;
80
- const entry = this.pending.shift();
81
- this.currentController = new AbortController();
82
- this.currentPayload = entry.payload;
83
- try {
84
- await entry.task(this.currentController.signal);
85
- entry.resolve();
86
- } catch (error) {
87
- entry.reject(error);
88
- } finally {
89
- this.isRunning = false;
90
- this.currentController = null;
91
- this.currentPayload = void 0;
92
- this.processNext().catch(() => {});
93
- }
94
- }
95
- abortCurrent(predicate) {
96
- if (this.currentController) {
97
- if (!predicate || this.currentPayload !== void 0 && predicate(this.currentPayload)) {
98
- const error = /* @__PURE__ */ new Error("Task aborted");
99
- error.name = "AbortError";
100
- this.currentController.abort(error);
101
- }
102
- }
103
- }
104
- getCurrentPayload() {
105
- return this.currentPayload;
106
- }
107
- clear(reason = "Task cleared", predicate) {
108
- const tasksToClear = predicate ? this.pending.filter((p) => p.payload !== void 0 && predicate(p.payload)) : [...this.pending];
109
- if (predicate) this.pending = this.pending.filter((p) => !(p.payload !== void 0 && predicate(p.payload)));
110
- else this.pending = [];
111
- for (const { reject } of tasksToClear) {
112
- const error = new Error(reason);
113
- error.name = "AbortError";
114
- reject(error);
115
- }
116
- }
117
- extractPending(predicate) {
118
- const extracted = this.pending.map((p) => p.payload).filter((p) => p !== void 0 && (!predicate || predicate(p)));
119
- this.clear("Task extracted for batching", predicate);
120
- return extracted;
121
- }
122
- };
123
- const messageQueues = /* @__PURE__ */ new Map();
124
- function getMessageQueue(dir) {
125
- if (!messageQueues.has(dir)) messageQueues.set(dir, new Queue());
126
- return messageQueues.get(dir);
127
- }
128
-
129
37
  //#endregion
130
38
  //#region src/daemon/routers/slash-new.ts
131
39
  function slashNew(state) {
132
40
  if (/^\/new(\s|$)/.test(state.message)) {
133
41
  const newMessage = state.message.replace(/^\/new(\s+|$)/, "").trim();
42
+ const id = crypto.randomUUID();
134
43
  return {
135
44
  ...state,
136
45
  message: newMessage,
137
- sessionId: crypto.randomUUID(),
46
+ sessionId: id,
47
+ nextSessionId: id,
138
48
  reply: "[@clawmini/slash-new] Starting a new session..."
139
49
  };
140
50
  }
@@ -356,6 +266,16 @@ function executeSafe(command, args, options) {
356
266
  });
357
267
  });
358
268
  }
269
+ async function executeRequest(request, policy, cwd) {
270
+ const interpolatedArgs = interpolateArgs([...policy.args || [], ...request.args], request.fileMappings);
271
+ const { stdout, stderr, exitCode } = await executeSafe(policy.command, interpolatedArgs, cwd ? { cwd } : void 0);
272
+ return {
273
+ stdout,
274
+ stderr,
275
+ exitCode,
276
+ commandStr: `${policy.command} ${interpolatedArgs.join(" ")}`
277
+ };
278
+ }
359
279
  async function generateRequestPreview(request) {
360
280
  let previewContent = `Sandbox Policy Request: ${request.commandName}\n`;
361
281
  previewContent += `ID: ${request.id}\n`;
@@ -374,6 +294,28 @@ async function generateRequestPreview(request) {
374
294
  return previewContent;
375
295
  }
376
296
 
297
+ //#endregion
298
+ //#region src/daemon/events.ts
299
+ const daemonEvents = new EventEmitter();
300
+ const DAEMON_EVENT_MESSAGE_APPENDED = "message-appended";
301
+ const DAEMON_EVENT_TYPING = "typing";
302
+ function emitMessageAppended(chatId, message) {
303
+ daemonEvents.emit(DAEMON_EVENT_MESSAGE_APPENDED, {
304
+ chatId,
305
+ message
306
+ });
307
+ }
308
+ function emitTyping(chatId) {
309
+ daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId });
310
+ }
311
+
312
+ //#endregion
313
+ //#region src/daemon/chats.ts
314
+ async function appendMessage(id, message, startDir = process.cwd()) {
315
+ await appendMessage$1(id, message, startDir);
316
+ emitMessageAppended(id, message);
317
+ }
318
+
377
319
  //#endregion
378
320
  //#region src/daemon/routers/slash-policies.ts
379
321
  async function loadAndValidateRequest(id, state) {
@@ -425,29 +367,30 @@ async function slashPolicies(state) {
425
367
  reply: `Policy not found: ${req.commandName}`
426
368
  };
427
369
  req.state = "Approved";
370
+ const { stdout, stderr, exitCode } = await executeRequest(req, policy, getWorkspaceRoot());
371
+ req.executionResult = {
372
+ stdout,
373
+ stderr,
374
+ exitCode
375
+ };
428
376
  await store.save(req);
429
- const interpolatedArgs = interpolateArgs([...policy.args || [], ...req.args], req.fileMappings);
430
- const { stdout, stderr, exitCode } = await executeSafe(policy.command, interpolatedArgs, { cwd: getWorkspaceRoot() });
431
- const commandStr = `${policy.command} ${interpolatedArgs.join(" ")}`;
377
+ const agentMessage = `Request ${id} approved.\n\n${wrapInHtml("stdout", stdout)}\n\n${wrapInHtml("stderr", stderr)}\n\nExit Code: ${exitCode}`;
432
378
  const logMsg = {
433
379
  id: randomUUID(),
434
380
  messageId: state.messageId,
435
- role: "log",
436
- source: "router",
437
- content: `Request ${id} approved and executed.`,
438
- stderr,
439
- stdout,
381
+ role: "system",
382
+ event: "policy_approved",
383
+ displayRole: "user",
384
+ content: agentMessage,
440
385
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
441
- command: commandStr,
442
- cwd: getWorkspaceRoot(),
443
- exitCode
386
+ ...req.subagentId ? { subagentId: req.subagentId } : {}
444
387
  };
445
388
  await appendMessage(state.chatId, logMsg);
446
- const agentMessage = `Request ${id} approved.\n\n${wrapInHtml("stdout", stdout)}\n\n${wrapInHtml("stderr", stderr)}\n\nExit Code: ${exitCode}`;
447
389
  return {
448
390
  ...state,
449
391
  message: agentMessage,
450
- reply: `Approved request, running ${req.commandName}`
392
+ reply: `Approved request, running ${req.commandName}`,
393
+ ...req.subagentId ? { subagentId: req.subagentId } : {}
451
394
  };
452
395
  }
453
396
  const rejectMatch = message.match(/^\/reject\s+([^\s]+)(?:\s+(.*))?/);
@@ -461,23 +404,33 @@ async function slashPolicies(state) {
461
404
  req.state = "Rejected";
462
405
  req.rejectionReason = reason;
463
406
  await store.save(req);
407
+ const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
464
408
  const logMsg = {
465
409
  id: randomUUID(),
466
410
  messageId: state.messageId,
467
- role: "log",
468
- source: "router",
469
- content: `Request ${id} rejected. Reason: ${reason}`,
470
- stderr: "",
411
+ role: "system",
412
+ event: "policy_rejected",
413
+ displayRole: "user",
414
+ content: agentMessage,
471
415
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
472
- command: `policy-request-reject ${id}`,
473
- cwd: getWorkspaceRoot(),
474
- exitCode: 1
416
+ ...req.subagentId ? { subagentId: req.subagentId } : {}
417
+ };
418
+ const userNotificationMsg = {
419
+ id: randomUUID(),
420
+ messageId: state.messageId,
421
+ role: "system",
422
+ event: "policy_rejected",
423
+ displayRole: "agent",
424
+ content: agentMessage,
425
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
426
+ ...req.subagentId ? { subagentId: req.subagentId } : {}
475
427
  };
476
428
  await appendMessage(state.chatId, logMsg);
477
- const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
429
+ await appendMessage(state.chatId, userNotificationMsg);
478
430
  return {
479
431
  ...state,
480
- message: agentMessage
432
+ message: agentMessage,
433
+ ...req.subagentId ? { subagentId: req.subagentId } : {}
481
434
  };
482
435
  }
483
436
  return state;
@@ -487,17 +440,130 @@ function wrapInHtml(tag, text) {
487
440
  return `<${tag}>\n${text.trim()}\n</${tag}>`;
488
441
  }
489
442
 
443
+ //#endregion
444
+ //#region src/daemon/routers/session-timeout.ts
445
+ /**
446
+ * Router that automatically starts a new session after a period of inactivity.
447
+ *
448
+ * To register this router, add it to your `~/.gemini/settings.json`:
449
+ * ```json
450
+ * {
451
+ * "routers": [
452
+ * {
453
+ * "use": "session-timeout",
454
+ * "with": {
455
+ * "timeout": "60m",
456
+ * "prompt": "This chat session has ended. Save any important details from it to your memory. When finished, reply with NO_REPLY_NECESSARY."
457
+ * }
458
+ * }
459
+ * ]
460
+ * }
461
+ * ```
462
+ */
463
+ function createSessionTimeoutRouter(config = {}) {
464
+ const timeStr = config.timeout ?? "60m";
465
+ const prompt = config.prompt ?? "This chat session has ended. Save any important details from it to your memory. When finished, reply with NO_REPLY_NECESSARY.";
466
+ return function(state) {
467
+ if (state.env?.__SESSION_TIMEOUT__ === "true") return state;
468
+ const sessionId = state.sessionId || crypto.randomUUID();
469
+ const jobId = `__session_timeout__${sessionId}`;
470
+ const jobs = {
471
+ ...state.jobs,
472
+ remove: [
473
+ ...state.jobs?.remove || [],
474
+ jobId,
475
+ "__session_timeout__"
476
+ ]
477
+ };
478
+ return {
479
+ ...state,
480
+ sessionId,
481
+ jobs: {
482
+ ...jobs,
483
+ add: [...jobs.add || [], {
484
+ id: jobId,
485
+ schedule: { at: timeStr },
486
+ message: prompt,
487
+ reply: "[@clawmini/session-timeout] Starting a fresh session...",
488
+ nextSessionId: randomUUID(),
489
+ session: {
490
+ type: "existing",
491
+ id: sessionId
492
+ },
493
+ env: { __SESSION_TIMEOUT__: "true" },
494
+ jobs: { remove: [jobId] }
495
+ }]
496
+ }
497
+ };
498
+ };
499
+ }
500
+
490
501
  //#endregion
491
502
  //#region src/daemon/routers.ts
503
+ const GLOBAL_ROUTERS = ["@clawmini/session-timeout"];
504
+ const USER_ROUTERS = [
505
+ "@clawmini/slash-new",
506
+ "@clawmini/slash-command",
507
+ "@clawmini/slash-stop",
508
+ "@clawmini/slash-interrupt",
509
+ "@clawmini/slash-policies"
510
+ ];
511
+ function resolveRouters(userRouters, isUserMessage) {
512
+ const resolvedGlobals = [];
513
+ const resolvedUsers = [];
514
+ const userConfigMap = /* @__PURE__ */ new Map();
515
+ for (const r of userRouters) {
516
+ const name = typeof r === "string" ? r : r.use;
517
+ const config = typeof r === "string" ? {} : r.with || {};
518
+ if (name.startsWith("@clawmini/")) userConfigMap.set(name, config);
519
+ else resolvedUsers.push(r);
520
+ }
521
+ for (const globalRouter of GLOBAL_ROUTERS) {
522
+ const name = typeof globalRouter === "string" ? globalRouter : globalRouter.use;
523
+ const baseConfig = typeof globalRouter === "string" ? {} : globalRouter.with || {};
524
+ const userConfig = userConfigMap.get(name) || {};
525
+ const mergedConfig = {
526
+ ...baseConfig,
527
+ ...userConfig
528
+ };
529
+ resolvedGlobals.push({
530
+ use: name,
531
+ with: mergedConfig
532
+ });
533
+ }
534
+ const defaultUserRouters = [];
535
+ for (const defaultUserRouter of USER_ROUTERS) {
536
+ const name = typeof defaultUserRouter === "string" ? defaultUserRouter : defaultUserRouter.use;
537
+ const baseConfig = typeof defaultUserRouter === "string" ? {} : defaultUserRouter.with || {};
538
+ const userConfig = userConfigMap.get(name) || {};
539
+ const mergedConfig = {
540
+ ...baseConfig,
541
+ ...userConfig
542
+ };
543
+ defaultUserRouters.push({
544
+ use: name,
545
+ with: mergedConfig
546
+ });
547
+ }
548
+ if (isUserMessage) return [
549
+ ...resolvedGlobals,
550
+ ...defaultUserRouters,
551
+ ...resolvedUsers
552
+ ];
553
+ else return resolvedGlobals;
554
+ }
492
555
  async function executeRouterPipeline(initialState, routers) {
493
556
  let state = { ...initialState };
494
- for (const router of routers) {
557
+ for (const routerDef of routers) {
495
558
  if (state.action === "stop") break;
559
+ const router = typeof routerDef === "string" ? routerDef : routerDef.use;
560
+ const config = typeof routerDef === "string" ? {} : routerDef.with || {};
496
561
  if (router === "@clawmini/slash-new") state = slashNew(state);
497
562
  else if (router === "@clawmini/slash-command") state = await slashCommand(state);
498
563
  else if (router === "@clawmini/slash-stop") state = slashStop(state);
499
564
  else if (router === "@clawmini/slash-interrupt") state = slashInterrupt(state);
500
565
  else if (router === "@clawmini/slash-policies") state = await slashPolicies(state);
566
+ else if (router === "@clawmini/session-timeout") state = createSessionTimeoutRouter(config)(state);
501
567
  else try {
502
568
  state = await executeCustomRouter(router, state);
503
569
  } catch (err) {
@@ -563,6 +629,387 @@ async function executeCustomRouter(command, state) {
563
629
  });
564
630
  }
565
631
 
632
+ //#endregion
633
+ //#region src/daemon/agent/agent-context.ts
634
+ function formatEnvironmentPrefix(prefix, replacements) {
635
+ const map = {
636
+ "{WORKSPACE_DIR}": replacements.targetPath,
637
+ "{AGENT_DIR}": replacements.executionCwd,
638
+ "{ENV_DIR}": replacements.envDir,
639
+ "{HOME_DIR}": process.env.HOME || "",
640
+ "{ENV_ARGS}": replacements.envArgs
641
+ };
642
+ return prefix.replace(/{(WORKSPACE_DIR|AGENT_DIR|ENV_DIR|HOME_DIR|ENV_ARGS)}/g, (match) => map[match] || match);
643
+ }
644
+ async function sandboxExecutionContext(initialCommand, env, agentSpecificEnvKeys, executionCwd, cwd) {
645
+ let command = initialCommand;
646
+ const activeEnvInfo = await getActiveEnvironmentInfo(executionCwd, cwd);
647
+ if (!activeEnvInfo) return command;
648
+ const activeEnvName = activeEnvInfo.name;
649
+ const activeEnv = await readEnvironment(activeEnvName, cwd);
650
+ if (activeEnv?.env) for (const [key, value] of Object.entries(activeEnv.env)) if (value === false) {
651
+ delete env[key];
652
+ agentSpecificEnvKeys.delete(key);
653
+ } else {
654
+ let interpolatedValue = String(value);
655
+ interpolatedValue = interpolatedValue.replace(/\{PATH\}/g, process.env.PATH || "");
656
+ interpolatedValue = interpolatedValue.replace(/\{ENV_DIR\}/g, getEnvironmentPath(activeEnvName, cwd));
657
+ interpolatedValue = interpolatedValue.replace(/\{WORKSPACE_DIR\}/g, activeEnvInfo.targetPath);
658
+ env[key] = interpolatedValue;
659
+ agentSpecificEnvKeys.add(key);
660
+ }
661
+ if (activeEnv?.prefix) {
662
+ const envArgs = Array.from(agentSpecificEnvKeys).map((key) => {
663
+ if (activeEnv.envFormat) return activeEnv.envFormat.replace("{key}", key);
664
+ return key;
665
+ }).join(" ");
666
+ const prefixReplaced = formatEnvironmentPrefix(activeEnv.prefix, {
667
+ targetPath: activeEnvInfo.targetPath,
668
+ executionCwd,
669
+ envDir: getEnvironmentPath(activeEnvName, cwd),
670
+ envArgs
671
+ });
672
+ if (prefixReplaced.includes("{COMMAND}")) command = prefixReplaced.replace("{COMMAND}", command);
673
+ else command = `${prefixReplaced} ${command}`;
674
+ }
675
+ return command;
676
+ }
677
+
678
+ //#endregion
679
+ //#region src/daemon/agent/agent-extractors.ts
680
+ async function runExtractionCommand(name, command, runCommand, cwd, env, mainResult, signal) {
681
+ try {
682
+ console.log(`Executing extraction command (${name}): ${command}`);
683
+ const res = await runCommand({
684
+ command,
685
+ cwd,
686
+ env,
687
+ stdin: mainResult.stdout,
688
+ signal
689
+ });
690
+ if (res.exitCode === 0) return { result: res.stdout.trim() };
691
+ else return { error: `${name} failed: ${res.stderr}` };
692
+ } catch (e) {
693
+ return { error: `${name} error: ${e.message}` };
694
+ }
695
+ }
696
+ async function extractMessageContent(context, mainResult, runCommand, executionCwd, signal) {
697
+ if (!context.currentAgent.commands?.getMessageContent) return {};
698
+ return runExtractionCommand("getMessageContent", context.currentAgent.commands.getMessageContent, runCommand, executionCwd, context.env, mainResult, signal);
699
+ }
700
+ async function extractSessionId(context, mainResult, runCommand, executionCwd, signal) {
701
+ if (!context.currentAgent.commands?.getSessionId) return {};
702
+ return runExtractionCommand("getSessionId", context.currentAgent.commands.getSessionId, runCommand, executionCwd, context.env, mainResult, signal);
703
+ }
704
+
705
+ //#endregion
706
+ //#region src/daemon/agent/utils.ts
707
+ function formatPendingMessages(payloads) {
708
+ return payloads.map((text) => `<message>\n${text}\n</message>`).join("\n\n");
709
+ }
710
+ function isNewSession(env) {
711
+ return env["SESSION_ID"] === void 0;
712
+ }
713
+
714
+ //#endregion
715
+ //#region src/daemon/agent/agent-runner.ts
716
+ function calculateDelay(attempt, baseDelayMs, isFallback = false) {
717
+ const effectiveAttempt = isFallback ? attempt + 1 : attempt;
718
+ if (effectiveAttempt <= 0) return 0;
719
+ const delay = baseDelayMs * Math.pow(2, effectiveAttempt - 1);
720
+ return Math.min(delay, 15e3);
721
+ }
722
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
723
+ var AgentRunner = class {
724
+ constructor(session, runCommand) {
725
+ this.session = session;
726
+ this.runCommand = runCommand;
727
+ }
728
+ async withTypingIndicator(fn) {
729
+ const interval = setInterval(() => emitTyping(this.session.chatId), 5e3);
730
+ try {
731
+ return await fn();
732
+ } finally {
733
+ clearInterval(interval);
734
+ }
735
+ }
736
+ *getExecutionAttempts() {
737
+ const executionConfigs = [{
738
+ fallback: void 0,
739
+ retries: 0,
740
+ delayMs: 1e3
741
+ }, ...(this.session.settings.fallbacks || []).map((f) => ({
742
+ fallback: f,
743
+ retries: f.retries,
744
+ delayMs: f.delayMs
745
+ }))];
746
+ for (let configIdx = 0; configIdx < executionConfigs.length; configIdx++) {
747
+ const config = executionConfigs[configIdx];
748
+ const isFallbackConfig = configIdx > 0;
749
+ for (let attempt = 0; attempt <= config.retries; attempt++) yield {
750
+ fallback: config.fallback,
751
+ delay: calculateDelay(attempt, config.delayMs, isFallbackConfig)
752
+ };
753
+ }
754
+ }
755
+ async executeSingleAttempt(message, fallback, signal) {
756
+ const context = await this.session.buildExecutionContext(message.content, message.env, fallback);
757
+ if (!context) return { success: false };
758
+ const mainResult = await this.withTypingIndicator(() => this.runCommand({
759
+ command: context.command,
760
+ cwd: this.session.workDirectory,
761
+ env: context.env,
762
+ signal
763
+ }));
764
+ let success = mainResult.exitCode === 0;
765
+ let finalContent = mainResult.stdout.trim();
766
+ const additonalErrors = [];
767
+ if (success && context.currentAgent.commands?.getMessageContent) {
768
+ const extraction = await extractMessageContent(context, mainResult, this.runCommand, this.session.workDirectory, signal);
769
+ if (extraction.error) additonalErrors.push(extraction.error);
770
+ if (extraction.result !== void 0) finalContent = extraction.result.trim();
771
+ if (!finalContent) success = false;
772
+ }
773
+ let extractedSessionId;
774
+ if (success && isNewSession(message.env) && context.currentAgent.commands?.getSessionId) {
775
+ const extraction = await extractSessionId(context, mainResult, this.runCommand, this.session.workDirectory, signal);
776
+ if (extraction.error) additonalErrors.push(extraction.error);
777
+ if (extraction.result) extractedSessionId = extraction.result;
778
+ }
779
+ return {
780
+ success,
781
+ response: {
782
+ messageId: message.id,
783
+ content: finalContent,
784
+ command: context.command,
785
+ cwd: this.session.workDirectory,
786
+ extractedSessionId,
787
+ result: {
788
+ ...mainResult,
789
+ stderr: [mainResult.stderr, ...additonalErrors].join("\n\n")
790
+ }
791
+ }
792
+ };
793
+ }
794
+ async executeWithFallbacks(message, signal) {
795
+ let lastResponse;
796
+ for (const attempt of this.getExecutionAttempts()) {
797
+ if (attempt.delay > 0) {
798
+ await this.session.logger.logCommandRetry({
799
+ messageId: message.id,
800
+ content: `Error running agent, retrying in ${Math.round(attempt.delay / 1e3)} seconds...`,
801
+ cwd: this.session.workDirectory
802
+ });
803
+ await sleep(attempt.delay);
804
+ }
805
+ const attemptResult = await this.executeSingleAttempt(message, attempt.fallback, signal);
806
+ lastResponse = attemptResult.response || lastResponse;
807
+ if (attemptResult.success) return lastResponse;
808
+ }
809
+ return lastResponse;
810
+ }
811
+ };
812
+
813
+ //#endregion
814
+ //#region src/daemon/utils/spawn.ts
815
+ const runCommand = async ({ command, cwd, env, stdin, signal }) => {
816
+ return new Promise((resolve, reject) => {
817
+ const p = spawn(command, {
818
+ shell: true,
819
+ cwd,
820
+ env,
821
+ signal
822
+ });
823
+ if (stdin && p.stdin) {
824
+ p.stdin.on("error", (err) => {
825
+ if (err.code !== "EPIPE") console.error("stdin error:", err);
826
+ });
827
+ p.stdin.write(stdin);
828
+ p.stdin.end();
829
+ }
830
+ let stdout = "";
831
+ let stderr = "";
832
+ if (p.stdout) p.stdout.on("data", (data) => {
833
+ stdout += data.toString();
834
+ if (!stdin) process.stdout.write(data);
835
+ });
836
+ if (p.stderr) p.stderr.on("data", (data) => {
837
+ stderr += data.toString();
838
+ if (!stdin) process.stderr.write(data);
839
+ });
840
+ p.on("close", (code) => {
841
+ resolve({
842
+ stdout,
843
+ stderr,
844
+ exitCode: code ?? 1
845
+ });
846
+ });
847
+ p.on("error", (err) => {
848
+ if (err.name === "AbortError") {
849
+ reject(err);
850
+ return;
851
+ }
852
+ resolve({
853
+ stdout: "",
854
+ stderr: err.toString(),
855
+ exitCode: 1
856
+ });
857
+ });
858
+ });
859
+ };
860
+
861
+ //#endregion
862
+ //#region src/daemon/agent/chat-logger.ts
863
+ function createChatLogger(chatId, subagentId) {
864
+ async function append(msg) {
865
+ const finalMsg = subagentId ? {
866
+ ...msg,
867
+ subagentId
868
+ } : msg;
869
+ await appendMessage(chatId, finalMsg);
870
+ return finalMsg;
871
+ }
872
+ return {
873
+ append,
874
+ getMessages: async (limit) => {
875
+ let filtered = (await getMessages$1(chatId)).filter((m) => m.subagentId === subagentId);
876
+ if (limit !== void 0 && limit > 0) filtered = filtered.slice(-limit);
877
+ return filtered;
878
+ },
879
+ findLastMessage: async (predicate) => {
880
+ return findLastMessage(chatId, (msg) => {
881
+ if (msg.subagentId !== subagentId) return false;
882
+ return predicate(msg);
883
+ });
884
+ },
885
+ logUserMessage: async (msg) => append({
886
+ id: crypto.randomUUID(),
887
+ role: "user",
888
+ content: msg,
889
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
890
+ }),
891
+ logCommandResult: async ({ messageId, content, command, cwd, result }) => append({
892
+ id: crypto.randomUUID(),
893
+ role: "command",
894
+ content,
895
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
896
+ messageId,
897
+ command,
898
+ cwd,
899
+ stdout: result.stdout,
900
+ stderr: result.stderr,
901
+ exitCode: result.exitCode
902
+ }),
903
+ logSystemEvent: async ({ content }) => append({
904
+ id: crypto.randomUUID(),
905
+ role: "command",
906
+ content,
907
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
908
+ messageId: crypto.randomUUID(),
909
+ stderr: "",
910
+ command: "",
911
+ cwd: "",
912
+ stdout: "",
913
+ exitCode: 0
914
+ }),
915
+ logAutomaticReply: async ({ messageId, content }) => append({
916
+ id: crypto.randomUUID(),
917
+ role: "system",
918
+ content,
919
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
920
+ messageId,
921
+ event: "router",
922
+ displayRole: "agent"
923
+ }),
924
+ logCommandRetry: async ({ messageId, content, cwd }) => append({
925
+ id: crypto.randomUUID(),
926
+ role: "command",
927
+ content,
928
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
929
+ messageId,
930
+ command: "retry-delay",
931
+ stderr: "",
932
+ stdout: "",
933
+ cwd,
934
+ exitCode: 0
935
+ }),
936
+ logSystemMessage: async ({ content, event, messageId, displayRole }) => {
937
+ const msg = {
938
+ id: crypto.randomUUID(),
939
+ role: "system",
940
+ content,
941
+ event,
942
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
943
+ };
944
+ if (messageId !== void 0) msg.messageId = messageId;
945
+ if (displayRole !== void 0) msg.displayRole = displayRole;
946
+ return append(msg);
947
+ },
948
+ logSubagentStatus: async ({ subagentId: targetSubagentId, status }) => {
949
+ return append({
950
+ id: crypto.randomUUID(),
951
+ role: "subagent_status",
952
+ content: `Subagent ${status}`,
953
+ subagentId: targetSubagentId,
954
+ status,
955
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
956
+ });
957
+ },
958
+ logAgentReply: async ({ content, files }) => {
959
+ const msg = {
960
+ id: crypto.randomUUID(),
961
+ role: "agent",
962
+ content,
963
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
964
+ };
965
+ if (files !== void 0) msg.files = files;
966
+ return append(msg);
967
+ },
968
+ logToolMessage: async ({ content, messageId, name, payload }) => {
969
+ return append({
970
+ id: crypto.randomUUID(),
971
+ role: "tool",
972
+ content,
973
+ messageId,
974
+ name,
975
+ payload,
976
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
977
+ });
978
+ },
979
+ logPolicyRequestMessage: async ({ content, messageId, requestId, commandName, args, status }) => {
980
+ return append({
981
+ id: crypto.randomUUID(),
982
+ role: "policy",
983
+ content,
984
+ messageId,
985
+ requestId,
986
+ commandName,
987
+ args,
988
+ status,
989
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
990
+ });
991
+ }
992
+ };
993
+ }
994
+
995
+ //#endregion
996
+ //#region src/shared/utils/env.ts
997
+ function applyEnvOverrides(targetEnv, overrides) {
998
+ if (!overrides) return;
999
+ for (const [key, val] of Object.entries(overrides)) if (val === true && process.env[key] !== void 0) targetEnv[key] = process.env[key];
1000
+ else if (typeof val === "string") targetEnv[key] = val;
1001
+ }
1002
+ function getActiveEnvKeys(...envs) {
1003
+ const keys = /* @__PURE__ */ new Set();
1004
+ for (const env of envs) {
1005
+ if (!env) continue;
1006
+ Object.entries(env).forEach(([key, val]) => {
1007
+ if (val === true || typeof val === "string") keys.add(key);
1008
+ });
1009
+ }
1010
+ return keys;
1011
+ }
1012
+
566
1013
  //#endregion
567
1014
  //#region src/daemon/auth.ts
568
1015
  const DAEMON_SECRET = crypto$1.randomBytes(32);
@@ -608,315 +1055,387 @@ function getApiContext(settings) {
608
1055
  }
609
1056
 
610
1057
  //#endregion
611
- //#region src/shared/utils/env.ts
612
- function applyEnvOverrides(targetEnv, overrides) {
613
- if (!overrides) return;
614
- for (const [key, val] of Object.entries(overrides)) if (val === true && process.env[key] !== void 0) targetEnv[key] = process.env[key];
615
- else if (typeof val === "string") targetEnv[key] = val;
616
- }
617
- function getActiveEnvKeys(...envs) {
618
- const keys = /* @__PURE__ */ new Set();
619
- for (const env of envs) {
620
- if (!env) continue;
621
- Object.entries(env).forEach(([key, val]) => {
622
- if (val === true || typeof val === "string") keys.add(key);
1058
+ //#region src/daemon/agent/task-scheduler.ts
1059
+ var ResourceLock = class {
1060
+ resources = /* @__PURE__ */ new Map();
1061
+ async acquire(resourceId, workspaceId, signal) {
1062
+ if (signal?.aborted) {
1063
+ const error = /* @__PURE__ */ new Error("Task aborted");
1064
+ error.name = "AbortError";
1065
+ throw error;
1066
+ }
1067
+ const res = this.resources.get(resourceId);
1068
+ if (!res) {
1069
+ this.resources.set(resourceId, {
1070
+ activeWorkspace: workspaceId,
1071
+ count: 1,
1072
+ waiters: []
1073
+ });
1074
+ return;
1075
+ }
1076
+ if (res.activeWorkspace === workspaceId) {
1077
+ res.count++;
1078
+ return;
1079
+ }
1080
+ return new Promise((resolve, reject) => {
1081
+ const waiter = {
1082
+ workspaceId,
1083
+ resolve,
1084
+ reject
1085
+ };
1086
+ res.waiters.push(waiter);
1087
+ if (signal) {
1088
+ const onAbort = () => {
1089
+ const idx = res.waiters.indexOf(waiter);
1090
+ if (idx !== -1) {
1091
+ res.waiters.splice(idx, 1);
1092
+ const error = /* @__PURE__ */ new Error("Task aborted");
1093
+ error.name = "AbortError";
1094
+ reject(error);
1095
+ }
1096
+ };
1097
+ signal.addEventListener("abort", onAbort);
1098
+ waiter.resolve = () => {
1099
+ signal.removeEventListener("abort", onAbort);
1100
+ resolve();
1101
+ };
1102
+ waiter.reject = (err) => {
1103
+ signal.removeEventListener("abort", onAbort);
1104
+ reject(err);
1105
+ };
1106
+ }
623
1107
  });
624
1108
  }
625
- return keys;
626
- }
1109
+ release(resourceId, _workspaceId) {
1110
+ const res = this.resources.get(resourceId);
1111
+ if (!res) return;
1112
+ res.count--;
1113
+ if (res.count === 0) if (res.waiters.length > 0) {
1114
+ const nextWorkspace = res.waiters[0].workspaceId;
1115
+ res.activeWorkspace = nextWorkspace;
1116
+ const remainingWaiters = [];
1117
+ for (const waiter of res.waiters) if (waiter.workspaceId === nextWorkspace) {
1118
+ res.count++;
1119
+ waiter.resolve();
1120
+ } else remainingWaiters.push(waiter);
1121
+ res.waiters = remainingWaiters;
1122
+ } else this.resources.delete(resourceId);
1123
+ }
1124
+ };
1125
+ var TaskQueue = class {
1126
+ queue = [];
1127
+ activeTask = null;
1128
+ isProcessing = false;
1129
+ constructor(sessionId, resourceLock, onEmpty) {
1130
+ this.sessionId = sessionId;
1131
+ this.resourceLock = resourceLock;
1132
+ this.onEmpty = onEmpty;
1133
+ }
1134
+ enqueue(task) {
1135
+ return new Promise((resolve, reject) => {
1136
+ this.queue.push({
1137
+ task,
1138
+ resolve,
1139
+ reject
1140
+ });
1141
+ this.process();
1142
+ });
1143
+ }
1144
+ async process() {
1145
+ if (this.isProcessing || this.activeTask || this.queue.length === 0) return;
1146
+ this.isProcessing = true;
1147
+ while (this.queue.length > 0) {
1148
+ const next = this.queue.shift();
1149
+ const controller = new AbortController();
1150
+ this.activeTask = {
1151
+ task: next.task,
1152
+ controller
1153
+ };
1154
+ let acquired = false;
1155
+ try {
1156
+ await this.resourceLock.acquire(next.task.dirPath, next.task.rootChatId, controller.signal);
1157
+ acquired = true;
1158
+ if (!controller.signal.aborted) await next.task.execute(controller.signal);
1159
+ next.resolve();
1160
+ } catch (err) {
1161
+ next.reject(err);
1162
+ } finally {
1163
+ if (acquired) this.resourceLock.release(next.task.dirPath, next.task.rootChatId);
1164
+ this.activeTask = null;
1165
+ }
1166
+ }
1167
+ this.isProcessing = false;
1168
+ this.onEmpty(this.sessionId);
1169
+ }
1170
+ abortAll() {
1171
+ const error = /* @__PURE__ */ new Error("Task aborted");
1172
+ error.name = "AbortError";
1173
+ if (this.activeTask) this.activeTask.controller.abort(error);
1174
+ for (const qTask of this.queue) qTask.reject(error);
1175
+ this.queue = [];
1176
+ }
1177
+ interruptAndExtract() {
1178
+ const payloads = [];
1179
+ const error = /* @__PURE__ */ new Error("Task aborted");
1180
+ error.name = "AbortError";
1181
+ if (this.activeTask) {
1182
+ if (this.activeTask.task.text !== void 0) payloads.push(this.activeTask.task.text);
1183
+ this.activeTask.controller.abort(error);
1184
+ }
1185
+ for (const qTask of this.queue) {
1186
+ if (qTask.task.text !== void 0) payloads.push(qTask.task.text);
1187
+ qTask.reject(error);
1188
+ }
1189
+ this.queue = [];
1190
+ return payloads;
1191
+ }
1192
+ extractPending() {
1193
+ const payloads = [];
1194
+ const error = /* @__PURE__ */ new Error("Task extracted for batching");
1195
+ error.name = "AbortError";
1196
+ for (const qTask of this.queue) {
1197
+ if (qTask.task.text !== void 0) payloads.push(qTask.task.text);
1198
+ qTask.reject(error);
1199
+ }
1200
+ this.queue = [];
1201
+ return payloads;
1202
+ }
1203
+ hasTasks() {
1204
+ return this.activeTask !== null || this.queue.length > 0;
1205
+ }
1206
+ };
1207
+ var TaskScheduler = class {
1208
+ queues = /* @__PURE__ */ new Map();
1209
+ resourceLock = new ResourceLock();
1210
+ getQueueKey(sessionId, rootChatId) {
1211
+ return `${rootChatId}:${sessionId}`;
1212
+ }
1213
+ schedule(task) {
1214
+ const key = this.getQueueKey(task.sessionId, task.rootChatId);
1215
+ let queue = this.queues.get(key);
1216
+ if (!queue) {
1217
+ queue = new TaskQueue(task.sessionId, this.resourceLock, () => {
1218
+ this.queues.delete(key);
1219
+ });
1220
+ this.queues.set(key, queue);
1221
+ }
1222
+ return queue.enqueue(task);
1223
+ }
1224
+ hasTasks(sessionId) {
1225
+ for (const queue of this.queues.values()) if (queue.sessionId === sessionId && queue.hasTasks()) return true;
1226
+ return false;
1227
+ }
1228
+ extractPending(sessionId) {
1229
+ const payloads = [];
1230
+ for (const queue of this.queues.values()) if (queue.sessionId === sessionId) payloads.push(...queue.extractPending());
1231
+ return payloads;
1232
+ }
1233
+ abortTasks(sessionId) {
1234
+ for (const queue of this.queues.values()) if (queue.sessionId === sessionId) queue.abortAll();
1235
+ }
1236
+ interruptTasks(sessionId) {
1237
+ const payloads = [];
1238
+ for (const queue of this.queues.values()) if (queue.sessionId === sessionId) payloads.push(...queue.interruptAndExtract());
1239
+ return payloads;
1240
+ }
1241
+ };
1242
+ const taskScheduler = new TaskScheduler();
627
1243
 
628
1244
  //#endregion
629
- //#region src/daemon/message.ts
630
- function calculateDelay(attempt, baseDelayMs, isFallback = false) {
631
- const effectiveAttempt = isFallback ? attempt + 1 : attempt;
632
- if (effectiveAttempt <= 0) return 0;
633
- const delay = baseDelayMs * Math.pow(2, effectiveAttempt - 1);
634
- return Math.min(delay, 15e3);
635
- }
636
- function formatPendingMessages(payloads) {
637
- return payloads.map((text) => `<message>\n${text}\n</message>`).join("\n\n");
638
- }
639
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
640
- async function resolveSessionState(chatId, cwd, sessionId, overrideAgentId) {
641
- const chatSettings = await readChatSettings(chatId, cwd);
642
- const agentId = overrideAgentId ?? (typeof chatSettings?.defaultAgent === "string" ? chatSettings.defaultAgent : "default");
643
- let targetSessionId = sessionId;
644
- if (!targetSessionId) targetSessionId = (chatSettings?.sessions || {})[agentId] || "default";
645
- const agentSessionSettings = await readAgentSessionSettings(agentId, targetSessionId, cwd);
646
- return {
647
- chatSettings,
648
- agentId,
649
- targetSessionId,
650
- agentSessionSettings,
651
- isNewSession: !agentSessionSettings
652
- };
653
- }
654
- function prepareCommandAndEnv(agent, message, isNewSession, agentSessionSettings, fallback) {
655
- const currentAgent = {
656
- ...agent,
657
- commands: {
658
- ...agent.commands,
659
- ...fallback?.commands || {}
660
- },
661
- env: {
662
- ...agent.env,
663
- ...fallback?.env || {}
664
- }
665
- };
666
- let command = currentAgent.commands?.new ?? "";
667
- const env = {
668
- ...process.env,
669
- CLAW_CLI_MESSAGE: message
670
- };
671
- applyEnvOverrides(env, currentAgent.env);
672
- if (!isNewSession && currentAgent.commands?.append) {
673
- command = currentAgent.commands.append;
674
- applyEnvOverrides(env, agentSessionSettings?.env);
1245
+ //#region src/daemon/agent/agent-session.ts
1246
+ var AgentSession = class {
1247
+ agentId;
1248
+ sessionId;
1249
+ chatId;
1250
+ subagentId;
1251
+ settings;
1252
+ workspaceRoot;
1253
+ globalSettings;
1254
+ logger;
1255
+ constructor(config) {
1256
+ this.agentId = config.agentId;
1257
+ this.sessionId = config.sessionId;
1258
+ this.chatId = config.chatId;
1259
+ this.subagentId = config.subagentId;
1260
+ this.settings = config.settings;
1261
+ this.workspaceRoot = config.workspaceRoot;
1262
+ this.globalSettings = config.globalSettings;
1263
+ this.logger = config.logger ?? createChatLogger(this.chatId, this.subagentId);
675
1264
  }
676
- return {
677
- command,
678
- env,
679
- currentAgent
680
- };
681
- }
682
- async function runExtractionCommand(name, command, runCommand, cwd, env, mainResult, signal) {
683
- try {
684
- console.log(`Executing extraction command (${name}): ${command}`);
685
- const res = await runCommand({
1265
+ async buildExecutionContext(messageContent, routerEnv, fallback) {
1266
+ const currentAgent = {
1267
+ ...this.settings,
1268
+ commands: {
1269
+ ...this.settings.commands,
1270
+ ...fallback?.commands || {}
1271
+ },
1272
+ env: {
1273
+ ...this.settings.env,
1274
+ ...this.subagentId && this.settings.subagentEnv ? this.settings.subagentEnv : {},
1275
+ ...fallback?.env || {}
1276
+ }
1277
+ };
1278
+ let initialCommand = currentAgent.commands?.new ?? "";
1279
+ const env = {
1280
+ ...process.env,
1281
+ CLAW_CLI_MESSAGE: messageContent
1282
+ };
1283
+ applyEnvOverrides(env, currentAgent.env);
1284
+ if (!isNewSession(routerEnv) && currentAgent.commands?.append) initialCommand = currentAgent.commands.append;
1285
+ if (!initialCommand) return null;
1286
+ const agentSpecificEnvKeys = getActiveEnvKeys(currentAgent.env);
1287
+ agentSpecificEnvKeys.add("CLAW_CLI_MESSAGE");
1288
+ Object.assign(env, routerEnv);
1289
+ Object.keys(routerEnv).forEach((k) => agentSpecificEnvKeys.add(k));
1290
+ const apiCtx = getApiContext(this.globalSettings);
1291
+ if (apiCtx) {
1292
+ const proxyUrl = apiCtx.proxy_host ? `${apiCtx.proxy_host}:${apiCtx.port}` : `http://${apiCtx.host}:${apiCtx.port}`;
1293
+ if (currentAgent.apiUrlEnvVar) {
1294
+ env[currentAgent.apiUrlEnvVar] = proxyUrl;
1295
+ agentSpecificEnvKeys.add(currentAgent.apiUrlEnvVar);
1296
+ env["CLAW_LITE_URL_VAR"] = currentAgent.apiUrlEnvVar;
1297
+ agentSpecificEnvKeys.add("CLAW_LITE_URL_VAR");
1298
+ } else {
1299
+ env["CLAW_API_URL"] = proxyUrl;
1300
+ agentSpecificEnvKeys.add("CLAW_API_URL");
1301
+ }
1302
+ const token = generateToken({
1303
+ chatId: this.chatId,
1304
+ agentId: this.agentId,
1305
+ sessionId: this.sessionId,
1306
+ ...this.subagentId ? { subagentId: this.subagentId } : {},
1307
+ timestamp: Date.now()
1308
+ });
1309
+ if (currentAgent.apiTokenEnvVar) {
1310
+ env[currentAgent.apiTokenEnvVar] = token;
1311
+ agentSpecificEnvKeys.add(currentAgent.apiTokenEnvVar);
1312
+ env["CLAW_LITE_API_VAR"] = currentAgent.apiTokenEnvVar;
1313
+ agentSpecificEnvKeys.add("CLAW_LITE_API_VAR");
1314
+ } else {
1315
+ env["CLAW_API_TOKEN"] = token;
1316
+ agentSpecificEnvKeys.add("CLAW_API_TOKEN");
1317
+ }
1318
+ }
1319
+ let command = initialCommand;
1320
+ command = await sandboxExecutionContext(command, env, agentSpecificEnvKeys, this.workDirectory, this.workspaceRoot);
1321
+ return {
686
1322
  command,
687
- cwd,
688
1323
  env,
689
- stdin: mainResult.stdout,
690
- signal
691
- });
692
- if (res.exitCode === 0) return { result: res.stdout.trim() };
693
- else return { error: `${name} failed: ${res.stderr}` };
694
- } catch (e) {
695
- return { error: `${name} error: ${e.message}` };
1324
+ currentAgent
1325
+ };
696
1326
  }
697
- }
698
- /**
699
- * Formats the environment prefix string by replacing placeholders with actual values.
700
- * Available placeholders:
701
- * - {WORKSPACE_DIR}: The root directory of the workspace.
702
- * - {AGENT_DIR}: The directory where the agent is executing.
703
- * - {ENV_DIR}: The directory of the active environment.
704
- * - {HOME_DIR}: The home directory of the current user.
705
- * - {ENV_ARGS}: The formatted environment arguments based on envFormat.
706
- */
707
- function formatEnvironmentPrefix(prefix, replacements) {
708
- const map = {
709
- "{WORKSPACE_DIR}": replacements.targetPath,
710
- "{AGENT_DIR}": replacements.executionCwd,
711
- "{ENV_DIR}": replacements.envDir,
712
- "{HOME_DIR}": process.env.HOME || "",
713
- "{ENV_ARGS}": replacements.envArgs
714
- };
715
- return prefix.replace(/{(WORKSPACE_DIR|AGENT_DIR|ENV_DIR|HOME_DIR|ENV_ARGS)}/g, (match) => map[match] || match);
716
- }
717
- async function executeDirectMessage(chatId, state, settings, cwd, runCommand, noWait = false, userMessageContent) {
718
- const userMsg = {
719
- id: state.messageId ?? crypto.randomUUID(),
720
- role: "user",
721
- content: userMessageContent ?? state.message,
722
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
723
- };
724
- await appendMessage(chatId, userMsg);
725
- if (state.reply) await appendMessage(chatId, {
726
- id: crypto.randomUUID(),
727
- messageId: userMsg.id,
728
- role: "log",
729
- source: "router",
730
- content: state.reply,
731
- stderr: "",
732
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
733
- command: "router",
734
- cwd,
735
- exitCode: 0,
736
- ...state.reply.includes("NO_REPLY_NECESSARY") ? { level: "verbose" } : {}
737
- });
738
- if (!state.message.trim() && state.action !== "stop" && state.action !== "interrupt") return;
739
- const queue = getMessageQueue(cwd);
740
- if (state.action === "stop") {
741
- queue.abortCurrent();
742
- queue.clear();
743
- return;
1327
+ createRunner() {
1328
+ return new AgentRunner(this, runCommand);
744
1329
  }
745
- if (state.action === "interrupt") {
746
- const targetSessionId = state.sessionId || "default";
747
- const isMatchingSession = (p) => p.sessionId === targetSessionId;
748
- const currentPayload = queue.getCurrentPayload();
749
- const currentMatches = currentPayload ? isMatchingSession(currentPayload) : false;
750
- const extracted = queue.extractPending(isMatchingSession);
751
- queue.abortCurrent(isMatchingSession);
752
- const payloads = currentMatches && currentPayload ? [currentPayload, ...extracted] : extracted;
753
- if (payloads.length > 0) state.message = `${formatPendingMessages(payloads.map((p) => p.text))}\n\n<message>\n${state.message}\n</message>`.trim();
754
- }
755
- if (!state.message.trim()) return;
756
- const routerEnv = state.env ?? {};
757
- const taskPromise = queue.enqueue(async (signal) => {
758
- const { agentId, agentSessionSettings, isNewSession, targetSessionId: finalSessionId } = await resolveSessionState(chatId, cwd, state.sessionId, state.agentId);
759
- let mergedAgent = settings?.defaultAgent || {};
760
- if (agentId !== "default") try {
761
- const customAgent = await getAgent(agentId, cwd);
762
- if (customAgent) mergedAgent = {
763
- ...mergedAgent,
764
- ...customAgent,
765
- commands: {
766
- ...mergedAgent.commands,
767
- ...customAgent.commands
768
- },
769
- env: {
770
- ...mergedAgent.env,
771
- ...customAgent.env
772
- }
1330
+ get workDirectory() {
1331
+ return resolveAgentWorkDir(this.agentId, this.settings.directory, this.workspaceRoot);
1332
+ }
1333
+ stop() {
1334
+ taskScheduler.abortTasks(this.sessionId);
1335
+ }
1336
+ interrupt(message) {
1337
+ const payloads = taskScheduler.interruptTasks(this.sessionId);
1338
+ if (payloads.length > 0) {
1339
+ const pendingText = formatPendingMessages(payloads);
1340
+ return {
1341
+ ...message,
1342
+ content: `${pendingText}\n\n<message>\n${message.content}\n</message>`.trim()
773
1343
  };
774
- } catch {}
775
- const executionConfigs = [{
776
- retries: 0,
777
- delayMs: 1e3
778
- }, ...(mergedAgent.fallbacks || []).map((f) => ({
779
- fallback: f,
780
- retries: f.retries,
781
- delayMs: f.delayMs
782
- }))];
783
- const workspaceRoot = getWorkspaceRoot(cwd);
784
- let executionCwd = cwd;
785
- if (mergedAgent.directory) executionCwd = path.resolve(workspaceRoot, mergedAgent.directory);
786
- else if (agentId !== "default") executionCwd = path.resolve(workspaceRoot, agentId);
787
- let lastLogMsg;
788
- let success = false;
789
- for (let configIdx = 0; configIdx < executionConfigs.length; configIdx++) {
790
- const config = executionConfigs[configIdx];
791
- const isFallbackConfig = configIdx > 0;
792
- for (let attempt = 0; attempt <= config.retries; attempt++) {
793
- const delay = calculateDelay(attempt, config.delayMs, isFallbackConfig);
794
- if (delay > 0) {
795
- await appendMessage(chatId, {
796
- id: crypto.randomUUID(),
797
- messageId: userMsg.id,
798
- role: "log",
799
- content: `Error running agent, retrying in ${Math.round(delay / 1e3)} seconds...`,
800
- stderr: "",
801
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
802
- command: "retry-delay",
803
- cwd: executionCwd,
804
- exitCode: 0
805
- });
806
- await sleep(delay);
807
- }
808
- const { env, currentAgent, command: initialCommand } = prepareCommandAndEnv(mergedAgent, state.message, isNewSession, agentSessionSettings, config.fallback);
809
- let command = initialCommand;
810
- if (!command) continue;
811
- const agentSpecificEnv = getActiveEnvKeys(currentAgent.env, !isNewSession ? agentSessionSettings?.env : void 0);
812
- agentSpecificEnv.add("CLAW_CLI_MESSAGE");
813
- Object.assign(env, routerEnv);
814
- Object.keys(routerEnv).forEach((k) => agentSpecificEnv.add(k));
815
- const apiCtx = getApiContext(settings);
816
- if (apiCtx) {
817
- env["CLAW_API_URL"] = apiCtx.proxy_host ? `${apiCtx.proxy_host}:${apiCtx.port}` : `http://${apiCtx.host}:${apiCtx.port}`;
818
- agentSpecificEnv.add("CLAW_API_URL");
819
- env["CLAW_API_TOKEN"] = generateToken({
820
- chatId,
821
- agentId,
822
- sessionId: finalSessionId,
823
- timestamp: Date.now()
824
- });
825
- agentSpecificEnv.add("CLAW_API_TOKEN");
826
- }
827
- const activeEnvInfo = await getActiveEnvironmentInfo(executionCwd, cwd);
828
- if (activeEnvInfo) {
829
- const activeEnvName = activeEnvInfo.name;
830
- const activeEnv = await readEnvironment(activeEnvName, cwd);
831
- if (activeEnv?.env) for (const [key, value] of Object.entries(activeEnv.env)) if (value === false) {
832
- delete env[key];
833
- agentSpecificEnv.delete(key);
834
- } else {
835
- let interpolatedValue = String(value);
836
- interpolatedValue = interpolatedValue.replace(/\{PATH\}/g, process.env.PATH || "");
837
- interpolatedValue = interpolatedValue.replace(/\{ENV_DIR\}/g, getEnvironmentPath(activeEnvName, cwd));
838
- interpolatedValue = interpolatedValue.replace(/\{WORKSPACE_DIR\}/g, activeEnvInfo.targetPath);
839
- env[key] = interpolatedValue;
840
- agentSpecificEnv.add(key);
841
- }
842
- if (activeEnv?.prefix) {
843
- const envArgs = Array.from(agentSpecificEnv).map((key) => {
844
- if (activeEnv.envFormat) return activeEnv.envFormat.replace("{key}", key);
845
- return key;
846
- }).join(" ");
847
- const prefixReplaced = formatEnvironmentPrefix(activeEnv.prefix, {
848
- targetPath: activeEnvInfo.targetPath,
849
- executionCwd,
850
- envDir: getEnvironmentPath(activeEnvName, cwd),
851
- envArgs
852
- });
853
- if (prefixReplaced.includes("{COMMAND}")) command = prefixReplaced.replace("{COMMAND}", command);
854
- else command = `${prefixReplaced} ${command}`;
855
- }
856
- }
857
- console.log(`Executing command: ${command}`);
858
- let mainResult;
859
- const typingInterval = setInterval(() => {
860
- emitTyping(chatId);
861
- }, 5e3);
862
- try {
863
- mainResult = await runCommand({
864
- command,
865
- cwd: executionCwd,
866
- env,
867
- signal
868
- });
869
- } finally {
870
- clearInterval(typingInterval);
871
- }
872
- const logMsg = {
873
- id: crypto.randomUUID(),
874
- messageId: userMsg.id,
875
- role: "log",
876
- content: mainResult.stdout,
877
- stdout: mainResult.stdout,
878
- stderr: "",
879
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
880
- command,
881
- cwd: executionCwd,
882
- exitCode: mainResult.exitCode,
883
- ...mainResult.stdout.includes("NO_REPLY_NECESSARY") ? { level: "verbose" } : {}
884
- };
885
- const errors = [];
886
- if (mainResult.stderr) errors.push(mainResult.stderr);
887
- let currentSuccess = mainResult.exitCode === 0;
888
- if (currentSuccess) {
889
- if (currentAgent.commands?.getMessageContent) {
890
- const { result, error } = await runExtractionCommand("getMessageContent", currentAgent.commands.getMessageContent, runCommand, executionCwd, env, mainResult, signal);
891
- if (result !== void 0) {
892
- logMsg.content = result;
893
- logMsg.stdout = mainResult.stdout;
894
- if (result.includes("NO_REPLY_NECESSARY")) logMsg.level = "verbose";
895
- else delete logMsg.level;
896
- if (result.trim() === "") currentSuccess = false;
897
- }
898
- if (error) errors.push(error);
899
- }
900
- }
901
- logMsg.stderr = errors.join("\n\n");
902
- lastLogMsg = logMsg;
903
- if (currentSuccess) {
904
- success = true;
905
- if (isNewSession && currentAgent.commands?.getSessionId) {
906
- const { result, error } = await runExtractionCommand("getSessionId", currentAgent.commands.getSessionId, runCommand, executionCwd, env, mainResult, signal);
907
- if (result) await writeAgentSessionSettings(agentId, finalSessionId, { env: { SESSION_ID: result } }, cwd);
908
- if (error) logMsg.stderr = [logMsg.stderr, error].filter(Boolean).join("\n\n");
909
- }
910
- break;
911
- }
1344
+ }
1345
+ return message;
1346
+ }
1347
+ async handleMessage(message) {
1348
+ if (!message.content.trim()) return;
1349
+ await taskScheduler.schedule({
1350
+ id: `task-${this.agentId}-${randomUUID()}`,
1351
+ rootChatId: this.chatId,
1352
+ dirPath: this.workDirectory,
1353
+ sessionId: this.sessionId,
1354
+ text: message.content,
1355
+ execute: async (signal) => {
1356
+ const sessionSettings = await readAgentSessionSettings(this.agentId, this.sessionId, this.workspaceRoot);
1357
+ applyEnvOverrides(message.env, sessionSettings?.env);
1358
+ const result = await this.createRunner().executeWithFallbacks(message, signal);
1359
+ if (!result) return;
1360
+ if (result.extractedSessionId) await writeAgentSessionSettings(this.agentId, this.sessionId, { env: { SESSION_ID: result.extractedSessionId } }, this.workspaceRoot);
1361
+ await this.logger.logCommandResult(result);
1362
+ if (!result.content.includes("NO_REPLY_NECESSARY")) await this.logger.logAgentReply({ content: result.content });
1363
+ }
1364
+ });
1365
+ }
1366
+ };
1367
+ async function createAgentSession(options) {
1368
+ const settings = options.settings ?? await readSettings(options.cwd) ?? void 0;
1369
+ const mergedAgent = await resolveMergedAgent(options.agentId, settings, options.cwd);
1370
+ const workspaceRoot = getWorkspaceRoot(options.cwd);
1371
+ return new AgentSession({
1372
+ agentId: options.agentId,
1373
+ sessionId: options.sessionId,
1374
+ chatId: options.chatId,
1375
+ ...options.subagentId ? { subagentId: options.subagentId } : {},
1376
+ settings: mergedAgent,
1377
+ workspaceRoot,
1378
+ globalSettings: settings,
1379
+ ...options.logger ? { logger: options.logger } : {}
1380
+ });
1381
+ }
1382
+ async function resolveMergedAgent(agentId, settings, cwd) {
1383
+ let mergedAgent = settings?.defaultAgent || {};
1384
+ if (agentId !== "default") try {
1385
+ const customAgent = await getAgent(agentId, cwd);
1386
+ if (customAgent) mergedAgent = {
1387
+ ...mergedAgent,
1388
+ ...customAgent,
1389
+ commands: {
1390
+ ...mergedAgent.commands,
1391
+ ...customAgent.commands
1392
+ },
1393
+ env: {
1394
+ ...mergedAgent.env,
1395
+ ...customAgent.env
912
1396
  }
913
- if (success) break;
914
- }
915
- if (lastLogMsg) await appendMessage(chatId, lastLogMsg);
916
- }, {
917
- text: state.message,
918
- sessionId: state.sessionId || "default"
1397
+ };
1398
+ } catch {}
1399
+ return mergedAgent;
1400
+ }
1401
+
1402
+ //#endregion
1403
+ //#region src/daemon/message.ts
1404
+ async function executeDirectMessage(chatId, state, settings, cwd, noWait = false, userMessageContent, subagentId, systemEvent, displayRole) {
1405
+ const logger = createChatLogger(chatId, subagentId);
1406
+ let msgId;
1407
+ if (systemEvent) msgId = (await logger.logSystemMessage({
1408
+ content: userMessageContent ?? state.message,
1409
+ event: systemEvent,
1410
+ messageId: state.messageId,
1411
+ ...displayRole ? { displayRole } : {}
1412
+ })).id;
1413
+ else msgId = (await logger.logUserMessage(userMessageContent ?? state.message)).id;
1414
+ if (state.reply) await logger.logAutomaticReply({
1415
+ messageId: msgId,
1416
+ content: state.reply
1417
+ });
1418
+ if (!state.message.trim() && state.action !== "stop" && state.action !== "interrupt") return;
1419
+ const agentSession = await createAgentSession({
1420
+ chatId,
1421
+ agentId: state.agentId || "default",
1422
+ sessionId: state.sessionId || "default",
1423
+ ...subagentId ? { subagentId } : {},
1424
+ cwd,
1425
+ settings,
1426
+ logger
919
1427
  });
1428
+ let finalMessage = {
1429
+ id: state.messageId,
1430
+ content: state.message,
1431
+ env: state.env ?? {}
1432
+ };
1433
+ if (state.action === "stop") {
1434
+ agentSession.stop();
1435
+ return;
1436
+ }
1437
+ if (state.action === "interrupt") finalMessage = agentSession.interrupt(finalMessage);
1438
+ const taskPromise = agentSession.handleMessage(finalMessage);
920
1439
  if (!noWait) try {
921
1440
  await taskPromise;
922
1441
  } catch (err) {
@@ -926,12 +1445,11 @@ async function executeDirectMessage(chatId, state, settings, cwd, runCommand, no
926
1445
  if (err.name !== "AbortError") console.error("Task execution error:", err);
927
1446
  });
928
1447
  }
929
- async function getInitialRouterState(chatId, message, cwd = process.cwd(), overrideAgentId, overrideSessionId, overrideMessageId) {
930
- const chatSettings = await readChatSettings(chatId, cwd) ?? {};
1448
+ async function getInitialRouterState(chatId, message, chatSettings, overrideAgentId, overrideSessionId) {
931
1449
  const agentId = overrideAgentId ?? chatSettings.defaultAgent ?? "default";
932
1450
  const sessionId = overrideSessionId ?? chatSettings.sessions?.[agentId] ?? "default";
933
1451
  return {
934
- messageId: overrideMessageId ?? crypto.randomUUID(),
1452
+ messageId: crypto.randomUUID(),
935
1453
  message,
936
1454
  chatId,
937
1455
  agentId,
@@ -939,92 +1457,52 @@ async function getInitialRouterState(chatId, message, cwd = process.cwd(), overr
939
1457
  env: {}
940
1458
  };
941
1459
  }
942
- async function handleUserMessage(chatId, message, settings, cwd = process.cwd(), noWait = false, runCommand, sessionId, overrideAgentId) {
1460
+ async function handleUserMessage(chatId, message, settings, cwd = process.cwd(), noWait = false, sessionId, overrideAgentId) {
943
1461
  const chatSettings = await readChatSettings(chatId, cwd) ?? {};
944
1462
  if (overrideAgentId && chatSettings.defaultAgent !== overrideAgentId) {
945
1463
  chatSettings.defaultAgent = overrideAgentId;
946
1464
  await writeChatSettings(chatId, chatSettings, cwd);
947
1465
  }
948
- const initialState = await getInitialRouterState(chatId, message, cwd, overrideAgentId, sessionId);
949
- const initialAgent = initialState.agentId;
950
- const finalState = await executeRouterPipeline(initialState, chatSettings.routers ?? settings?.routers ?? []);
951
- const finalMessage = finalState.message;
1466
+ const initialState = await getInitialRouterState(chatId, message, chatSettings, overrideAgentId, sessionId);
1467
+ const finalState = await executeRouterPipeline(initialState, resolveRouters(chatSettings.routers ?? settings?.routers ?? [], true));
1468
+ await applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, initialState.agentId);
1469
+ await executeDirectMessage(chatId, finalState, settings, cwd, noWait, message);
1470
+ }
1471
+ async function applyRouterStateUpdates(chatId, cwd, finalState, chatSettings, initialAgent) {
952
1472
  const finalAgentId = finalState.agentId;
953
1473
  const finalSessionId = finalState.sessionId ?? crypto.randomUUID();
954
- const routerEnv = finalState.env ?? {};
955
1474
  const currentAgentId = finalAgentId ?? chatSettings.defaultAgent ?? "default";
956
1475
  let settingsChanged = false;
957
1476
  if (finalAgentId && finalAgentId !== initialAgent) {
958
1477
  chatSettings.defaultAgent = finalAgentId;
959
1478
  settingsChanged = true;
960
1479
  }
961
- if (finalSessionId && chatSettings.sessions?.[currentAgentId] !== finalSessionId) {
1480
+ if (finalState.nextSessionId) {
962
1481
  chatSettings.sessions = chatSettings.sessions || {};
963
- chatSettings.sessions[currentAgentId] = finalSessionId;
1482
+ chatSettings.sessions[currentAgentId] = finalState.nextSessionId;
964
1483
  settingsChanged = true;
965
1484
  }
1485
+ if (finalState.jobs) {
1486
+ chatSettings.jobs = chatSettings.jobs || [];
1487
+ if (finalState.jobs.remove?.length) {
1488
+ const removeSet = new Set(finalState.jobs.remove);
1489
+ for (const jobId of finalState.jobs.remove) cronManager.unscheduleJob(chatId, jobId);
1490
+ chatSettings.jobs = chatSettings.jobs.filter((job) => !removeSet.has(job.id));
1491
+ settingsChanged = true;
1492
+ }
1493
+ if (finalState.jobs.add?.length) {
1494
+ const addMap = new Map(finalState.jobs.add.map((job) => [job.id, job]));
1495
+ for (const job of finalState.jobs.add) cronManager.scheduleJob(chatId, job);
1496
+ chatSettings.jobs = chatSettings.jobs.filter((job) => !addMap.has(job.id));
1497
+ chatSettings.jobs.push(...finalState.jobs.add);
1498
+ settingsChanged = true;
1499
+ }
1500
+ }
966
1501
  if (settingsChanged) await writeChatSettings(chatId, chatSettings, cwd);
967
- const directState = {
968
- messageId: finalState.messageId,
969
- message: finalMessage,
970
- chatId,
971
- env: routerEnv
972
- };
973
- if (finalAgentId !== void 0) directState.agentId = finalAgentId;
974
- if (finalSessionId !== void 0) directState.sessionId = finalSessionId;
975
- if (finalState.reply !== void 0) directState.reply = finalState.reply;
976
- if (finalState.action !== void 0) directState.action = finalState.action;
977
- await executeDirectMessage(chatId, directState, settings, cwd, runCommand, noWait, message);
1502
+ if (finalState.sessionId === void 0) finalState.sessionId = finalSessionId;
1503
+ if (finalState.agentId === void 0) finalState.agentId = currentAgentId;
978
1504
  }
979
1505
 
980
- //#endregion
981
- //#region src/daemon/utils/spawn.ts
982
- const runCommand = async ({ command, cwd, env, stdin, signal, logToTerminal }) => {
983
- return new Promise((resolve, reject) => {
984
- const p = spawn(command, {
985
- shell: true,
986
- cwd,
987
- env,
988
- signal
989
- });
990
- if (stdin && p.stdin) {
991
- p.stdin.on("error", (err) => {
992
- if (err.code !== "EPIPE") console.error("stdin error:", err);
993
- });
994
- p.stdin.write(stdin);
995
- p.stdin.end();
996
- }
997
- let stdout = "";
998
- let stderr = "";
999
- if (p.stdout) p.stdout.on("data", (data) => {
1000
- stdout += data.toString();
1001
- if (logToTerminal && !stdin) process.stdout.write(data);
1002
- });
1003
- if (p.stderr) p.stderr.on("data", (data) => {
1004
- stderr += data.toString();
1005
- if (logToTerminal && !stdin) process.stderr.write(data);
1006
- });
1007
- p.on("close", (code) => {
1008
- resolve({
1009
- stdout,
1010
- stderr,
1011
- exitCode: code ?? 1
1012
- });
1013
- });
1014
- p.on("error", (err) => {
1015
- if (err.name === "AbortError") {
1016
- reject(err);
1017
- return;
1018
- }
1019
- resolve({
1020
- stdout: "",
1021
- stderr: err.toString(),
1022
- exitCode: 1
1023
- });
1024
- });
1025
- });
1026
- };
1027
-
1028
1506
  //#endregion
1029
1507
  //#region src/daemon/cron.ts
1030
1508
  var CronManager = class {
@@ -1107,14 +1585,25 @@ var CronManager = class {
1107
1585
  } catch {
1108
1586
  globalSettings = void 0;
1109
1587
  }
1110
- const overrideSessionId = job.session?.type === "new" ? crypto.randomUUID() : void 0;
1111
- const routerState = await getInitialRouterState(chatId, job.message, process.cwd(), job.agentId, overrideSessionId);
1588
+ const overrideSessionId = job.session?.type === "new" ? crypto.randomUUID() : job.session?.id;
1589
+ const chatSettings = await readChatSettings(chatId, process.cwd()) ?? {};
1590
+ let routerState = await getInitialRouterState(chatId, job.message, chatSettings, job.agentId, overrideSessionId);
1112
1591
  if (job.env !== void 0) {
1113
1592
  routerState.env = routerState.env || {};
1114
1593
  applyEnvOverrides(routerState.env, job.env);
1115
1594
  }
1116
- if (job.reply !== void 0) routerState.reply = job.reply;
1117
- await executeDirectMessage(chatId, routerState, globalSettings, process.cwd(), runCommand, false, job.message);
1595
+ const currentAgentId = job.agentId ?? chatSettings.defaultAgent ?? "default";
1596
+ const currentActiveSession = chatSettings.sessions?.[currentAgentId];
1597
+ const isOutdatedSession = job.session?.type === "existing" && currentActiveSession !== void 0 && currentActiveSession !== job.session.id;
1598
+ if (job.reply !== void 0 && !isOutdatedSession) routerState.reply = job.reply;
1599
+ if (job.nextSessionId !== void 0 && !isOutdatedSession) routerState.nextSessionId = job.nextSessionId;
1600
+ if (job.action !== void 0) routerState.action = job.action;
1601
+ if (job.jobs !== void 0) routerState.jobs = job.jobs;
1602
+ const resolvedRouters = resolveRouters(chatSettings.routers ?? globalSettings?.routers ?? [], false);
1603
+ const initialState = { ...routerState };
1604
+ routerState = await executeRouterPipeline(routerState, resolvedRouters);
1605
+ await applyRouterStateUpdates(chatId, process.cwd(), routerState, chatSettings, initialState.agentId);
1606
+ await executeDirectMessage(chatId, routerState, globalSettings, process.cwd(), false, job.message, void 0, "cron");
1118
1607
  if (isOneOff) {
1119
1608
  const chatSettings = await readChatSettings(chatId);
1120
1609
  if (chatSettings && chatSettings.jobs) {
@@ -1297,10 +1786,7 @@ const sendMessage = apiProcedure.input(z.object({
1297
1786
  const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join("\n")}`;
1298
1787
  message = message ? `${message}\n\n${fileList}` : fileList;
1299
1788
  }
1300
- await handleUserMessage(chatId, message, settings, void 0, noWait, (args) => runCommand({
1301
- ...args,
1302
- logToTerminal: true
1303
- }), sessionId, agentId);
1789
+ await handleUserMessage(chatId, message, settings, void 0, noWait, sessionId, agentId);
1304
1790
  return { success: true };
1305
1791
  });
1306
1792
  const getMessages = apiProcedure.input(z.object({
@@ -1348,6 +1834,23 @@ const shutdown = publicProcedure.mutation(() => {
1348
1834
  const userListCronJobs = apiProcedure.input(z.object({ chatId: z.string().optional() })).query(async ({ input }) => {
1349
1835
  return listCronJobsShared(input.chatId ?? await getDefaultChatId());
1350
1836
  });
1837
+ const getChats = apiProcedure.query(async () => {
1838
+ return listChats();
1839
+ });
1840
+ const getAgents = apiProcedure.query(async () => {
1841
+ return listAgents();
1842
+ });
1843
+ const userCreateChat = apiProcedure.input(z.object({
1844
+ chatId: z.string(),
1845
+ agent: z.string().optional()
1846
+ })).mutation(async ({ input }) => {
1847
+ await createChat(input.chatId);
1848
+ if (input.agent) await writeChatSettings(input.chatId, { defaultAgent: input.agent });
1849
+ return {
1850
+ success: true,
1851
+ chatId: input.chatId
1852
+ };
1853
+ });
1351
1854
  const userAddCronJob = apiProcedure.input(z.object({
1352
1855
  chatId: z.string().optional(),
1353
1856
  job: CronJobSchema
@@ -1369,7 +1872,10 @@ const userRouter = router({
1369
1872
  shutdown,
1370
1873
  listCronJobs: userListCronJobs,
1371
1874
  addCronJob: userAddCronJob,
1372
- deleteCronJob: userDeleteCronJob
1875
+ deleteCronJob: userDeleteCronJob,
1876
+ getChats,
1877
+ getAgents,
1878
+ createChat: userCreateChat
1373
1879
  });
1374
1880
 
1375
1881
  //#endregion
@@ -1385,7 +1891,7 @@ var PolicyRequestService = class {
1385
1891
  this.snapshotDir = snapshotDir;
1386
1892
  this.maxPending = maxPending;
1387
1893
  }
1388
- async createRequest(commandName, args, fileMappings, chatId, agentId) {
1894
+ async createRequest(commandName, args, fileMappings, chatId, agentId, skipSave = false, subagentId) {
1389
1895
  const allRequests = await this.store.list();
1390
1896
  if (allRequests.filter((r) => r.state === "Pending").length >= this.maxPending) throw new Error(`Maximum number of pending requests (${this.maxPending}) reached.`);
1391
1897
  const snapshotMappings = {};
@@ -1399,12 +1905,13 @@ var PolicyRequestService = class {
1399
1905
  commandName,
1400
1906
  args,
1401
1907
  fileMappings: snapshotMappings,
1402
- state: "Pending",
1908
+ state: skipSave ? "Approved" : "Pending",
1403
1909
  createdAt: Date.now(),
1404
1910
  chatId,
1405
- agentId
1911
+ agentId,
1912
+ ...subagentId ? { subagentId } : {}
1406
1913
  };
1407
- await this.store.save(request);
1914
+ if (!skipSave) await this.store.save(request);
1408
1915
  return request;
1409
1916
  }
1410
1917
  getInterpolatedArgs(request) {
@@ -1412,6 +1919,295 @@ var PolicyRequestService = class {
1412
1919
  }
1413
1920
  };
1414
1921
 
1922
+ //#endregion
1923
+ //#region src/daemon/api/subagent-utils.ts
1924
+ function getSubagentDepth(settings, parentId) {
1925
+ let depth = 0;
1926
+ let currentParentId = parentId;
1927
+ while (currentParentId && settings.subagents?.[currentParentId]) {
1928
+ depth++;
1929
+ currentParentId = settings.subagents[currentParentId]?.parentId;
1930
+ }
1931
+ return depth;
1932
+ }
1933
+ async function executeSubagent(chatId, subagentId, agentId, sessionId, prompt, isAsync, parentTokenPayload, workspaceRoot) {
1934
+ try {
1935
+ const settings = await readChatSettings(chatId) || {};
1936
+ const resolvedRouters = resolveRouters(settings.routers ?? [], false);
1937
+ let routerState = {
1938
+ messageId: randomUUID(),
1939
+ message: prompt,
1940
+ chatId,
1941
+ agentId,
1942
+ sessionId,
1943
+ env: {}
1944
+ };
1945
+ const initialState = { ...routerState };
1946
+ routerState = await executeRouterPipeline(routerState, resolvedRouters);
1947
+ await applyRouterStateUpdates(chatId, workspaceRoot, routerState, settings, initialState.agentId);
1948
+ await executeDirectMessage(chatId, routerState, void 0, workspaceRoot, false, void 0, subagentId);
1949
+ if (taskScheduler.hasTasks(sessionId)) return;
1950
+ await updateChatSettings(chatId, (finalSettings) => {
1951
+ if (finalSettings.subagents?.[subagentId]) finalSettings.subagents[subagentId].status = "completed";
1952
+ return finalSettings;
1953
+ });
1954
+ const logger = createChatLogger(chatId, subagentId);
1955
+ await logger.logSubagentStatus({
1956
+ subagentId,
1957
+ status: "completed"
1958
+ });
1959
+ if (isAsync) {
1960
+ const lastLogMessage = await logger.findLastMessage((m) => m.role === "agent" || m.displayRole === "agent");
1961
+ let outputContent = "";
1962
+ if (lastLogMessage && "content" in lastLogMessage) outputContent = `\n\n<subagent_output>\n${lastLogMessage.content}\n</subagent_output>`;
1963
+ console.log("Notifying parent", chatId, parentTokenPayload?.agentId, parentTokenPayload?.subagentId);
1964
+ await executeDirectMessage(chatId, {
1965
+ messageId: randomUUID(),
1966
+ message: `<notification>Subagent ${subagentId} completed.</notification>${outputContent}`,
1967
+ chatId,
1968
+ agentId: parentTokenPayload?.agentId || "default",
1969
+ ...parentTokenPayload?.subagentId ? { subagentId: parentTokenPayload.subagentId } : {},
1970
+ sessionId: parentTokenPayload?.sessionId || "default",
1971
+ env: {}
1972
+ }, void 0, workspaceRoot, true, void 0, parentTokenPayload?.subagentId, "subagent_update");
1973
+ }
1974
+ } catch {
1975
+ await updateChatSettings(chatId, (errSettings) => {
1976
+ if (errSettings.subagents?.[subagentId]) errSettings.subagents[subagentId].status = "failed";
1977
+ return errSettings;
1978
+ });
1979
+ await createChatLogger(chatId, subagentId).logSubagentStatus({
1980
+ subagentId,
1981
+ status: "failed"
1982
+ });
1983
+ }
1984
+ }
1985
+
1986
+ //#endregion
1987
+ //#region src/daemon/api/subagent-router.ts
1988
+ const MAX_SUBAGENT_DEPTH = 2;
1989
+ const subagentSpawn = apiProcedure.input(z.object({
1990
+ subagentId: z.string().optional(),
1991
+ targetAgentId: z.string().optional(),
1992
+ prompt: z.string(),
1993
+ async: z.boolean().optional()
1994
+ })).mutation(async ({ input, ctx }) => {
1995
+ if (!ctx.tokenPayload) throw new TRPCError({
1996
+ code: "UNAUTHORIZED",
1997
+ message: "Missing token"
1998
+ });
1999
+ const chatId = ctx.tokenPayload.chatId;
2000
+ const parentAgentId = ctx.tokenPayload.agentId;
2001
+ const parentId = ctx.tokenPayload.subagentId;
2002
+ const id = input.subagentId || randomUUID();
2003
+ const sessionId = randomUUID();
2004
+ const agentId = input.targetAgentId || parentAgentId;
2005
+ let depth = 0;
2006
+ await updateChatSettings(chatId, (settings) => {
2007
+ settings.subagents = settings.subagents || {};
2008
+ depth = getSubagentDepth(settings, parentId);
2009
+ if (depth >= MAX_SUBAGENT_DEPTH) throw new TRPCError({
2010
+ code: "BAD_REQUEST",
2011
+ message: "Max subagent depth reached"
2012
+ });
2013
+ if (settings.subagents[id]) throw new TRPCError({
2014
+ code: "BAD_REQUEST",
2015
+ message: "Subagent ID already exists"
2016
+ });
2017
+ settings.subagents[id] = {
2018
+ id,
2019
+ agentId,
2020
+ sessionId,
2021
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2022
+ status: "active",
2023
+ parentId
2024
+ };
2025
+ return settings;
2026
+ });
2027
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
2028
+ const isAsync = input.async ?? depth === 0;
2029
+ executeSubagent(chatId, id, agentId, sessionId, input.prompt, isAsync, ctx.tokenPayload, workspaceRoot);
2030
+ return {
2031
+ id,
2032
+ depth,
2033
+ isAsync
2034
+ };
2035
+ });
2036
+ const subagentSend = apiProcedure.input(z.object({
2037
+ subagentId: z.string(),
2038
+ prompt: z.string(),
2039
+ async: z.boolean().optional()
2040
+ })).mutation(async ({ input, ctx }) => {
2041
+ if (!ctx.tokenPayload) throw new TRPCError({
2042
+ code: "UNAUTHORIZED",
2043
+ message: "Missing token"
2044
+ });
2045
+ const chatId = ctx.tokenPayload.chatId;
2046
+ let sub;
2047
+ await updateChatSettings(chatId, (settings) => {
2048
+ if (!settings.subagents?.[input.subagentId]) throw new TRPCError({
2049
+ code: "NOT_FOUND",
2050
+ message: "Subagent not found"
2051
+ });
2052
+ sub = settings.subagents[input.subagentId];
2053
+ sub.status = "active";
2054
+ return settings;
2055
+ });
2056
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
2057
+ executeSubagent(chatId, sub.id, sub.agentId || "default", sub.sessionId || "default", input.prompt, input.async, ctx.tokenPayload, workspaceRoot);
2058
+ return { success: true };
2059
+ });
2060
+ async function checkSubagentStatus(chatId, subagentId) {
2061
+ const sub = (await readChatSettings(chatId))?.subagents?.[subagentId];
2062
+ if (!sub) throw new TRPCError({
2063
+ code: "NOT_FOUND",
2064
+ message: "Subagent not found"
2065
+ });
2066
+ if (sub.status === "completed" || sub.status === "failed") {
2067
+ let outputContent;
2068
+ if (sub.status === "completed") {
2069
+ const lastLogMessage = await createChatLogger(chatId, subagentId).findLastMessage((m) => m.role === "agent");
2070
+ if (lastLogMessage && "content" in lastLogMessage) outputContent = lastLogMessage.content;
2071
+ }
2072
+ return {
2073
+ status: sub.status,
2074
+ output: outputContent
2075
+ };
2076
+ }
2077
+ return null;
2078
+ }
2079
+ const subagentWait = apiProcedure.input(z.object({ subagentId: z.string() })).mutation(async ({ input, ctx, signal }) => {
2080
+ if (!ctx.tokenPayload) throw new TRPCError({
2081
+ code: "UNAUTHORIZED",
2082
+ message: "Missing token"
2083
+ });
2084
+ const chatId = ctx.tokenPayload.chatId;
2085
+ const ac = new AbortController();
2086
+ const timeout = setTimeout(() => ac.abort(), 6e4);
2087
+ const onAbort = () => {
2088
+ clearTimeout(timeout);
2089
+ ac.abort();
2090
+ };
2091
+ if (signal) signal.addEventListener("abort", onAbort);
2092
+ const eventIterator = on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal: ac.signal });
2093
+ try {
2094
+ const initialStatus = await checkSubagentStatus(chatId, input.subagentId);
2095
+ if (initialStatus) {
2096
+ clearTimeout(timeout);
2097
+ if (signal) signal.removeEventListener("abort", onAbort);
2098
+ return initialStatus;
2099
+ }
2100
+ for await (const [event] of eventIterator) if (event.chatId === chatId && event.message?.subagentId === input.subagentId) {
2101
+ if (event.message.role === "subagent_status") {
2102
+ const status = await checkSubagentStatus(chatId, input.subagentId);
2103
+ if (status) {
2104
+ clearTimeout(timeout);
2105
+ if (signal) signal.removeEventListener("abort", onAbort);
2106
+ return status;
2107
+ }
2108
+ }
2109
+ }
2110
+ } catch (err) {
2111
+ if (err && typeof err === "object" && "name" in err && err.name === "AbortError") return {
2112
+ status: "active",
2113
+ output: void 0
2114
+ };
2115
+ throw err;
2116
+ } finally {
2117
+ clearTimeout(timeout);
2118
+ if (signal) signal.removeEventListener("abort", onAbort);
2119
+ ac.abort();
2120
+ }
2121
+ return {
2122
+ status: "active",
2123
+ output: void 0
2124
+ };
2125
+ });
2126
+ const subagentStop = apiProcedure.input(z.object({ subagentId: z.string() })).mutation(async ({ input, ctx }) => {
2127
+ if (!ctx.tokenPayload) throw new TRPCError({
2128
+ code: "UNAUTHORIZED",
2129
+ message: "Missing token"
2130
+ });
2131
+ const chatId = ctx.tokenPayload.chatId;
2132
+ let subToStop;
2133
+ await updateChatSettings(chatId, (settings) => {
2134
+ if (settings.subagents) {
2135
+ const sub = settings.subagents[input.subagentId];
2136
+ if (sub) {
2137
+ sub.status = "failed";
2138
+ subToStop = sub;
2139
+ }
2140
+ }
2141
+ return settings;
2142
+ });
2143
+ if (subToStop) (await createAgentSession({
2144
+ chatId,
2145
+ agentId: subToStop.agentId || "default",
2146
+ sessionId: subToStop.sessionId || "default",
2147
+ subagentId: input.subagentId,
2148
+ cwd: process.cwd()
2149
+ })).stop();
2150
+ return { success: true };
2151
+ });
2152
+ const subagentDelete = apiProcedure.input(z.object({ subagentId: z.string() })).mutation(async ({ input, ctx }) => {
2153
+ if (!ctx.tokenPayload) throw new TRPCError({
2154
+ code: "UNAUTHORIZED",
2155
+ message: "Missing token"
2156
+ });
2157
+ const chatId = ctx.tokenPayload.chatId;
2158
+ let subToDelete;
2159
+ await updateChatSettings(chatId, (settings) => {
2160
+ if (settings.subagents && settings.subagents[input.subagentId]) {
2161
+ subToDelete = settings.subagents[input.subagentId];
2162
+ delete settings.subagents[input.subagentId];
2163
+ }
2164
+ return settings;
2165
+ });
2166
+ if (subToDelete) {
2167
+ (await createAgentSession({
2168
+ chatId,
2169
+ agentId: subToDelete.agentId || "default",
2170
+ sessionId: subToDelete.sessionId || "default",
2171
+ subagentId: input.subagentId,
2172
+ cwd: process.cwd()
2173
+ })).stop();
2174
+ return {
2175
+ success: true,
2176
+ deleted: true
2177
+ };
2178
+ }
2179
+ return {
2180
+ success: true,
2181
+ deleted: false
2182
+ };
2183
+ });
2184
+ const subagentList = apiProcedure.input(z.object({ blocking: z.boolean().optional() }).optional()).query(async ({ input, ctx }) => {
2185
+ if (!ctx.tokenPayload) throw new TRPCError({
2186
+ code: "UNAUTHORIZED",
2187
+ message: "Missing token"
2188
+ });
2189
+ const chatId = ctx.tokenPayload.chatId;
2190
+ const settings = await readChatSettings(chatId);
2191
+ let subagents = Object.values(settings?.subagents || {});
2192
+ const isSubagent = !!ctx.tokenPayload.subagentId;
2193
+ const myId = ctx.tokenPayload.subagentId;
2194
+ subagents = subagents.filter((s) => s.parentId === myId);
2195
+ if (input?.blocking) if (!isSubagent) subagents = [];
2196
+ else subagents = subagents.filter((s) => s.status === "active");
2197
+ return { subagents };
2198
+ });
2199
+ const subagentTail = apiProcedure.input(z.object({
2200
+ subagentId: z.string(),
2201
+ limit: z.number().optional()
2202
+ })).query(async ({ input, ctx }) => {
2203
+ if (!ctx.tokenPayload) throw new TRPCError({
2204
+ code: "UNAUTHORIZED",
2205
+ message: "Missing token"
2206
+ });
2207
+ const chatId = ctx.tokenPayload.chatId;
2208
+ return { messages: await createChatLogger(chatId, input.subagentId).getMessages(input.limit) };
2209
+ });
2210
+
1415
2211
  //#endregion
1416
2212
  //#region src/daemon/api/agent-router.ts
1417
2213
  const logMessage = apiProcedure.input(z.object({
@@ -1438,18 +2234,81 @@ const logMessage = apiProcedure.input(z.object({
1438
2234
  await appendMessage(chatId, {
1439
2235
  id,
1440
2236
  messageId: id,
1441
- role: "log",
1442
- source: "router",
2237
+ role: "command",
1443
2238
  content: input.message || "",
2239
+ stdout: "",
1444
2240
  stderr: "",
1445
2241
  timestamp,
1446
2242
  command: `clawmini-lite log${filesArgStr}`,
1447
2243
  cwd: process.cwd(),
1448
2244
  exitCode: 0,
2245
+ ...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
2246
+ ...filePaths.length > 0 ? { files: filePaths } : {}
2247
+ });
2248
+ return { success: true };
2249
+ });
2250
+ const logReplyMessage = apiProcedure.input(z.object({
2251
+ message: z.string(),
2252
+ files: z.array(z.string()).optional()
2253
+ })).mutation(async ({ input, ctx }) => {
2254
+ if (!ctx.tokenPayload) throw new TRPCError({
2255
+ code: "UNAUTHORIZED",
2256
+ message: "Missing token"
2257
+ });
2258
+ const chatId = ctx.tokenPayload.chatId;
2259
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2260
+ const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
2261
+ const filePaths = [];
2262
+ if (input.files && input.files.length > 0) {
2263
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
2264
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
2265
+ for (const file of input.files) {
2266
+ const validPath = await validateLogFile(file, agentDir, workspaceRoot);
2267
+ filePaths.push(validPath);
2268
+ }
2269
+ }
2270
+ await appendMessage(chatId, {
2271
+ id,
2272
+ role: "agent",
2273
+ content: input.message,
2274
+ timestamp,
2275
+ ...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {},
1449
2276
  ...filePaths.length > 0 ? { files: filePaths } : {}
1450
2277
  });
1451
2278
  return { success: true };
1452
2279
  });
2280
+ const logToolMessage = apiProcedure.input(z.object({
2281
+ name: z.string(),
2282
+ payload: z.any().optional()
2283
+ })).mutation(async ({ input, ctx }) => {
2284
+ if (!ctx.tokenPayload) throw new TRPCError({
2285
+ code: "UNAUTHORIZED",
2286
+ message: "Missing token"
2287
+ });
2288
+ const chatId = ctx.tokenPayload.chatId;
2289
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2290
+ const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
2291
+ const messageId = randomUUID();
2292
+ const payloadObj = input.payload !== void 0 ? input.payload : {};
2293
+ let contentStr;
2294
+ if (typeof payloadObj === "string") contentStr = payloadObj;
2295
+ else try {
2296
+ contentStr = JSON.stringify(payloadObj, null, 2);
2297
+ } catch {
2298
+ contentStr = String(payloadObj);
2299
+ }
2300
+ await appendMessage(chatId, {
2301
+ id,
2302
+ messageId,
2303
+ role: "tool",
2304
+ name: input.name,
2305
+ payload: payloadObj,
2306
+ content: contentStr,
2307
+ timestamp,
2308
+ ...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}
2309
+ });
2310
+ return { success: true };
2311
+ });
1453
2312
  const agentListCronJobs = apiProcedure.query(async ({ ctx }) => {
1454
2313
  if (!ctx.tokenPayload) throw new TRPCError({
1455
2314
  code: "UNAUTHORIZED",
@@ -1510,34 +2369,68 @@ const createPolicyRequest = apiProcedure.input(z.object({
1510
2369
  });
1511
2370
  const workspaceRoot = getWorkspaceRoot(process.cwd());
1512
2371
  const snapshotDir = path.join(getClawminiDir(process.cwd()), "tmp", "snapshots");
1513
- const service = new PolicyRequestService(new RequestStore(process.cwd()), await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), snapshotDir);
2372
+ const store = new RequestStore(process.cwd());
2373
+ const service = new PolicyRequestService(store, await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot), snapshotDir);
1514
2374
  const chatId = ctx.tokenPayload.chatId;
1515
2375
  const agentId = ctx.tokenPayload.agentId;
1516
- const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId);
2376
+ const policy = (await readPolicies())?.policies?.[input.commandName];
2377
+ if (!policy) throw new TRPCError({
2378
+ code: "NOT_FOUND",
2379
+ message: `Policy not found: ${input.commandName}`
2380
+ });
2381
+ const isAutoApprove = !!policy.autoApprove;
2382
+ const request = await service.createRequest(input.commandName, input.args, input.fileMappings, chatId, agentId, isAutoApprove, ctx.tokenPayload.subagentId);
2383
+ if (isAutoApprove) {
2384
+ const { stdout, stderr, exitCode, commandStr } = await executeRequest(request, policy, getWorkspaceRoot());
2385
+ request.executionResult = {
2386
+ stdout,
2387
+ stderr,
2388
+ exitCode
2389
+ };
2390
+ await store.save(request);
2391
+ await appendMessage(chatId, {
2392
+ id: randomUUID(),
2393
+ messageId: randomUUID(),
2394
+ role: "policy",
2395
+ requestId: request.id,
2396
+ commandName: input.commandName,
2397
+ args: input.args,
2398
+ status: "approved",
2399
+ content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
2400
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2401
+ ...ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}
2402
+ });
2403
+ return request;
2404
+ }
1517
2405
  const previewContent = await generateRequestPreview(request);
1518
2406
  await appendMessage(chatId, {
1519
2407
  id: randomUUID(),
1520
2408
  messageId: randomUUID(),
1521
- role: "log",
1522
- source: "router",
2409
+ role: "policy",
2410
+ requestId: request.id,
2411
+ commandName: input.commandName,
2412
+ args: input.args,
2413
+ status: "pending",
1523
2414
  content: previewContent,
1524
- stderr: "",
1525
2415
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1526
- command: "policy-request",
1527
- cwd: process.cwd(),
1528
- exitCode: 0
2416
+ displayRole: "agent"
1529
2417
  });
1530
2418
  return request;
1531
2419
  });
1532
2420
  const fetchPendingMessages = apiProcedure.mutation(async ({ ctx }) => {
1533
- const queue = getMessageQueue(process.cwd());
2421
+ if (!ctx.tokenPayload?.agentId) throw new TRPCError({
2422
+ code: "UNAUTHORIZED",
2423
+ message: "Missing agent ID"
2424
+ });
1534
2425
  const targetSessionId = ctx.tokenPayload?.sessionId || "default";
1535
- const extracted = queue.extractPending((p) => p.sessionId === targetSessionId);
2426
+ const extracted = taskScheduler.extractPending(targetSessionId);
1536
2427
  if (extracted.length === 0) return { messages: "" };
1537
- return { messages: formatPendingMessages(extracted.map((p) => p.text)) };
2428
+ return { messages: formatPendingMessages(extracted) };
1538
2429
  });
1539
2430
  const agentRouter = router({
1540
2431
  logMessage,
2432
+ logReplyMessage,
2433
+ logToolMessage,
1541
2434
  listCronJobs: agentListCronJobs,
1542
2435
  addCronJob: agentAddCronJob,
1543
2436
  deleteCronJob: agentDeleteCronJob,
@@ -1545,7 +2438,14 @@ const agentRouter = router({
1545
2438
  executePolicyHelp,
1546
2439
  createPolicyRequest,
1547
2440
  fetchPendingMessages,
1548
- ping
2441
+ ping,
2442
+ subagentSpawn,
2443
+ subagentSend,
2444
+ subagentWait,
2445
+ subagentStop,
2446
+ subagentDelete,
2447
+ subagentList,
2448
+ subagentTail
1549
2449
  });
1550
2450
 
1551
2451
  //#endregion
@@ -1644,6 +2544,20 @@ async function initDaemon() {
1644
2544
  resolve();
1645
2545
  });
1646
2546
  });
2547
+ const cleanOrphanedSubagents = async () => {
2548
+ try {
2549
+ const chats = await listChats();
2550
+ for (const chatId of chats) await updateChatSettings(chatId, (settings) => {
2551
+ if (settings.subagents) {
2552
+ for (const subagent of Object.values(settings.subagents)) if (subagent.status === "active") subagent.status = "failed";
2553
+ }
2554
+ return settings;
2555
+ });
2556
+ } catch (err) {
2557
+ console.warn("Failed to clean orphaned subagents:", err);
2558
+ }
2559
+ };
2560
+ await cleanOrphanedSubagents();
1647
2561
  await runHooks("up");
1648
2562
  isReady = true;
1649
2563
  readyPromiseResolve();