clawmini 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.0arZe_Uf.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +0 -1
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -118
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.0arZe_Uf.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/D5iV40bG.js +0 -1
  328. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  329. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  331. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/D5iV40bG.js +0 -1
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import {
3
+ registerTurn,
4
+ incrementSubagent,
5
+ decrementSubagent,
6
+ markParentExited,
7
+ _resetTurnRegistryForTests,
8
+ _getTurnStateForTests,
9
+ } from './turn-registry.js';
10
+ import { daemonEvents, DAEMON_EVENT_TURN_ENDED, type TurnEndedEvent } from '../events.js';
11
+
12
+ describe('turn-registry', () => {
13
+ let ended: TurnEndedEvent[];
14
+ const capture = (e: TurnEndedEvent) => {
15
+ ended.push(e);
16
+ };
17
+
18
+ beforeEach(() => {
19
+ ended = [];
20
+ daemonEvents.on(DAEMON_EVENT_TURN_ENDED, capture);
21
+ _resetTurnRegistryForTests();
22
+ });
23
+
24
+ afterEach(() => {
25
+ daemonEvents.off(DAEMON_EVENT_TURN_ENDED, capture);
26
+ _resetTurnRegistryForTests();
27
+ });
28
+
29
+ it('fires turnEnded immediately when parent exits with no outstanding subagents', () => {
30
+ registerTurn('chat-1', 'turn-a');
31
+ markParentExited('turn-a', 'ok');
32
+ expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
33
+ expect(_getTurnStateForTests('turn-a')).toBeUndefined();
34
+ });
35
+
36
+ it('defers turnEnded until outstanding subagents drain', () => {
37
+ registerTurn('chat-1', 'turn-a');
38
+ incrementSubagent('turn-a');
39
+ incrementSubagent('turn-a');
40
+ markParentExited('turn-a', 'ok');
41
+ expect(ended).toEqual([]);
42
+ decrementSubagent('turn-a');
43
+ expect(ended).toEqual([]);
44
+ decrementSubagent('turn-a');
45
+ expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
46
+ });
47
+
48
+ it('preserves outcome=error when parent exited with an error', () => {
49
+ registerTurn('chat-1', 'turn-a');
50
+ incrementSubagent('turn-a');
51
+ markParentExited('turn-a', 'error');
52
+ decrementSubagent('turn-a');
53
+ expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'error' }]);
54
+ });
55
+
56
+ it('ignores decrements below zero and extra markParentExited calls', () => {
57
+ registerTurn('chat-1', 'turn-a');
58
+ decrementSubagent('turn-a'); // no-op
59
+ markParentExited('turn-a', 'ok');
60
+ markParentExited('turn-a', 'error'); // ignored
61
+ expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'ok' }]);
62
+ });
63
+
64
+ it('force-fires turnEnded with outcome=error when the timeout elapses', () => {
65
+ vi.useFakeTimers();
66
+ try {
67
+ registerTurn('chat-1', 'turn-a', 100);
68
+ incrementSubagent('turn-a');
69
+ markParentExited('turn-a', 'ok');
70
+ expect(ended).toEqual([]);
71
+ vi.advanceTimersByTime(150);
72
+ expect(ended).toEqual([{ chatId: 'chat-1', turnId: 'turn-a', outcome: 'error' }]);
73
+ // A later decrement after force-fire is a no-op (turn already removed).
74
+ decrementSubagent('turn-a');
75
+ expect(ended).toHaveLength(1);
76
+ } finally {
77
+ vi.useRealTimers();
78
+ }
79
+ });
80
+
81
+ it('is a no-op for unknown turnIds and undefined turnIds', () => {
82
+ incrementSubagent(undefined);
83
+ decrementSubagent(undefined);
84
+ incrementSubagent('never-registered');
85
+ decrementSubagent('never-registered');
86
+ markParentExited('never-registered', 'ok');
87
+ expect(ended).toEqual([]);
88
+ });
89
+ });
@@ -0,0 +1,94 @@
1
+ import { emitTurnEnded } from '../events.js';
2
+
3
+ interface TurnState {
4
+ chatId: string;
5
+ outstanding: number;
6
+ parentExited: boolean;
7
+ outcome: 'ok' | 'error';
8
+ timeoutHandle: NodeJS.Timeout;
9
+ }
10
+
11
+ const turns = new Map<string, TurnState>();
12
+
13
+ /**
14
+ * Watchdog for turns whose subagent count never drains. Pathological cases
15
+ * (crashed subagent, bug leaving the counter > 0) would otherwise pin the
16
+ * turn's activity log open indefinitely. Default is a guess — instrument
17
+ * before tuning.
18
+ */
19
+ export const DEFAULT_TURN_MAX_DURATION_MS = 30 * 60 * 1000;
20
+
21
+ export function registerTurn(
22
+ chatId: string,
23
+ turnId: string,
24
+ maxDurationMs: number = DEFAULT_TURN_MAX_DURATION_MS
25
+ ): void {
26
+ if (turns.has(turnId)) return;
27
+ const state: TurnState = {
28
+ chatId,
29
+ outstanding: 0,
30
+ parentExited: false,
31
+ outcome: 'ok',
32
+ timeoutHandle: setTimeout(() => {
33
+ const s = turns.get(turnId);
34
+ if (!s) return;
35
+ console.warn(
36
+ `Turn ${turnId} force-ended after ${maxDurationMs}ms (outstanding=${s.outstanding}).`
37
+ );
38
+ turns.delete(turnId);
39
+ emitTurnEnded({ chatId: s.chatId, turnId, outcome: 'error' });
40
+ }, maxDurationMs),
41
+ };
42
+ state.timeoutHandle.unref();
43
+ turns.set(turnId, state);
44
+ }
45
+
46
+ export function incrementSubagent(turnId: string | undefined): void {
47
+ if (!turnId) return;
48
+ const state = turns.get(turnId);
49
+ if (!state) return;
50
+ state.outstanding++;
51
+ }
52
+
53
+ export function decrementSubagent(turnId: string | undefined): void {
54
+ if (!turnId) return;
55
+ const state = turns.get(turnId);
56
+ if (!state) return;
57
+ state.outstanding = Math.max(0, state.outstanding - 1);
58
+ maybeFinalize(turnId, state);
59
+ }
60
+
61
+ /**
62
+ * Called once, when the parent agent's initial `handleMessage` promise
63
+ * settles. Records the outcome; the turn actually ends (emits `turnEnded`)
64
+ * only once the outstanding subagent count also reaches zero.
65
+ */
66
+ export function markParentExited(turnId: string, outcome: 'ok' | 'error'): void {
67
+ const state = turns.get(turnId);
68
+ if (!state) return;
69
+ if (state.parentExited) return;
70
+ state.parentExited = true;
71
+ state.outcome = outcome;
72
+ maybeFinalize(turnId, state);
73
+ }
74
+
75
+ function maybeFinalize(turnId: string, state: TurnState): void {
76
+ if (!state.parentExited) return;
77
+ if (state.outstanding > 0) return;
78
+ clearTimeout(state.timeoutHandle);
79
+ turns.delete(turnId);
80
+ emitTurnEnded({ chatId: state.chatId, turnId, outcome: state.outcome });
81
+ }
82
+
83
+ /** Test hook: drop all state without emitting events. */
84
+ export function _resetTurnRegistryForTests(): void {
85
+ for (const state of turns.values()) {
86
+ clearTimeout(state.timeoutHandle);
87
+ }
88
+ turns.clear();
89
+ }
90
+
91
+ /** Test hook: inspect registry state. */
92
+ export function _getTurnStateForTests(turnId: string): Readonly<TurnState> | undefined {
93
+ return turns.get(turnId);
94
+ }
@@ -30,6 +30,7 @@ export interface Logger {
30
30
  event: SystemMessage['event'];
31
31
  messageId?: string;
32
32
  displayRole?: 'user' | 'agent';
33
+ jobId?: string;
33
34
  }): Promise<SystemMessage>;
34
35
  logSubagentStatus(options: {
35
36
  subagentId: string;
@@ -56,6 +57,7 @@ export interface Message {
56
57
  id: string;
57
58
  content: string;
58
59
  env: Record<string, string>;
60
+ turnId?: string;
59
61
  }
60
62
 
61
63
  export interface ExecutionResponse {
@@ -0,0 +1,263 @@
1
+ import { z } from 'zod';
2
+ import { TRPCError } from '@trpc/server';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { apiProcedure } from './trpc.js';
7
+ import { getWorkspaceRoot, readPoliciesForPath, getClawminiDir } from '../../shared/workspace.js';
8
+ import { pathIsInsideDir } from '../../shared/utils/fs.js';
9
+ import { resolveAgentDir } from './router-utils.js';
10
+ import { PolicyRequestService } from '../policy-request-service.js';
11
+ import { RequestStore } from '../request-store.js';
12
+ import {
13
+ executeSafe,
14
+ generateRequestPreview,
15
+ executeRequest,
16
+ resolveRequestCwd,
17
+ truncateLargeOutput,
18
+ } from '../policy-utils.js';
19
+ import { appendMessage, type PolicyRequestMessage } from '../chats.js';
20
+
21
+ const MAX_POLICY_SCRIPT_BYTES = 1 * 1024 * 1024;
22
+ // Above this, the script is copied into the agent's tmp/ instead of being
23
+ // inlined in the response, so `requests show` does not flood the agent's
24
+ // context with a long script body. Mirrors truncateLargeOutput in policy-utils.
25
+ const MAX_INLINE_SCRIPT_LENGTH = 4000;
26
+
27
+ export const listPolicies = apiProcedure.query(async ({ ctx }) => {
28
+ const workspaceRoot = getWorkspaceRoot();
29
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
30
+ const config = await readPoliciesForPath(agentDir, workspaceRoot);
31
+ return { policies: config?.policies || {} };
32
+ });
33
+
34
+ // Returns the contents of a policy's script file. Restricted to scripts inside
35
+ // `.clawmini/policy-scripts/` so an arbitrary `command` path (e.g. `/etc/passwd`
36
+ // or a built-in node binary) cannot be exfiltrated through this endpoint.
37
+ export const readPolicyScript = apiProcedure
38
+ .input(z.object({ commandName: z.string() }))
39
+ .query(async ({ input, ctx }) => {
40
+ const workspaceRoot = getWorkspaceRoot();
41
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
42
+ const config = await readPoliciesForPath(agentDir, workspaceRoot);
43
+ const policy = config?.policies?.[input.commandName];
44
+
45
+ if (!policy) {
46
+ throw new TRPCError({
47
+ code: 'NOT_FOUND',
48
+ message: `Policy not found: ${input.commandName}`,
49
+ });
50
+ }
51
+
52
+ const scriptsDir = path.join(getClawminiDir(), 'policy-scripts');
53
+ const resolvedCommand = path.resolve(policy.command);
54
+
55
+ if (!pathIsInsideDir(resolvedCommand, scriptsDir, { allowSameDir: false })) {
56
+ throw new TRPCError({
57
+ code: 'BAD_REQUEST',
58
+ message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
59
+ });
60
+ }
61
+
62
+ // realpath resolves symlinks in both paths; without this, a symlink inside
63
+ // policy-scripts/ pointing at /etc/passwd would pass the string-prefix
64
+ // check above, then fs.stat/readFile would dereference and exfiltrate.
65
+ let realCommand: string;
66
+ let realScriptsDir: string;
67
+ try {
68
+ realCommand = await fs.realpath(resolvedCommand);
69
+ } catch (err) {
70
+ throw new TRPCError({
71
+ code: 'NOT_FOUND',
72
+ message: `Script file not found for policy '${input.commandName}': ${
73
+ err instanceof Error ? err.message : String(err)
74
+ }`,
75
+ });
76
+ }
77
+ try {
78
+ realScriptsDir = await fs.realpath(scriptsDir);
79
+ } catch {
80
+ throw new TRPCError({
81
+ code: 'BAD_REQUEST',
82
+ message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
83
+ });
84
+ }
85
+ if (!pathIsInsideDir(realCommand, realScriptsDir, { allowSameDir: false })) {
86
+ throw new TRPCError({
87
+ code: 'BAD_REQUEST',
88
+ message: `Policy '${input.commandName}' does not point at a script in policy-scripts/.`,
89
+ });
90
+ }
91
+
92
+ let stat;
93
+ try {
94
+ stat = await fs.stat(realCommand);
95
+ } catch (err) {
96
+ throw new TRPCError({
97
+ code: 'NOT_FOUND',
98
+ message: `Script file not found for policy '${input.commandName}': ${
99
+ err instanceof Error ? err.message : String(err)
100
+ }`,
101
+ });
102
+ }
103
+
104
+ if (!stat.isFile()) {
105
+ throw new TRPCError({
106
+ code: 'BAD_REQUEST',
107
+ message: `Script path for policy '${input.commandName}' is not a regular file.`,
108
+ });
109
+ }
110
+
111
+ if (stat.size > MAX_POLICY_SCRIPT_BYTES) {
112
+ throw new TRPCError({
113
+ code: 'BAD_REQUEST',
114
+ message: `Script file exceeds the ${MAX_POLICY_SCRIPT_BYTES}-byte limit.`,
115
+ });
116
+ }
117
+
118
+ if (stat.size > MAX_INLINE_SCRIPT_LENGTH) {
119
+ const tmpDir = path.join(agentDir, 'tmp');
120
+ await fs.mkdir(tmpDir, { recursive: true });
121
+ const ext = path.extname(realCommand);
122
+ const safeName = input.commandName.replace(/[^a-zA-Z0-9._-]/g, '_');
123
+ const destPath = path.join(tmpDir, `policy-script-${safeName}${ext}`);
124
+ await fs.copyFile(realCommand, destPath);
125
+ return {
126
+ path: realCommand,
127
+ size: stat.size,
128
+ spilledTo: `./tmp/policy-script-${safeName}${ext}`,
129
+ };
130
+ }
131
+
132
+ const content = await fs.readFile(realCommand, 'utf8');
133
+ return { path: realCommand, size: stat.size, content };
134
+ });
135
+
136
+ export const executePolicyHelp = apiProcedure
137
+ .input(z.object({ commandName: z.string() }))
138
+ .query(async ({ input, ctx }) => {
139
+ const workspaceRoot = getWorkspaceRoot();
140
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
141
+ const config = await readPoliciesForPath(agentDir, workspaceRoot);
142
+ const policy = config?.policies?.[input.commandName];
143
+
144
+ if (!policy) {
145
+ throw new TRPCError({
146
+ code: 'NOT_FOUND',
147
+ message: `Policy not found: ${input.commandName}`,
148
+ });
149
+ }
150
+
151
+ if (!policy.allowHelp) {
152
+ return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
153
+ }
154
+
155
+ const fullArgs = [...(policy.args || []), '--help'];
156
+ const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
157
+ cwd: getWorkspaceRoot(),
158
+ });
159
+
160
+ return { stdout, stderr, exitCode };
161
+ });
162
+
163
+ export const createPolicyRequest = apiProcedure
164
+ .input(
165
+ z.object({
166
+ commandName: z.string(),
167
+ args: z.array(z.string()),
168
+ fileMappings: z.record(z.string(), z.string()),
169
+ // Path traversal is guarded by assertPathInsideDir in resolveRequestCwd
170
+ // (policy-utils.ts), which realpath-resolves the cwd before comparing —
171
+ // so it covers encoded separators, symlinks, etc.
172
+ cwd: z.string().optional(),
173
+ })
174
+ )
175
+ .mutation(async ({ input, ctx }) => {
176
+ if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
177
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
178
+ const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
179
+ const store = new RequestStore(process.cwd());
180
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
181
+ const service = new PolicyRequestService(store, agentDir, snapshotDir);
182
+
183
+ const chatId = ctx.tokenPayload.chatId;
184
+ const agentId = ctx.tokenPayload.agentId;
185
+
186
+ const config = await readPoliciesForPath(agentDir, workspaceRoot);
187
+ const policy = config?.policies?.[input.commandName];
188
+
189
+ if (!policy) {
190
+ throw new TRPCError({
191
+ code: 'NOT_FOUND',
192
+ message: `Policy not found: ${input.commandName}`,
193
+ });
194
+ }
195
+
196
+ const isAutoApprove = !!policy.autoApprove;
197
+
198
+ const request = await service.createRequest(
199
+ input.commandName,
200
+ input.args,
201
+ input.fileMappings,
202
+ chatId,
203
+ agentId,
204
+ isAutoApprove,
205
+ ctx.tokenPayload.subagentId,
206
+ input.cwd
207
+ );
208
+
209
+ if (isAutoApprove) {
210
+ const hostCwd = await resolveRequestCwd(request.cwd, agentId, workspaceRoot);
211
+
212
+ const result = await executeRequest(request, policy, hostCwd);
213
+ const { exitCode, commandStr } = result;
214
+ const { stdout, stderr } = await truncateLargeOutput(
215
+ result.stdout,
216
+ result.stderr,
217
+ request.id,
218
+ agentId
219
+ );
220
+
221
+ request.executionResult = { stdout, stderr, exitCode };
222
+
223
+ const logMsg: PolicyRequestMessage = {
224
+ id: randomUUID(),
225
+ // TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
226
+ messageId: randomUUID(),
227
+ role: 'policy',
228
+ requestId: request.id,
229
+ commandName: input.commandName,
230
+ args: input.args,
231
+ status: 'approved',
232
+ content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
233
+ timestamp: new Date().toISOString(),
234
+ sessionId: ctx.tokenPayload.sessionId,
235
+ ...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
236
+ ...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
237
+ };
238
+
239
+ await appendMessage(chatId, logMsg);
240
+ return request;
241
+ }
242
+
243
+ const previewContent = await generateRequestPreview(request);
244
+
245
+ const logMsg: PolicyRequestMessage = {
246
+ id: randomUUID(),
247
+ // TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
248
+ messageId: randomUUID(),
249
+ role: 'policy',
250
+ requestId: request.id,
251
+ commandName: input.commandName,
252
+ args: input.args,
253
+ status: 'pending',
254
+ content: previewContent,
255
+ timestamp: new Date().toISOString(),
256
+ displayRole: 'agent',
257
+ sessionId: ctx.tokenPayload.sessionId,
258
+ ...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
259
+ };
260
+
261
+ await appendMessage(chatId, logMsg);
262
+ return request;
263
+ });
@@ -1,19 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { randomUUID } from 'node:crypto';
3
- import path from 'node:path';
4
3
  import { TRPCError } from '@trpc/server';
5
4
  import {
6
5
  appendMessage,
7
6
  type CommandLogMessage,
8
7
  type AgentReplyMessage,
9
8
  type ToolMessage,
10
- type PolicyRequestMessage,
11
9
  } from '../chats.js';
12
- import { executeSafe, generateRequestPreview, executeRequest } from '../policy-utils.js';
13
- import { getWorkspaceRoot, readPolicies, getClawminiDir } from '../../shared/workspace.js';
14
- import { PolicyRequestService } from '../policy-request-service.js';
15
- import { RequestStore } from '../request-store.js';
16
- import { CronJobSchema } from '../../shared/config.js';
10
+ import { getWorkspaceRoot } from '../../shared/workspace.js';
11
+ import type { CronJob } from '../../shared/config.js';
17
12
  import { apiProcedure, router } from './trpc.js';
18
13
  import { taskScheduler } from '../agent/task-scheduler.js';
19
14
  import { formatPendingMessages } from '../agent/utils.js';
@@ -62,7 +57,9 @@ export const logMessage = apiProcedure
62
57
  command: `clawmini-lite log${filesArgStr}`,
63
58
  cwd: process.cwd(),
64
59
  exitCode: 0,
60
+ sessionId: ctx.tokenPayload.sessionId,
65
61
  ...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
62
+ ...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
66
63
  ...(filePaths.length > 0 ? { files: filePaths } : {}),
67
64
  };
68
65
 
@@ -99,7 +96,9 @@ export const logReplyMessage = apiProcedure
99
96
  role: 'agent',
100
97
  content: input.message,
101
98
  timestamp,
99
+ sessionId: ctx.tokenPayload.sessionId,
102
100
  ...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
101
+ ...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
103
102
  ...(filePaths.length > 0 ? { files: filePaths } : {}),
104
103
  };
105
104
 
@@ -141,7 +140,9 @@ export const logToolMessage = apiProcedure
141
140
  payload: payloadObj,
142
141
  content: contentStr,
143
142
  timestamp,
143
+ sessionId: ctx.tokenPayload.sessionId,
144
144
  ...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
145
+ ...(ctx.tokenPayload.turnId ? { turnId: ctx.tokenPayload.turnId } : {}),
145
146
  };
146
147
 
147
148
  await appendMessage(chatId, logMsg);
@@ -154,12 +155,42 @@ export const agentListCronJobs = apiProcedure.query(async ({ ctx }) => {
154
155
  return listCronJobsShared(chatId);
155
156
  });
156
157
 
158
+ // Agents may only set a restricted subset of CronJob fields. The remaining
159
+ // fields (agentId, createdAt, env, nextSessionId, action, jobs) are reserved
160
+ // for internal use and filled in by the server.
161
+ export const AgentCronJobInputSchema = z.strictObject({
162
+ id: z.string().min(1),
163
+ message: z.string().default(''),
164
+ reply: z.string().optional(),
165
+ session: z
166
+ .union([
167
+ z.strictObject({ type: z.literal('new') }),
168
+ z.strictObject({ type: z.literal('existing'), id: z.string() }),
169
+ ])
170
+ .optional(),
171
+ schedule: z.union([
172
+ z.strictObject({ cron: z.string() }),
173
+ z.strictObject({ every: z.string() }),
174
+ z.strictObject({ at: z.string() }),
175
+ ]),
176
+ });
177
+
178
+ export type AgentCronJobInput = z.infer<typeof AgentCronJobInputSchema>;
179
+
157
180
  export const agentAddCronJob = apiProcedure
158
- .input(z.object({ job: CronJobSchema }))
181
+ .input(z.object({ job: AgentCronJobInputSchema }))
159
182
  .mutation(async ({ input, ctx }) => {
160
183
  if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
161
184
  const chatId = ctx.tokenPayload.chatId;
162
- const job = { ...input.job, agentId: ctx.tokenPayload.agentId };
185
+ const job: CronJob = {
186
+ id: input.job.id,
187
+ message: input.job.message,
188
+ schedule: input.job.schedule,
189
+ createdAt: new Date().toISOString(),
190
+ agentId: ctx.tokenPayload.agentId,
191
+ ...(input.job.reply !== undefined ? { reply: input.job.reply } : {}),
192
+ ...(input.job.session !== undefined ? { session: input.job.session } : {}),
193
+ };
163
194
  return addCronJobShared(chatId, job);
164
195
  });
165
196
 
@@ -171,123 +202,12 @@ export const agentDeleteCronJob = apiProcedure
171
202
  return deleteCronJobShared(chatId, input.id);
172
203
  });
173
204
 
174
- export const listPolicies = apiProcedure.query(async () => {
175
- return await readPolicies();
176
- });
177
-
178
- export const executePolicyHelp = apiProcedure
179
- .input(z.object({ commandName: z.string() }))
180
- .query(async ({ input }) => {
181
- const config = await readPolicies();
182
- const policy = config?.policies?.[input.commandName];
183
-
184
- if (!policy) {
185
- throw new TRPCError({
186
- code: 'NOT_FOUND',
187
- message: `Policy not found: ${input.commandName}`,
188
- });
189
- }
190
-
191
- if (!policy.allowHelp) {
192
- return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
193
- }
194
-
195
- const fullArgs = [...(policy.args || []), '--help'];
196
- const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
197
- cwd: getWorkspaceRoot(),
198
- });
199
-
200
- return { stdout, stderr, exitCode };
201
- });
202
-
203
- export const createPolicyRequest = apiProcedure
204
- .input(
205
- z.object({
206
- commandName: z.string(),
207
- args: z.array(z.string()),
208
- fileMappings: z.record(z.string(), z.string()),
209
- })
210
- )
211
- .mutation(async ({ input, ctx }) => {
212
- if (!ctx.tokenPayload) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing token' });
213
- const workspaceRoot = getWorkspaceRoot(process.cwd());
214
- const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
215
- const store = new RequestStore(process.cwd());
216
- const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
217
- const service = new PolicyRequestService(store, agentDir, snapshotDir);
218
-
219
- const chatId = ctx.tokenPayload.chatId;
220
- const agentId = ctx.tokenPayload.agentId;
221
-
222
- const config = await readPolicies();
223
- const policy = config?.policies?.[input.commandName];
224
-
225
- if (!policy) {
226
- throw new TRPCError({
227
- code: 'NOT_FOUND',
228
- message: `Policy not found: ${input.commandName}`,
229
- });
230
- }
231
-
232
- const isAutoApprove = !!policy.autoApprove;
233
-
234
- const request = await service.createRequest(
235
- input.commandName,
236
- input.args,
237
- input.fileMappings,
238
- chatId,
239
- agentId,
240
- isAutoApprove,
241
- ctx.tokenPayload.subagentId
242
- );
243
-
244
- if (isAutoApprove) {
245
- const { stdout, stderr, exitCode, commandStr } = await executeRequest(
246
- request,
247
- policy,
248
- getWorkspaceRoot()
249
- );
250
-
251
- request.executionResult = { stdout, stderr, exitCode };
252
- await store.save(request);
253
-
254
- const logMsg: PolicyRequestMessage = {
255
- id: randomUUID(),
256
- // TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
257
- messageId: randomUUID(),
258
- role: 'policy',
259
- requestId: request.id,
260
- commandName: input.commandName,
261
- args: input.args,
262
- status: 'approved',
263
- content: `[Auto-approved] Policy ${input.commandName} was executed.\n\nCommand: ${commandStr}\nExit Code: ${exitCode}\n\nStdout:\n${stdout}\n\nStderr:\n${stderr}`,
264
- timestamp: new Date().toISOString(),
265
- ...(ctx.tokenPayload.subagentId ? { subagentId: ctx.tokenPayload.subagentId } : {}),
266
- };
267
-
268
- await appendMessage(chatId, logMsg);
269
- return request;
270
- }
271
-
272
- const previewContent = await generateRequestPreview(request);
273
-
274
- const logMsg: PolicyRequestMessage = {
275
- id: randomUUID(),
276
- // TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
277
- messageId: randomUUID(),
278
- role: 'policy',
279
- requestId: request.id,
280
- commandName: input.commandName,
281
- args: input.args,
282
- status: 'pending',
283
- content: previewContent,
284
- timestamp: new Date().toISOString(),
285
- displayRole: 'agent',
286
- };
287
-
288
- await appendMessage(chatId, logMsg);
289
- return request;
290
- });
205
+ import {
206
+ listPolicies,
207
+ executePolicyHelp,
208
+ createPolicyRequest,
209
+ readPolicyScript,
210
+ } from './agent-policy-endpoints.js';
291
211
 
292
212
  import { ping } from './user-router.js';
293
213
 
@@ -325,6 +245,7 @@ export const agentRouter = router({
325
245
  listPolicies,
326
246
  executePolicyHelp,
327
247
  createPolicyRequest,
248
+ readPolicyScript,
328
249
  fetchPendingMessages,
329
250
  ping,
330
251
  subagentSpawn,