clawmini 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.Dr0ot9sV.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +42 -13
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -176
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.Dr0ot9sV.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  328. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  329. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  331. package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-oWmVh5mi.mjs","names":[],"sources":["../src/shared/policies.ts","../src/shared/config.ts","../src/shared/utils/fs.ts","../src/shared/template-manifest.ts","../src/shared/workspace.ts"],"sourcesContent":["export interface PolicyDefinition {\n description?: string;\n command: string;\n args?: string[];\n allowHelp?: boolean;\n autoApprove?: boolean;\n}\n\n// Resolved policy config: built-ins merged in, `false` overrides stripped.\n// This is what every consumer should see.\nexport interface PolicyConfig {\n policies: Record<string, PolicyDefinition>;\n}\n\n// On-disk shape: `false` opts a built-in out, a definition opts in / overrides.\nexport interface PolicyConfigFile {\n policies: Record<string, PolicyDefinition | false>;\n}\n\nexport const BUILTIN_POLICY_SCRIPTS_DIR = '.clawmini/policy-scripts';\n\nexport const BUILTIN_POLICIES: Record<string, PolicyDefinition> = {\n 'manage-policies': {\n description:\n 'Add, update, or remove clawmini policies (subcommands: add, update, remove). Reads are unrestricted via `requests show`.',\n command: `./${BUILTIN_POLICY_SCRIPTS_DIR}/manage-policies.js`,\n allowHelp: true,\n autoApprove: false,\n },\n 'run-host': {\n description: 'Run an arbitrary shell command on the host via `sh -c`',\n command: `./${BUILTIN_POLICY_SCRIPTS_DIR}/run-host.js`,\n allowHelp: true,\n autoApprove: false,\n },\n};\n\nexport type RequestState = 'Pending' | 'Approved' | 'Rejected';\n\nexport interface PolicyRequest {\n id: string;\n commandName: string;\n args: string[];\n fileMappings: Record<string, string>;\n cwd?: string;\n state: RequestState;\n createdAt: number;\n rejectionReason?: string;\n chatId: string;\n agentId: string;\n subagentId?: string;\n executionResult?: {\n stdout: string;\n stderr: string;\n exitCode: number;\n };\n}\n","import { z } from 'zod';\n\nconst PolicyDefinitionSchema = z.looseObject({\n description: z.string().optional(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n allowHelp: z.boolean().optional(),\n autoApprove: z.boolean().optional(),\n});\n\nexport const FallbackSchema = z.looseObject({\n commands: z\n .looseObject({\n new: z.string().optional(),\n append: z.string().optional(),\n getSessionId: z.string().optional(),\n getMessageContent: z.string().optional(),\n })\n .optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n retries: z.number().int().min(0).default(1),\n delayMs: z.number().int().min(0).default(1000),\n});\n\nexport const AgentSchema = z.looseObject({\n extends: z.string().optional(),\n commands: z\n .looseObject({\n new: z.string().optional(),\n append: z.string().optional(),\n getSessionId: z.string().optional(),\n getMessageContent: z.string().optional(),\n })\n .optional(),\n apiTokenEnvVar: z.string().optional(),\n apiUrlEnvVar: z.string().optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n subagentEnv: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n modelShorthands: z.record(z.string(), z.string()).optional(),\n directory: z.string().optional(),\n // `null` explicitly disables skill install/refresh for this agent.\n // `undefined` (omitted) falls back to the template's value or `.agents/skills`.\n skillsDir: z.string().nullable().optional(),\n fallbacks: z.array(FallbackSchema).optional(),\n files: z.string().default('./attachments').optional(),\n});\n\nexport type Agent = z.infer<typeof AgentSchema>;\n\nexport type CronJob = {\n id: string;\n createdAt?: string;\n message: string;\n reply?: string;\n agentId?: string;\n env?: Record<string, string | boolean>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session?: { type: string; [key: string]: any };\n schedule: { cron: string } | { every: string } | { at: string };\n nextSessionId?: string;\n action?: 'stop' | 'interrupt' | 'continue';\n jobs?: {\n add?: CronJob[];\n remove?: string[];\n };\n};\n\nexport const CronJobSchema = z.lazy(() =>\n z.looseObject({\n id: z.string().min(1),\n createdAt: z.string().optional(),\n message: z.string().default(''),\n reply: z.string().optional(),\n agentId: z.string().optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n session: z.looseObject({ type: z.string() }).optional(),\n schedule: z.union([\n z.looseObject({ cron: z.string() }),\n z.looseObject({ every: z.string() }),\n z.looseObject({ at: z.string() }),\n ]),\n nextSessionId: z.string().optional(),\n action: z.enum(['stop', 'interrupt', 'continue']).optional(),\n jobs: z\n .looseObject({\n add: z.array(z.lazy(() => CronJobSchema)).optional(),\n remove: z.array(z.string()).optional(),\n })\n .optional(),\n })\n) as z.ZodType<CronJob>;\n\nexport const RouterConfigSchema = z.union([\n z.string(),\n z.looseObject({\n use: z.string(),\n with: z.record(z.string(), z.any()).optional(),\n }),\n]);\n\nexport type RouterConfig = z.infer<typeof RouterConfigSchema>;\n\nexport const SubagentTrackerSchema = z.looseObject({\n id: z.string(),\n agentId: z.string().optional(),\n sessionId: z.string().optional(),\n createdAt: z.string(),\n status: z.enum(['active', 'completed', 'failed']),\n parentId: z.string().optional(),\n});\n\nexport type SubagentTracker = z.infer<typeof SubagentTrackerSchema>;\n\nexport const ChatSettingsSchema = z.looseObject({\n defaultAgent: z.string().optional(),\n sessions: z.record(z.string(), z.string()).optional(),\n routers: z.array(RouterConfigSchema).optional(),\n jobs: z.array(CronJobSchema).optional(),\n subagents: z.record(z.string(), SubagentTrackerSchema).optional(),\n});\n\nexport type ChatSettings = z.infer<typeof ChatSettingsSchema>;\n\nexport const AgentSessionSettingsSchema = z.looseObject({\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n});\n\nexport type AgentSessionSettings = z.infer<typeof AgentSessionSettingsSchema>;\n\nexport const EnvironmentSchema = z.looseObject({\n extends: z.string().optional(),\n init: z.string().optional(),\n up: z.string().optional(),\n down: z.string().optional(),\n prefix: z.string().optional(),\n envFormat: z.string().optional(),\n exportLiteTo: z.string().optional(),\n baseDir: z.string().optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n policies: z.record(z.string(), PolicyDefinitionSchema).optional(),\n});\n\nexport type Environment = z.infer<typeof EnvironmentSchema>;\n\nexport const SettingsSchema = z.looseObject({\n chats: z\n .looseObject({\n defaultId: z.string().optional(),\n })\n .optional(),\n defaultAgent: AgentSchema.optional(),\n environments: z.record(z.string(), z.string()).optional(),\n routers: z.array(RouterConfigSchema).optional(),\n files: z.string().default('./attachments').optional(),\n api: z\n .union([\n z.boolean(),\n z.looseObject({\n host: z.string().optional(),\n port: z.number().optional(),\n proxy_host: z.string().optional(),\n }),\n ])\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { sep } from 'node:path';\n\nexport function pathIsInsideDir(\n path: string,\n dir: string,\n { allowSameDir = false }: { allowSameDir?: boolean } = {}\n): boolean {\n const dirWithSep = dir.endsWith(sep) ? dir : dir + sep;\n if (allowSameDir && path === dir) {\n return true;\n }\n return path.startsWith(dirWithSep) && path !== dir;\n}\n","import fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash, randomBytes } from 'node:crypto';\nimport { z } from 'zod';\n\nexport type FileMode = 'track' | 'seed-once';\n\nconst TemplateManifestSchema = z.looseObject({\n files: z.record(z.string(), z.enum(['track', 'seed-once'])).optional(),\n});\n\nexport type TemplateManifest = z.infer<typeof TemplateManifestSchema>;\n\nconst InstalledFilesSchema = z.looseObject({\n files: z\n .record(\n z.string(),\n z.looseObject({\n sha: z.string(),\n })\n )\n .optional(),\n});\n\nexport type InstalledFiles = z.infer<typeof InstalledFilesSchema>;\n\nexport function sha256(content: string | Buffer): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function fileSha(filePath: string): Promise<string | null> {\n try {\n const buf = await fsPromises.readFile(filePath);\n return sha256(buf);\n } catch {\n return null;\n }\n}\n\nexport async function readTemplateManifest(templateDir: string): Promise<TemplateManifest | null> {\n const manifestPath = path.join(templateDir, 'template.json');\n try {\n const raw = await fsPromises.readFile(manifestPath, 'utf-8');\n const parsed = TemplateManifestSchema.safeParse(JSON.parse(raw));\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n}\n\n// Resolve the manifest mode for a given relative path. Exact entries win; if\n// no exact match, walk the prefix directory entries (longest first). Fallback\n// is the provided default mode.\nexport function getFileMode(\n relPath: string,\n manifest: TemplateManifest | null,\n defaultMode: FileMode\n): FileMode {\n if (!manifest?.files) return defaultMode;\n const normalized = relPath.split(path.sep).join('/');\n if (manifest.files[normalized]) return manifest.files[normalized];\n\n const dirEntries = Object.entries(manifest.files).filter(([k]) => k.endsWith('/'));\n dirEntries.sort((a, b) => b[0].length - a[0].length);\n for (const [entry, mode] of dirEntries) {\n if (normalized.startsWith(entry)) return mode;\n }\n return defaultMode;\n}\n\n// Walk every file under `dir`, returning relative paths (posix-style). Skips\n// template.json and settings.json at the root — both are template metadata\n// that is read for its specific purpose but never copied to the target.\nexport async function walkTemplateFiles(\n dir: string,\n opts: { skipRoot?: string[] } = {}\n): Promise<string[]> {\n const skipRoot = new Set(opts.skipRoot ?? ['template.json', 'settings.json']);\n const out: string[] = [];\n async function walk(current: string, prefix: string): Promise<void> {\n let entries: fs.Dirent[];\n try {\n entries = await fsPromises.readdir(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n await walk(path.join(current, entry.name), rel);\n } else if (entry.isFile()) {\n if (!prefix && skipRoot.has(entry.name)) continue;\n out.push(rel);\n }\n }\n }\n await walk(dir, '');\n return out;\n}\n\nexport type FileAction =\n | { action: 'write'; relPath: string; reason: 'new' | 'refresh' }\n | { action: 'skip-unchanged'; relPath: string }\n | { action: 'skip-diverged'; relPath: string; reason: 'edited' | 'no-recorded-sha' }\n | { action: 'skip-seed-once'; relPath: string }\n | { action: 'skip-absent-from-template'; relPath: string };\n\nexport interface RefreshPlan {\n actions: FileAction[];\n nextInstalled: InstalledFiles;\n}\n\nexport interface RefreshOptions {\n defaultMode: FileMode;\n firstInstall?: boolean;\n accept?: boolean;\n}\n\n// Produce the per-file plan + the `installed-files.json` state that would\n// follow from applying it. Does not touch disk — the caller decides whether\n// to execute the plan.\nexport async function planRefresh(\n templateDir: string,\n targetDir: string,\n manifest: TemplateManifest | null,\n installed: InstalledFiles | null,\n options: RefreshOptions\n): Promise<RefreshPlan> {\n const templateFiles = await walkTemplateFiles(templateDir);\n const actions: FileAction[] = [];\n const nextFiles: Record<string, { sha: string }> = {\n ...(installed?.files ?? {}),\n };\n\n for (const rel of templateFiles) {\n const mode = getFileMode(rel, manifest, options.defaultMode);\n const templatePath = path.join(templateDir, rel);\n const targetPath = path.join(targetDir, rel);\n const templateHash = sha256(await fsPromises.readFile(templatePath));\n\n if (options.firstInstall) {\n actions.push({ action: 'write', relPath: rel, reason: 'new' });\n nextFiles[rel] = { sha: templateHash };\n continue;\n }\n\n if (mode === 'seed-once') {\n const diskHash = await fileSha(targetPath);\n const recorded = installed?.files?.[rel]?.sha;\n if (diskHash === null && !recorded) {\n // Never seeded before — safe to drop the initial copy.\n actions.push({ action: 'write', relPath: rel, reason: 'new' });\n nextFiles[rel] = { sha: templateHash };\n } else {\n // Either present, or previously seeded and then deleted by the user.\n actions.push({ action: 'skip-seed-once', relPath: rel });\n }\n continue;\n }\n\n // mode === 'track'\n const recorded = installed?.files?.[rel]?.sha;\n const diskHash = await fileSha(targetPath);\n\n if (diskHash === null) {\n // File vanished — re-seed it and update the SHA.\n actions.push({ action: 'write', relPath: rel, reason: 'refresh' });\n nextFiles[rel] = { sha: templateHash };\n continue;\n }\n\n if (!recorded) {\n if (options.accept) {\n actions.push({ action: 'write', relPath: rel, reason: 'refresh' });\n nextFiles[rel] = { sha: templateHash };\n } else {\n actions.push({ action: 'skip-diverged', relPath: rel, reason: 'no-recorded-sha' });\n }\n continue;\n }\n\n if (diskHash === templateHash) {\n actions.push({ action: 'skip-unchanged', relPath: rel });\n nextFiles[rel] = { sha: templateHash };\n continue;\n }\n\n if (diskHash === recorded || options.accept) {\n actions.push({ action: 'write', relPath: rel, reason: 'refresh' });\n nextFiles[rel] = { sha: templateHash };\n } else {\n actions.push({ action: 'skip-diverged', relPath: rel, reason: 'edited' });\n }\n }\n\n return {\n actions,\n nextInstalled: { files: nextFiles },\n };\n}\n\nexport async function applyPlan(\n templateDir: string,\n targetDir: string,\n plan: RefreshPlan\n): Promise<void> {\n for (const action of plan.actions) {\n if (action.action !== 'write') continue;\n const src = path.join(templateDir, action.relPath);\n const dst = path.join(targetDir, action.relPath);\n await fsPromises.mkdir(path.dirname(dst), { recursive: true });\n await fsPromises.copyFile(src, dst);\n }\n}\n\nexport async function readInstalledFiles(filePath: string): Promise<InstalledFiles | null> {\n try {\n const raw = await fsPromises.readFile(filePath, 'utf-8');\n const parsed = InstalledFilesSchema.safeParse(JSON.parse(raw));\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n}\n\nexport async function writeInstalledFiles(filePath: string, data: InstalledFiles): Promise<void> {\n await fsPromises.mkdir(path.dirname(filePath), { recursive: true });\n // Atomic write: plain writeFile truncates before writing, so a crash mid-\n // write drops the SHA state entirely and turns every tracked file into a\n // `no-recorded-sha` skip on the next refresh. rename(2) on the same\n // filesystem is atomic — readers always see the old or new content.\n const tmpPath = `${filePath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n await fsPromises.writeFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');\n await fsPromises.rename(tmpPath, filePath);\n}\n\n// Return an installed-files slice scoped to keys starting with `prefix + '/'`,\n// with the prefix stripped. Used by skill refresh, which runs a plan per\n// skill against a shared `installed-files.json`.\nexport function sliceInstalledUnder(\n installed: InstalledFiles | null,\n prefix: string\n): InstalledFiles | null {\n if (!installed?.files) return null;\n const p = `${prefix}/`;\n const filtered: Record<string, { sha: string }> = {};\n for (const [k, v] of Object.entries(installed.files)) {\n if (k.startsWith(p)) filtered[k.slice(p.length)] = v;\n }\n return { files: filtered };\n}\n\n// Re-key a plan's `actions` and `nextInstalled` entries by prefixing every\n// `relPath`. Used for merging a skill plan (with skill-internal paths) back\n// into the workdir-relative installed-files store.\nexport function prefixPlanKeys(plan: RefreshPlan, prefix: string): RefreshPlan {\n const p = prefix ? `${prefix}/` : '';\n return {\n actions: plan.actions.map((a) => ({ ...a, relPath: p + a.relPath })),\n nextInstalled: {\n files: Object.fromEntries(\n Object.entries(plan.nextInstalled.files ?? {}).map(([k, v]) => [p + k, v])\n ),\n },\n };\n}\n","/* eslint-disable max-lines */\nimport { execSync } from 'node:child_process';\nimport {\n BUILTIN_POLICIES,\n type PolicyConfig,\n type PolicyConfigFile,\n type PolicyDefinition,\n} from './policies.js';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n type Agent,\n AgentSchema,\n type ChatSettings,\n ChatSettingsSchema,\n type AgentSessionSettings,\n AgentSessionSettingsSchema,\n type Environment,\n EnvironmentSchema,\n type Settings,\n SettingsSchema,\n} from './config.js';\nimport { pathIsInsideDir } from './utils/fs.js';\nimport {\n readTemplateManifest,\n planRefresh,\n applyPlan,\n walkTemplateFiles,\n writeInstalledFiles,\n readInstalledFiles,\n sliceInstalledUnder,\n prefixPlanKeys,\n type InstalledFiles,\n type RefreshPlan,\n type FileMode,\n} from './template-manifest.js';\n\nexport function getWorkspaceRoot(startDir = process.cwd()): string {\n let curr = startDir;\n while (curr !== path.parse(curr).root) {\n if (fs.existsSync(path.join(curr, '.clawmini'))) {\n return curr;\n }\n if (fs.existsSync(path.join(curr, 'package.json')) || fs.existsSync(path.join(curr, '.git'))) {\n return curr;\n }\n curr = path.dirname(curr);\n }\n return startDir;\n}\n\nexport function resolveAgentWorkDir(\n agentId: string,\n customDir?: string,\n startDir = process.cwd()\n): string {\n const workspaceRoot = getWorkspaceRoot(startDir);\n let dirPath = workspaceRoot;\n if (customDir) {\n dirPath = path.resolve(workspaceRoot, customDir);\n } else if (agentId !== 'default') {\n dirPath = path.resolve(workspaceRoot, agentId);\n }\n\n if (!pathIsInsideDir(dirPath, workspaceRoot, { allowSameDir: true })) {\n throw new Error('Invalid agent directory: resolves outside the workspace.');\n }\n\n return dirPath;\n}\n\n// Returns null when the agent has explicitly opted out of skills via\n// `\"skillsDir\": null` in its settings. Callers must handle null by\n// skipping any skill-related install/refresh work.\nexport function resolveAgentSkillsDir(\n agentId: string,\n agentData: Agent,\n startDir = process.cwd()\n): string | null {\n if (agentData.skillsDir === null) return null;\n const workDir = resolveAgentWorkDir(agentId, agentData.directory, startDir);\n return path.resolve(workDir, agentData.skillsDir || '.agents/skills');\n}\n\nexport async function ensureAgentWorkDir(\n agentId: string,\n customDir?: string,\n startDir = process.cwd()\n): Promise<string> {\n const dirPath = resolveAgentWorkDir(agentId, customDir, startDir);\n\n if (!fs.existsSync(dirPath)) {\n await fsPromises.mkdir(dirPath, { recursive: true });\n console.log(`Created agent working directory at ${dirPath}`);\n }\n return dirPath;\n}\n\nexport function getClawminiDir(startDir = process.cwd()): string {\n return path.join(getWorkspaceRoot(startDir), '.clawmini');\n}\n\nexport function getSocketPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'daemon.sock');\n}\n\nexport function getSettingsPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'settings.json');\n}\n\nexport function getPoliciesPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'policies.json');\n}\n\nexport function getChatSettingsPath(chatId: string, startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'chats', chatId, 'settings.json');\n}\n\nexport function isValidAgentId(agentId: string): boolean {\n if (!agentId || agentId.length === 0) return false;\n return /^[a-zA-Z0-9_]+(?:-[a-zA-Z0-9_]+)*$/.test(agentId);\n}\n\nexport function getAgentDir(agentId: string, startDir = process.cwd()): string {\n if (!isValidAgentId(agentId)) {\n throw new Error(`Invalid agent ID: ${agentId}`);\n }\n return path.join(getClawminiDir(startDir), 'agents', agentId);\n}\n\nexport function getAgentSettingsPath(agentId: string, startDir = process.cwd()): string {\n return path.join(getAgentDir(agentId, startDir), 'settings.json');\n}\n\nexport function getInstalledFilesPath(agentId: string, startDir = process.cwd()): string {\n return path.join(getAgentDir(agentId, startDir), 'installed-files.json');\n}\n\nexport function getAgentSessionSettingsPath(\n agentId: string,\n sessionId: string,\n startDir = process.cwd()\n): string {\n if (!isValidAgentId(agentId)) {\n throw new Error(`Invalid agent ID: ${agentId}`);\n }\n return path.join(\n getClawminiDir(startDir),\n 'agents',\n agentId,\n 'sessions',\n sessionId,\n 'settings.json'\n );\n}\n\nasync function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {\n try {\n const data = await fsPromises.readFile(filePath, 'utf-8');\n return JSON.parse(data) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nasync function writeJsonFile(filePath: string, data: Record<string, unknown>): Promise<void> {\n const dir = path.dirname(filePath);\n await fsPromises.mkdir(dir, { recursive: true });\n // Atomic write: a plain writeFile truncates then writes, so a concurrent\n // reader can observe an empty file and throw `JSON.parse(\"\")`. rename(2)\n // on the same filesystem is atomic, so readers always see old or new.\n const tmpPath = `${filePath}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;\n await fsPromises.writeFile(tmpPath, JSON.stringify(data, null, 2), 'utf-8');\n await fsPromises.rename(tmpPath, filePath);\n}\n\nexport async function readChatSettings(\n chatId: string,\n startDir = process.cwd()\n): Promise<ChatSettings | null> {\n const data = await readJsonFile(getChatSettingsPath(chatId, startDir));\n if (!data) return null;\n const parsed = ChatSettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeChatSettings(\n chatId: string,\n data: ChatSettings,\n startDir = process.cwd()\n): Promise<void> {\n await writeJsonFile(getChatSettingsPath(chatId, startDir), data as Record<string, unknown>);\n}\n\nexport const chatSettingsLocks = new Map<string, Promise<void>>();\n\nexport async function updateChatSettings(\n chatId: string,\n updater: (settings: ChatSettings) => ChatSettings | Promise<ChatSettings>,\n startDir = process.cwd()\n): Promise<void> {\n const prevLock = chatSettingsLocks.get(chatId) || Promise.resolve();\n let release!: () => void;\n const nextLock = new Promise<void>((resolve) => {\n release = resolve;\n });\n const nextLockPromise = prevLock.catch(() => {}).then(() => nextLock);\n chatSettingsLocks.set(chatId, nextLockPromise);\n\n try {\n await prevLock;\n const settings = (await readChatSettings(chatId, startDir)) || {};\n const updated = await updater(settings);\n await writeChatSettings(chatId, updated, startDir);\n } finally {\n release();\n if (chatSettingsLocks.get(chatId) === nextLockPromise) {\n chatSettingsLocks.delete(chatId);\n }\n }\n}\n\nexport async function readAgentSessionSettings(\n agentId: string,\n sessionId: string,\n startDir = process.cwd()\n): Promise<AgentSessionSettings | null> {\n const data = await readJsonFile(getAgentSessionSettingsPath(agentId, sessionId, startDir));\n if (!data) return null;\n const parsed = AgentSessionSettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeAgentSessionSettings(\n agentId: string,\n sessionId: string,\n data: AgentSessionSettings,\n startDir = process.cwd()\n): Promise<void> {\n await writeJsonFile(\n getAgentSessionSettingsPath(agentId, sessionId, startDir),\n data as Record<string, unknown>\n );\n}\n\n// Reads only the on-disk overlay (local settings.json). Used when editing the\n// overlay — callers that want the fully-resolved agent (template fields\n// merged in) use `getAgent` instead.\nexport async function getAgentOverlay(\n agentId: string,\n startDir = process.cwd()\n): Promise<Agent | null> {\n const filePath = getAgentSettingsPath(agentId, startDir);\n let dataStr: string;\n try {\n dataStr = await fsPromises.readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') return null;\n throw err;\n }\n\n let data: unknown;\n try {\n data = JSON.parse(dataStr);\n } catch (parseErr: unknown) {\n const message = parseErr instanceof Error ? parseErr.message : String(parseErr);\n throw new Error(`Invalid JSON in ${filePath}: ${message}`, { cause: parseErr });\n }\n\n const parsed = AgentSchema.safeParse(data);\n if (!parsed.success) {\n throw new Error(`Invalid schema in ${filePath}: ${parsed.error.message}`);\n }\n return parsed.data;\n}\n\nasync function readAgentTemplateSettings(\n templateName: string,\n startDir: string\n): Promise<Agent | null> {\n let templatePath: string;\n try {\n templatePath = await resolveTemplatePath(templateName, startDir);\n } catch {\n return null;\n }\n const settingsPath = path.join(templatePath, 'settings.json');\n const data = await readJsonFile(settingsPath);\n if (!data) return null;\n const parsed = AgentSchema.safeParse(data);\n if (!parsed.success) return null;\n // `directory` in a template is never used — the overlay declares the work\n // directory instead. Strip it so it doesn't pollute the merge.\n const result = { ...parsed.data };\n delete result.directory;\n return result;\n}\n\n// Returns the fully-resolved agent: reads the local overlay, resolves any\n// `extends` template, then shallow-merges the overlay over the template\n// field-by-field. `env` and `subagentEnv` are deep-merged one level so the\n// overlay can add one entry without dropping the template's defaults.\nexport async function getAgent(agentId: string, startDir = process.cwd()): Promise<Agent | null> {\n const overlay = await getAgentOverlay(agentId, startDir);\n if (!overlay) return null;\n if (!overlay.extends) return overlay;\n\n const template = await readAgentTemplateSettings(overlay.extends, startDir);\n if (!template) return overlay;\n\n const {\n env: overlayEnv,\n subagentEnv: overlaySub,\n modelShorthands: overlayShorthands,\n ...overlayRest\n } = overlay;\n const {\n env: templateEnv,\n subagentEnv: templateSub,\n modelShorthands: templateShorthands,\n ...templateRest\n } = template;\n const merged: Agent = { ...templateRest, ...overlayRest };\n const mergedEnv = mergeOneLevel(templateEnv, overlayEnv);\n if (mergedEnv) merged.env = mergedEnv;\n const mergedSub = mergeOneLevel(templateSub, overlaySub);\n if (mergedSub) merged.subagentEnv = mergedSub;\n const mergedShorthands = mergeOneLevel(templateShorthands, overlayShorthands);\n if (mergedShorthands) merged.modelShorthands = mergedShorthands;\n return merged;\n}\n\nexport async function writeAgentSettings(\n agentId: string,\n data: Agent,\n startDir = process.cwd()\n): Promise<void> {\n await ensureAgentWorkDir(agentId, data.directory, startDir);\n await writeJsonFile(getAgentSettingsPath(agentId, startDir), data as Record<string, unknown>);\n}\n\nexport const agentSettingsLocks = new Map<string, Promise<void>>();\n\n// Read-modify-write the agent's on-disk overlay under a per-agent lock so\n// concurrent callers can't lose updates. Throws if the overlay does not\n// exist — callers that intend to *create* an agent should use\n// `writeAgentSettings` directly. Returning `null` from the updater skips\n// the write (useful when a fresh read shows the change is a no-op).\n// Returns `true` iff a write happened.\nexport async function updateAgentOverlay(\n agentId: string,\n updater: (overlay: Agent) => Agent | null | Promise<Agent | null>,\n startDir = process.cwd()\n): Promise<boolean> {\n const prevLock = agentSettingsLocks.get(agentId) || Promise.resolve();\n let release!: () => void;\n const nextLock = new Promise<void>((resolve) => {\n release = resolve;\n });\n const nextLockPromise = prevLock.catch(() => {}).then(() => nextLock);\n agentSettingsLocks.set(agentId, nextLockPromise);\n\n try {\n await prevLock;\n const overlay = await getAgentOverlay(agentId, startDir);\n if (!overlay) {\n throw new Error(`Agent '${agentId}' has no settings overlay.`);\n }\n const updated = await updater(overlay);\n if (updated === null) return false;\n await writeAgentSettings(agentId, updated, startDir);\n return true;\n } finally {\n release();\n if (agentSettingsLocks.get(agentId) === nextLockPromise) {\n agentSettingsLocks.delete(agentId);\n }\n }\n}\n\nexport async function listAgents(startDir = process.cwd()): Promise<string[]> {\n const agentsDir = path.join(getClawminiDir(startDir), 'agents');\n try {\n const entries = await fsPromises.readdir(agentsDir, { withFileTypes: true });\n const agentIds = [];\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const settingsPath = path.join(agentsDir, entry.name, 'settings.json');\n try {\n await fsPromises.access(settingsPath);\n agentIds.push(entry.name);\n } catch {\n // No settings.json, probably just a sessions dir for a non-existent agent or default agent\n }\n }\n }\n return agentIds;\n } catch {\n return [];\n }\n}\n\nexport async function deleteAgent(agentId: string, startDir = process.cwd()): Promise<void> {\n const dir = getAgentDir(agentId, startDir);\n const agentsDir = path.join(getClawminiDir(startDir), 'agents');\n\n if (!pathIsInsideDir(dir, agentsDir)) {\n throw new Error(`Security Error: Cannot delete agent directory outside of ${agentsDir}`);\n }\n\n try {\n await fsPromises.rm(dir, { recursive: true, force: true });\n } catch {\n // Ignore if not found\n }\n}\n\nasync function isDirectory(dirPath: string): Promise<boolean> {\n try {\n const stat = await fsPromises.stat(dirPath);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport async function resolveTemplatePathBase(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n const workspaceRoot = getWorkspaceRoot(startDir);\n const localTemplatePath = path.join(workspaceRoot, '.clawmini', 'templates', templateName);\n\n if (await isDirectory(localTemplatePath)) {\n return localTemplatePath;\n }\n\n // Fallback to built-in templates\n // Find the clawmini package root by looking for package.json\n let currentDir = path.dirname(fileURLToPath(import.meta.url));\n while (\n currentDir !== path.parse(currentDir).root &&\n !fs.existsSync(path.join(currentDir, 'package.json'))\n ) {\n currentDir = path.dirname(currentDir);\n }\n\n const searchPath = path.join(currentDir, 'templates', templateName);\n\n if (await isDirectory(searchPath)) {\n return searchPath;\n }\n\n throw new Error(\n `Template not found: ${templateName} (searched local: ${localTemplatePath}, built-in: ${searchPath})`\n );\n}\n\nexport async function resolveTemplatePath(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n if (templateName === 'environments' || templateName.startsWith('environments/')) {\n throw new Error(`Template not found: ${templateName}`);\n }\n return resolveTemplatePathBase(templateName, startDir);\n}\n\nexport async function resolveEnvironmentTemplatePath(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n return resolveTemplatePathBase(path.join('environments', templateName), startDir);\n}\n\nexport async function resolveSkillsTemplatePath(startDir = process.cwd()): Promise<string> {\n return resolveTemplatePathBase('skills', startDir);\n}\n\nexport async function copyTemplateBase(\n templatePath: string,\n targetDir: string,\n allowMissingDir: boolean = false,\n overwrite: boolean = false\n): Promise<void> {\n // Check if target directory exists and is not empty\n try {\n const entries = await fsPromises.readdir(targetDir);\n if (entries.length > 0 && !overwrite) {\n throw new Error(`Target directory is not empty: ${targetDir}`);\n }\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n if (allowMissingDir) {\n await fsPromises.mkdir(targetDir, { recursive: true });\n } else {\n throw new Error(`Target directory does not exist: ${targetDir}`, { cause: err });\n }\n } else {\n throw err;\n }\n }\n\n // Recursively copy. The template.json manifest is never copied — it's\n // metadata about how to handle the other files.\n const rootTemplateJson = path.resolve(templatePath, 'template.json');\n await fsPromises.cp(templatePath, targetDir, {\n recursive: true,\n force: true,\n filter: (src) => path.resolve(src) !== rootTemplateJson,\n });\n}\n\nexport async function copyTemplate(\n templateName: string,\n targetDir: string,\n startDir = process.cwd(),\n opts: { force?: boolean } = {}\n): Promise<void> {\n const templatePath = await resolveTemplatePath(templateName, startDir);\n await copyTemplateBase(templatePath, targetDir, false, opts.force ?? false);\n}\n\nexport async function resolveTargetAgentSkillsDir(\n agentId: string,\n startDir = process.cwd()\n): Promise<string | null> {\n const agentDir = getAgentDir(agentId, startDir);\n try {\n const stat = await fsPromises.stat(agentDir);\n if (!stat.isDirectory()) {\n throw new Error(`Agent not found: ${agentId}`);\n }\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n throw new Error(`Agent not found: ${agentId}`, { cause: err });\n }\n throw err;\n }\n\n let agentData: Agent | null = null;\n try {\n agentData = await getAgent(agentId, startDir);\n } catch {\n // Ignore malformed settings.json\n }\n\n if (agentData) {\n return resolveAgentSkillsDir(agentId, agentData, startDir);\n }\n\n const workDir = resolveAgentWorkDir(agentId, undefined, startDir);\n return path.resolve(workDir, '.agents/skills');\n}\n\nexport async function copyEnvironmentTemplate(\n templateName: string,\n targetDir: string,\n startDir = process.cwd()\n): Promise<void> {\n const templatePath = await resolveEnvironmentTemplatePath(templateName, startDir);\n await copyTemplateBase(templatePath, targetDir, true);\n}\n\nexport async function copyAgentSkills(\n agentId: string,\n startDir = process.cwd(),\n overwrite = false\n): Promise<void> {\n const targetDir = await resolveTargetAgentSkillsDir(agentId, startDir);\n if (targetDir === null) {\n throw new Error(`Agent '${agentId}' has skills disabled (skillsDir is null).`);\n }\n const templatePath = await resolveSkillsTemplatePath(startDir);\n await copyTemplateBase(templatePath, targetDir, true, overwrite);\n}\n\nexport async function copyAgentSkill(\n agentId: string,\n skillName: string,\n startDir = process.cwd(),\n overwrite = false\n): Promise<void> {\n const targetDir = await resolveTargetAgentSkillsDir(agentId, startDir);\n if (targetDir === null) {\n throw new Error(`Agent '${agentId}' has skills disabled (skillsDir is null).`);\n }\n const templatePath = await resolveSkillsTemplatePath(startDir);\n const specificSkillPath = path.join(templatePath, skillName);\n\n try {\n const stat = await fsPromises.stat(specificSkillPath);\n if (!stat.isDirectory()) {\n throw new Error(`Skill not found: ${skillName}`);\n }\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n throw new Error(`Skill not found: ${skillName}`, { cause: err });\n }\n throw err;\n }\n\n const skillTargetDir = path.join(targetDir, skillName);\n await copyTemplateBase(specificSkillPath, skillTargetDir, true, overwrite);\n}\n\n// Return the subset of template files that already exist in the target\n// directory. Used to refuse a silent overwrite on first install.\nasync function collectTemplateCollisions(\n templateDir: string,\n targetDir: string\n): Promise<string[]> {\n const templateFiles = await walkTemplateFiles(templateDir);\n const collisions: string[] = [];\n for (const rel of templateFiles) {\n try {\n await fsPromises.access(path.join(targetDir, rel));\n collisions.push(rel);\n } catch {\n // not present — no collision\n }\n }\n return collisions;\n}\n\nfunction formatCollisionError(collisions: string[]): string {\n const preview = collisions\n .slice(0, 5)\n .map((p) => ` ${p}`)\n .join('\\n');\n const suffix = collisions.length > 5 ? `\\n ... and ${collisions.length - 5} more` : '';\n return `Target directory has existing files that the template would overwrite:\\n${preview}${suffix}\\nRe-run with --force to overwrite.`;\n}\n\nexport async function applyTemplateToAgent(\n agentId: string,\n templateName: string,\n overrides: Agent,\n startDir = process.cwd(),\n opts: { fork?: boolean; force?: boolean } = {}\n): Promise<void> {\n const agentWorkDir = resolveAgentWorkDir(agentId, overrides.directory, startDir);\n\n if (opts.fork) {\n // Legacy path: copy everything, merge template settings into the local\n // file, then strip the template metadata files from the workdir.\n await copyTemplate(templateName, agentWorkDir, startDir, { force: opts.force ?? false });\n\n const settingsPath = path.join(agentWorkDir, 'settings.json');\n const manifestPath = path.join(agentWorkDir, 'template.json');\n\n try {\n const rawSettings = await fsPromises.readFile(settingsPath, 'utf-8');\n const parsedSettings = JSON.parse(rawSettings);\n const validation = AgentSchema.safeParse(parsedSettings);\n\n if (validation.success) {\n const templateData = validation.data;\n if (templateData.directory) {\n console.warn(\n `Warning: Ignoring 'directory' field from template settings.json. Using default or provided directory.`\n );\n delete templateData.directory;\n }\n\n const mergedEnv = { ...(templateData.env || {}), ...(overrides.env || {}) };\n const mergedData: Agent = { ...templateData, ...overrides };\n delete mergedData.extends;\n if (Object.keys(mergedEnv).length > 0) mergedData.env = mergedEnv;\n\n await writeAgentSettings(agentId, mergedData, startDir);\n }\n } catch {\n // Ignore parsing or file not found errors\n }\n\n for (const tmp of [settingsPath, manifestPath]) {\n try {\n await fsPromises.rm(tmp);\n } catch {\n // Ignore if it doesn't exist\n }\n }\n return;\n }\n\n // Overlay mode: install files via the manifest, record SHAs, and write the\n // overlay pointing at the template. settings.json and template.json in the\n // template are metadata — neither gets copied.\n const templateDir = await resolveTemplatePath(templateName, startDir);\n const manifest = await readTemplateManifest(templateDir);\n await fsPromises.mkdir(agentWorkDir, { recursive: true });\n\n if (!opts.force) {\n const collisions = await collectTemplateCollisions(templateDir, agentWorkDir);\n if (collisions.length > 0) {\n throw new Error(formatCollisionError(collisions));\n }\n }\n\n const plan = await planRefresh(templateDir, agentWorkDir, manifest, null, {\n defaultMode: 'seed-once',\n firstInstall: true,\n });\n await applyPlan(templateDir, agentWorkDir, plan);\n await writeInstalledFiles(getInstalledFilesPath(agentId, startDir), plan.nextInstalled);\n\n const overlay: Agent = { extends: templateName, ...overrides };\n await writeAgentSettings(agentId, overlay, startDir);\n}\n\n// Refresh all `track` files in the agent's working directory against the\n// template content. Diverged files are skipped unless `accept` is true.\n// Returns the full plan so callers can report / dry-run as needed.\nexport async function refreshAgentTemplate(\n agentId: string,\n agent: Agent,\n startDir = process.cwd(),\n opts: { accept?: boolean; dryRun?: boolean } = {}\n): Promise<RefreshPlan | null> {\n if (!agent.extends) return null;\n const templateDir = await resolveTemplatePath(agent.extends, startDir);\n const agentWorkDir = resolveAgentWorkDir(agentId, agent.directory, startDir);\n const manifest = await readTemplateManifest(templateDir);\n const installedPath = getInstalledFilesPath(agentId, startDir);\n const installed = await readInstalledFiles(installedPath);\n\n const plan = await planRefresh(templateDir, agentWorkDir, manifest, installed, {\n defaultMode: 'seed-once',\n ...(opts.accept === undefined ? {} : { accept: opts.accept }),\n });\n\n if (opts.dryRun) return plan;\n\n await applyPlan(templateDir, agentWorkDir, plan);\n await writeInstalledFiles(installedPath, plan.nextInstalled);\n return plan;\n}\n\n// Refresh the agent's template skills. Skills default to `track` for files\n// unlisted in their manifest — the opposite of agent workdir files — because\n// the authoring model differs: clawmini ships skill content, agents edit it.\n// SHAs share the agent's installed-files.json keyed by the path relative to\n// the agent's working directory (e.g. `.gemini/skills/skill-creator/SKILL.md`).\nexport async function refreshAgentSkills(\n agentId: string,\n agent: Agent,\n startDir = process.cwd(),\n opts: { accept?: boolean; dryRun?: boolean; firstInstall?: boolean } = {}\n): Promise<RefreshPlan | null> {\n const skillsTargetDir = resolveAgentSkillsDir(agentId, agent, startDir);\n if (skillsTargetDir === null) return null;\n\n let skillsTemplateRoot: string;\n try {\n skillsTemplateRoot = await resolveSkillsTemplatePath(startDir);\n } catch {\n return null;\n }\n\n const agentWorkDir = resolveAgentWorkDir(agentId, agent.directory, startDir);\n const prefixRel = path.relative(agentWorkDir, skillsTargetDir).split(path.sep).join('/');\n\n let skillDirs: fs.Dirent[];\n try {\n skillDirs = await fsPromises.readdir(skillsTemplateRoot, { withFileTypes: true });\n } catch {\n return null;\n }\n\n const installedPath = getInstalledFilesPath(agentId, startDir);\n let installed = await readInstalledFiles(installedPath);\n const allActions: RefreshPlan['actions'] = [];\n\n for (const entry of skillDirs) {\n if (!entry.isDirectory()) continue;\n const skillName = entry.name;\n const skillTemplateDir = path.join(skillsTemplateRoot, skillName);\n const skillTargetDir = path.join(skillsTargetDir, skillName);\n const keyPrefix = `${prefixRel}/${skillName}`;\n\n const manifest = await readTemplateManifest(skillTemplateDir);\n const slice = sliceInstalledUnder(installed, keyPrefix);\n\n const plan = await planRefresh(skillTemplateDir, skillTargetDir, manifest, slice, {\n defaultMode: 'track',\n ...(opts.firstInstall ? { firstInstall: true } : {}),\n ...(opts.accept === undefined ? {} : { accept: opts.accept }),\n });\n\n const prefixed = prefixPlanKeys(plan, keyPrefix);\n allActions.push(...prefixed.actions);\n\n if (!opts.dryRun) {\n await applyPlan(skillTemplateDir, skillTargetDir, plan);\n installed = {\n files: {\n ...(installed?.files ?? {}),\n ...(prefixed.nextInstalled.files ?? {}),\n },\n };\n }\n }\n\n if (!opts.dryRun && installed) {\n await writeInstalledFiles(installedPath, installed);\n }\n\n return { actions: allActions, nextInstalled: installed ?? { files: {} } };\n}\n\n// Human-readable per-action lines for logging / dry-run. Prefixed with the\n// agent id for readability when invoked over multiple agents at once.\nexport function formatPlanActions(\n plan: RefreshPlan,\n opts: { agentId?: string; prefix?: string } = {}\n): string[] {\n const prefix = opts.prefix ?? (opts.agentId ? `[${opts.agentId}] ` : '');\n return plan.actions.map((action) => {\n switch (action.action) {\n case 'write':\n return `${prefix}${action.reason === 'new' ? 'install' : 'refresh'} ${action.relPath}`;\n case 'skip-unchanged':\n return `${prefix}unchanged ${action.relPath}`;\n case 'skip-seed-once':\n return `${prefix}seed-once ${action.relPath}`;\n case 'skip-diverged':\n return `${prefix}diverged ${action.relPath} (${action.reason})`;\n case 'skip-absent-from-template':\n return `${prefix}absent ${action.relPath}`;\n }\n });\n}\n\nexport type { InstalledFiles, RefreshPlan, FileMode };\n\nexport async function readSettings(startDir = process.cwd()): Promise<Settings | null> {\n const data = await readJsonFile(getSettingsPath(startDir));\n if (!data) return null;\n const parsed = SettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeSettings(data: Settings, startDir = process.cwd()): Promise<void> {\n await writeJsonFile(getSettingsPath(startDir), data as Record<string, unknown>);\n}\n\nexport async function readPoliciesFile(startDir = process.cwd()): Promise<PolicyConfigFile | null> {\n const data = await readJsonFile(getPoliciesPath(startDir));\n if (!data) return null;\n if (data.policies && typeof data.policies === 'object') {\n return data as unknown as PolicyConfigFile;\n }\n return null;\n}\n\n// Merge built-ins, drop any user entries explicitly set to `false`. Pure: never\n// mutates the input. A built-in is only injected when its installed script\n// exists on disk, so the resolved config never advertises a command we know is\n// missing. Relative `command` paths are resolved against the workspace root so\n// the policy points at a real on-disk script regardless of the caller's cwd.\nexport function resolvePolicies(\n file: PolicyConfigFile | null,\n clawminiDir: string\n): PolicyConfig | null {\n if (!file) return null;\n const workspaceRoot = path.dirname(clawminiDir);\n const resolveCommand = (definition: PolicyDefinition): PolicyDefinition => {\n if (!definition.command.startsWith('./') && !definition.command.startsWith('../')) {\n return definition;\n }\n return { ...definition, command: path.resolve(workspaceRoot, definition.command) };\n };\n\n const resolved: Record<string, PolicyDefinition> = {};\n for (const [name, value] of Object.entries(file.policies)) {\n if (value !== false) resolved[name] = resolveCommand(value);\n }\n for (const [name, definition] of Object.entries(BUILTIN_POLICIES)) {\n if (name in file.policies) continue;\n const scriptPath = path.join(clawminiDir, 'policy-scripts', `${name}.js`);\n if (!fs.existsSync(scriptPath)) continue;\n resolved[name] = resolveCommand(definition);\n }\n return { policies: resolved };\n}\n\nasync function readBasePolicies(startDir = process.cwd()): Promise<PolicyConfig | null> {\n const file = await readPoliciesFile(startDir);\n return resolvePolicies(file, getClawminiDir(startDir));\n}\n\n// Resolves env-scoped policies for the active environment at `targetPath`.\n// Relative `command` paths are resolved against the layered env search dirs\n// (overlay first, then built-in template), so overlays can point at a\n// built-in script without copying it.\nexport async function readEnvironmentPoliciesForPath(\n targetPath: string,\n startDir = process.cwd()\n): Promise<Record<string, PolicyDefinition>> {\n const envInfo = await getActiveEnvironmentInfo(targetPath, startDir);\n if (!envInfo) return {};\n\n const envConfig = await readEnvironment(envInfo.name, startDir);\n if (!envConfig?.policies) return {};\n\n const searchDirs = await getEnvironmentSearchDirs(envInfo.name, startDir);\n const resolved: Record<string, PolicyDefinition> = {};\n for (const [name, definition] of Object.entries(envConfig.policies)) {\n const command =\n definition.command.startsWith('./') || definition.command.startsWith('../')\n ? resolveLayeredRelativePath(definition.command, searchDirs)\n : definition.command;\n const entries = Object.entries({ ...definition, command }).filter(\n ([, value]) => value !== undefined\n );\n resolved[name] = Object.fromEntries(entries) as unknown as PolicyDefinition;\n }\n return resolved;\n}\n\nexport async function readPoliciesForPath(\n targetPath: string,\n startDir = process.cwd()\n): Promise<PolicyConfig | null> {\n const base = await readBasePolicies(startDir);\n const envPolicies = await readEnvironmentPoliciesForPath(targetPath, startDir);\n if (Object.keys(envPolicies).length === 0) return base;\n return {\n policies: {\n ...(base?.policies || {}),\n ...envPolicies,\n },\n };\n}\n\nexport function getEnvironmentPath(name: string, startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'environments', name);\n}\n\n// Deep-merge one level of the nested value (used for env/policies). Local wins\n// on conflict, missing keys flow through from the base.\nfunction mergeOneLevel<T>(\n base: Record<string, T> | undefined,\n overlay: Record<string, T> | undefined\n): Record<string, T> | undefined {\n if (!base && !overlay) return undefined;\n return { ...(base || {}), ...(overlay || {}) };\n}\n\nasync function readEnvironmentRaw(name: string, startDir: string): Promise<Environment | null> {\n const localPath = path.join(getEnvironmentPath(name, startDir), 'env.json');\n const local = await readJsonFile(localPath);\n if (local) {\n const parsed = EnvironmentSchema.safeParse(local);\n if (parsed.success) return parsed.data;\n }\n\n // Parent references in `extends` resolve against built-in templates.\n try {\n const builtinDir = await resolveEnvironmentTemplatePath(name, startDir);\n const builtinData = await readJsonFile(path.join(builtinDir, 'env.json'));\n if (builtinData) {\n const parsed = EnvironmentSchema.safeParse(builtinData);\n if (parsed.success) return parsed.data;\n }\n } catch {\n // No built-in template with this name\n }\n\n return null;\n}\n\nasync function readBuiltinEnvironment(name: string, startDir: string): Promise<Environment | null> {\n let builtinDir: string;\n try {\n builtinDir = await resolveEnvironmentTemplatePath(name, startDir);\n } catch {\n return null;\n }\n const data = await readJsonFile(path.join(builtinDir, 'env.json'));\n if (!data) return null;\n const parsed = EnvironmentSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nasync function resolveEnvironmentWithSeen(\n name: string,\n startDir: string,\n seen: Set<string>\n): Promise<Environment | null> {\n if (seen.has(name)) {\n throw new Error(`Environment extends cycle detected at '${name}'`);\n }\n seen.add(name);\n\n const local = await readEnvironmentRaw(name, startDir);\n if (!local || !local.extends) return local;\n\n // Self-extends (`.clawmini/environments/macos` with `extends: \"macos\"`)\n // pivots from the overlay layer down to the built-in template of the same\n // name. Without this branch, the recursion would hit `seen` and throw.\n const parent =\n local.extends === name\n ? await readBuiltinEnvironment(name, startDir)\n : await resolveEnvironmentWithSeen(local.extends, startDir, seen);\n if (!parent) return local;\n\n const { env: localEnv, policies: localPolicies, ...localRestRaw } = local;\n delete (localRestRaw as { extends?: string }).extends;\n const { env: parentEnv, policies: parentPolicies, ...parentRest } = parent;\n const merged: Environment = { ...parentRest, ...localRestRaw };\n const mergedEnv = mergeOneLevel(parentEnv, localEnv);\n if (mergedEnv) merged.env = mergedEnv;\n const mergedPolicies = mergeOneLevel(parentPolicies, localPolicies);\n if (mergedPolicies) merged.policies = mergedPolicies;\n return merged;\n}\n\nexport async function readEnvironment(\n name: string,\n startDir = process.cwd()\n): Promise<Environment | null> {\n return resolveEnvironmentWithSeen(name, startDir, new Set());\n}\n\n// The ordered list of directories an {ENV_DIR}-relative path should resolve\n// against. The local overlay always comes first. If the overlay extends\n// another environment, the parent's local overlay (if any) and the parent's\n// built-in template dir are appended, walking up the chain. Consumers pick\n// the first dir that actually contains the referenced file.\nexport async function getEnvironmentSearchDirs(\n name: string,\n startDir = process.cwd()\n): Promise<string[]> {\n const dirs: string[] = [];\n const seen = new Set<string>();\n\n let currentName: string | undefined = name;\n while (currentName && !seen.has(currentName)) {\n seen.add(currentName);\n const overlayDir = getEnvironmentPath(currentName, startDir);\n if (fs.existsSync(overlayDir) && !dirs.includes(overlayDir)) dirs.push(overlayDir);\n\n let builtinDir: string | null = null;\n try {\n builtinDir = await resolveEnvironmentTemplatePath(currentName, startDir);\n } catch {\n // No built-in — overlay is self-contained\n }\n if (builtinDir && !dirs.includes(builtinDir)) dirs.push(builtinDir);\n\n const overlayEnvPath = path.join(overlayDir, 'env.json');\n const overlayData = await readJsonFile(overlayEnvPath);\n const overlayParsed = overlayData ? EnvironmentSchema.safeParse(overlayData) : null;\n if (overlayParsed?.success && overlayParsed.data.extends) {\n currentName = overlayParsed.data.extends;\n continue;\n }\n\n if (builtinDir) {\n const builtinEnvPath = path.join(builtinDir, 'env.json');\n const builtinData = await readJsonFile(builtinEnvPath);\n const builtinParsed = builtinData ? EnvironmentSchema.safeParse(builtinData) : null;\n if (builtinParsed?.success && builtinParsed.data.extends) {\n currentName = builtinParsed.data.extends;\n continue;\n }\n }\n\n currentName = undefined;\n }\n\n return dirs;\n}\n\n// Replace {ENV_DIR}[/subpath] occurrences with the first search dir that\n// actually contains the subpath on disk. If no dir has it, the first search\n// dir is used (so errors at exec time name a consistent location).\nexport function substituteLayeredEnvDir(input: string, searchDirs: string[]): string {\n if (searchDirs.length === 0) return input;\n return input.replace(/\\{ENV_DIR\\}(?:\\/([^\\s'\"}]+))?/g, (_match, sub?: string) => {\n if (!sub) return searchDirs[0]!;\n for (const dir of searchDirs) {\n const candidate = path.resolve(dir, sub);\n if (fs.existsSync(candidate)) return candidate;\n }\n return path.resolve(searchDirs[0]!, sub);\n });\n}\n\n// Resolve a relative (`./foo` or `../foo`) policy/command path against the\n// layered search dirs, preferring overlay first. Returns the first match; if\n// none exists, returns the overlay-dir resolution for a stable error path.\nexport function resolveLayeredRelativePath(relPath: string, searchDirs: string[]): string {\n if (searchDirs.length === 0) return relPath;\n for (const dir of searchDirs) {\n const candidate = path.resolve(dir, relPath);\n if (fs.existsSync(candidate)) return candidate;\n }\n return path.resolve(searchDirs[0]!, relPath);\n}\n\nexport async function getActiveEnvironmentInfo(\n targetPath: string,\n startDir = process.cwd()\n): Promise<{ name: string; targetPath: string } | null> {\n const settings = await readSettings(startDir);\n if (!settings?.environments) return null;\n\n const workspaceRoot = getWorkspaceRoot(startDir);\n const resolvedTarget = path.resolve(workspaceRoot, targetPath);\n\n let bestMatch: { name: string; targetPath: string } | null = null;\n let maxDepth = -1;\n\n for (const [envPath, envName] of Object.entries(settings.environments)) {\n const resolvedEnvPath = path.resolve(workspaceRoot, envPath);\n\n if (pathIsInsideDir(resolvedTarget, resolvedEnvPath, { allowSameDir: true })) {\n const depth = resolvedEnvPath.split(path.sep).length;\n if (depth > maxDepth) {\n maxDepth = depth;\n bestMatch = { name: envName, targetPath: resolvedEnvPath };\n }\n }\n }\n\n return bestMatch;\n}\n\nexport async function getActiveEnvironmentName(\n targetPath: string,\n startDir = process.cwd()\n): Promise<string | null> {\n const info = await getActiveEnvironmentInfo(targetPath, startDir);\n return info ? info.name : null;\n}\n\nexport async function enableEnvironment(\n name: string,\n targetPath: string = './',\n startDir = process.cwd(),\n opts: { fork?: boolean } = {}\n): Promise<void> {\n const targetDir = getEnvironmentPath(name, startDir);\n\n // Default: write a minimal overlay (`{extends: name}`) pointing at the\n // built-in. Fork: clone the whole built-in template directory (legacy).\n if (!fs.existsSync(targetDir)) {\n if (opts.fork) {\n await copyEnvironmentTemplate(name, targetDir, startDir);\n console.log(`Forked environment template '${name}'.`);\n } else {\n // Require the built-in to exist so we don't write a dangling overlay.\n await resolveEnvironmentTemplatePath(name, startDir);\n await fsPromises.mkdir(targetDir, { recursive: true });\n await fsPromises.writeFile(\n path.join(targetDir, 'env.json'),\n JSON.stringify({ extends: name }, null, 2),\n 'utf-8'\n );\n console.log(`Enabled environment overlay '${name}' (extends built-in).`);\n }\n } else {\n console.log(`Environment '${name}' already exists in workspace.`);\n }\n\n const settings = (await readSettings(startDir)) || { chats: { defaultId: '' } };\n const environments = settings.environments || {};\n\n environments[targetPath] = name;\n settings.environments = environments;\n\n await writeSettings(settings, startDir);\n console.log(`Enabled environment '${name}' for path '${targetPath}'.`);\n\n // Execute init command if present\n const envConfig = await readEnvironment(name, startDir);\n if (envConfig?.init) {\n // Get the target directory for the environment\n const workspaceRoot = getWorkspaceRoot(startDir);\n const affectedDir = path.resolve(workspaceRoot, targetPath);\n console.log(`Executing init command for environment '${name}': ${envConfig.init}`);\n execSync(envConfig.init, { cwd: affectedDir, stdio: 'inherit' });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ # Development Log: Adapter Slash Command Autocomplete
2
+
3
+ ## Step 1: Define Discord Slash Commands
4
+ - Starting work on defining slash commands for the Discord adapter.
5
+ - Verified that Step 1 was already mostly completed in `src/adapter-discord/commands.ts`. Marked as completed.
6
+
7
+ ## Step 2: Register Commands on Discord Startup
8
+ - Added `REST` and `Routes` to `src/adapter-discord/index.ts` to register global slash commands on `ClientReady`.
9
+ - Updated tests in `src/adapter-discord/index.test.ts` to mock `REST` and verify `rest.put` is called with the serialized command data.
10
+ ## Step 3: Handle Discord Slash Command Interactions
11
+ - Added logic in `src/adapter-discord/interactions.ts` to check if `interaction.isChatInputCommand()` is true.
12
+ - Reconstructed text commands by concatenating `commandName` with the arguments for `policy_id` and `rationale`.
13
+ - Added unit tests in `src/adapter-discord/interactions.test.ts` for handling chat input commands and routing them to the daemon via TRPC.
14
+ - Completed Step 3.
15
+
16
+ ## Step 4: Document Google Chat Slash Commands Setup
17
+ - Updated `docs/guides/google_chat_adapter_setup.md` to include explicit instructions on configuring slash commands in Google Cloud Console.
18
+ - Verified markdown formatting and content clarity.
19
+ - All tasks in tickets.md for adapter slash autocomplete are now completed.
@@ -0,0 +1,18 @@
1
+ # Research Notes: Adapter Slash Autocomplete
2
+
3
+ ## Discord Adapter
4
+ - Currently, the Discord adapter listens for messages starting with `/` but does not seem to register them as official Discord Application Commands (Slash Commands).
5
+ - To support autocomplete and a native `/` menu, the bot must register "Application Commands" via the Discord API (typically via HTTP PUT to `/applications/{application.id}/commands` or guild-specific equivalents).
6
+ - Discord slash commands support autocomplete for `STRING`, `INTEGER`, and `NUMBER` options, firing an `INTERACTION_CREATE` event with `type: 4` when a user types, allowing up to 25 choices to be returned within 3 seconds.
7
+
8
+ ## Google Chat Adapter
9
+ - Currently, the Google Chat adapter also parses `/` prefix messages.
10
+ - Google Chat supports native slash commands, but they must be registered in the Google Cloud Console under the Google Chat API Configuration.
11
+ - Google Chat API documentation indicates that Slash Commands can be configured with a Command ID and Description. When invoked, they send a specific `type: MESSAGE` event with a `slashCommand` object.
12
+ - Autocomplete in Google Chat happens automatically based on the registered commands in the console. There is no dynamic "on-typing" autocomplete API for Google Chat that lets the bot return choices in real-time, unlike Discord. The autocomplete just filters the pre-registered list of commands.
13
+
14
+ ## Requirements from Prompt
15
+ - Built-in routers: `/new`, `/stop`, `/approve`, `/reject`, `/pending`.
16
+ - Adapter-level commands: `/show`, `/hide`, `/debug`.
17
+ - Must show in a menu when the user types `/` and autocomplete.
18
+ - "If no platform-level primitives exist to let apps define their own slash commands, ignore it for that platform." - Since Google Chat only supports static slash command registration via Console, we can document this or implement automated setup via Google Cloud API if the credentials permit. Discord supports dynamic registration and interaction.
@@ -0,0 +1,46 @@
1
+ # Product Requirements Document: Adapter Slash Command Autocomplete
2
+
3
+ ## 1. Vision
4
+ To enhance the usability and discoverability of Clawmini's core features by integrating native platform slash commands into chat clients. By providing autocomplete for built-in routers and adapter-level commands in Discord and Google Chat, users will no longer need to memorize command names or precise syntax to interact effectively with the agent.
5
+
6
+ ## 2. Product & Market Background
7
+ Users operating Clawmini through chat platforms (Discord and Google Chat) often rely on `/` prefixed commands to manage sessions (e.g., `/new`, `/stop`), moderate policy requests (e.g., `/approve`, `/reject`, `/pending`), and debug adapter configurations (e.g., `/show`, `/hide`, `/debug`). Currently, these platforms treat these as regular text messages unless explicitly registered via their respective APIs. Without native integration, users lack the discoverability and guided argument entry that modern chat applications typically provide via a popup menu when typing `/`.
8
+
9
+ ## 3. Use Cases
10
+ * **Discoverability:** A user types `/` in a Discord channel where Clawmini is present and immediately sees a list of available commands like `/new` or `/pending`, without needing external documentation.
11
+ * **Guided Argument Entry:** A user wants to reject a pending action. They type `/reject` and the platform prompts them for `[policy_id]` and an optional `[rationale]`, making the required data format obvious.
12
+ * **Platform Nativity:** A user clicks a slash command from the autocomplete menu and hits enter. The platform natively formats the command, reducing typos.
13
+
14
+ ## 4. Requirements
15
+
16
+ ### 4.1 Discord Adapter
17
+ * **Startup Registration:** The adapter must automatically sync a predefined list of slash commands with the Discord API (using `applicationCommands`) when the bot logs in (`client.on('ready')`).
18
+ * **Command Set:** The following commands must be registered with their appropriate descriptions and arguments (using `SlashCommandBuilder` or raw JSON options):
19
+ * `/new`: Start a new session. Optional `STRING` argument: `message`.
20
+ * `/stop`: Stop the current generation.
21
+ * `/approve`: Approve a pending policy request. Required `STRING` argument: `policy_id`.
22
+ * `/reject`: Reject a pending policy request. Required `STRING` argument: `policy_id`. Optional `STRING` argument: `rationale`.
23
+ * `/pending`: List pending policy requests.
24
+ * `/show`: Show filtered UI elements. Optional `STRING` argument: `all`.
25
+ * `/hide`: Hide filtered UI elements. Optional `STRING` argument: `all`.
26
+ * `/debug`: Toggle debug view or show debug for a message. Optional `STRING` argument: `message_id`.
27
+ * **Interaction Handling:** The Discord adapter must listen for `interactionCreate` events where `interaction.isChatInputCommand()` is true.
28
+ * When invoked, the adapter should reconstruct the equivalent text command string (e.g., `/reject req-123 my reason`) and pipe it into the existing `handleAdapterCommand` or routing logic, or process it directly if that's cleaner.
29
+ * It must acknowledge the interaction appropriately (e.g., using `interaction.reply` or deferring).
30
+
31
+ ### 4.2 Google Chat Adapter
32
+ * **Documentation Only:** Since Google Chat requires static registration of slash commands via the Google Cloud Console (and does not provide a dynamic, unprivileged API to register them at runtime like Discord), the solution for this platform will be documentation.
33
+ * **Setup Guide Updates:** The `docs/guides/` or README documentation must be updated to provide explicit instructions on how users can manually configure the slash commands in the Google Cloud Console.
34
+ * The guide should list the exact Command Names (e.g., `/new`, `/approve`), suggested Command IDs, and Descriptions to configure.
35
+ * The adapter's existing text-based message parsing for `/` commands will continue to handle these invocations when the user selects them from the Google Chat autocomplete menu (which sends a `type: MESSAGE` event containing the text and slashCommand annotations).
36
+
37
+ ### 4.3 General
38
+ * **Existing Text Parsing:** The existing text-based `/` command handlers MUST remain functional, as users may still manually type the commands (especially in Google Chat) or use legacy clients. The new native slash commands should act as an alternative entry point that wraps or feeds into the same underlying logic.
39
+
40
+ ## 5. Privacy & Security Concerns
41
+ * No new sensitive information is collected.
42
+ * Discord slash command registration is a standard API feature and requires the application to be invited with `applications.commands` scope (which is already standard for Discord bots).
43
+
44
+ ## 6. Implementation Notes
45
+ * **Discord `discord.js`:** The `REST` module from `discord.js` should be used to `put` the commands to `Routes.applicationCommands(clientId)`.
46
+ * No dynamic autocomplete for *arguments* (e.g. suggesting specific policy IDs while typing) is required at this time. Standard option definitions are sufficient to trigger the base platform UI.
@@ -0,0 +1,6 @@
1
+ # Questions for PRD
2
+
3
+ 1. **Google Chat Registration:** Google Chat slash commands generally need to be configured statically in the Google Cloud Console. Should we document this as a manual setup step for users, or should the adapter attempt to use the Google Workspace APIs to register them programmatically (which requires additional OAuth scopes)?
4
+ 2. **Discord Registration:** Since Discord requires bots to register slash commands via their API to appear in the autocomplete menu, should the adapter sync these commands automatically on startup, or only when a specific CLI flag/command is run?
5
+ 3. **Dynamic Autocomplete:** Discord supports *dynamic* autocomplete for arguments (e.g., suggesting specific chat IDs for `/approve`). Google Chat does not support dynamic argument autocomplete, only auto-completing the base command itself. Is it acceptable to implement dynamic argument autocomplete only for Discord?
6
+ 4. **Command List:** The prompt mentions `/new`, `/stop`, `/approve`, `/reject`, `/pending`, `/show`, `/hide`, `/debug`. Are there any arguments for these commands that we should provide specific autocomplete suggestions for (e.g., policy IDs for `/approve`)?
@@ -0,0 +1,21 @@
1
+ # Tickets: Adapter Slash Command Autocomplete
2
+
3
+ ## Step 1: Define Discord Slash Commands
4
+ - **Description:** Create a module or configuration defining the supported slash commands (`/new`, `/stop`, `/approve`, `/reject`, `/pending`, `/show`, `/hide`, `/debug`) and their arguments (e.g., `policy_id` for `/approve`, `rationale` for `/reject`) using `discord.js` structures.
5
+ - **Verification:** Add unit tests to verify the command structures are correctly defined. Run `npm run validate`.
6
+ - **Status:** Completed
7
+
8
+ ## Step 2: Register Commands on Discord Startup
9
+ - **Description:** Update the Discord adapter's initialization logic (`client.on('ready')`) to register the defined slash commands globally using `REST` and `Routes.applicationCommands(clientId)` from `discord.js`.
10
+ - **Verification:** Add unit tests to ensure the registration API is called correctly upon client ready. Run `npm run validate`.
11
+ - **Status:** Completed
12
+
13
+ ## Step 3: Handle Discord Slash Command Interactions
14
+ - **Description:** Add an event listener for `interactionCreate` in the Discord adapter. If `interaction.isChatInputCommand()` is true, reconstruct the equivalent text command (e.g., `/reject req-123 my reason`) and pipe it into the existing text command logic. Ensure the interaction is appropriately acknowledged (`interaction.reply` or `interaction.deferReply`).
15
+ - **Verification:** Add unit tests to simulate `interactionCreate` events and verify they are correctly parsed, routed, and acknowledged. Run `npm run validate`.
16
+ - **Status:** Completed
17
+
18
+ ## Step 4: Document Google Chat Slash Commands Setup
19
+ - **Description:** Update the documentation (e.g., `README.md` or a new guide in `docs/guides/`) to provide explicit instructions on how users can manually configure these slash commands (Command Names, IDs, and Descriptions) in the Google Cloud Console for Google Chat.
20
+ - **Verification:** Manually review the generated markdown documentation for clarity and completeness.
21
+ - **Status:** Completed
@@ -0,0 +1,22 @@
1
+ # Development Log
2
+
3
+ ## Ticket 1: Session Timeout Isolation
4
+ - Starting work.
5
+ - Created E2E test `session-timeout-subagents.test.ts`.
6
+ - Discovered that the `subagentId` was not being passed inside `routerState` when `executeRouterPipeline` is called by `executeSubagent`. This caused the `session-timeout` router to incorrectly schedule a timeout for the parent session during a subagent's status update API call.
7
+ - Added `subagentId` to the initial `routerState` in `src/daemon/api/subagent-utils.ts` and updated `src/daemon/routers/session-timeout.ts` to return the unmodified state if `state.subagentId` is set.
8
+ - All tests and checks passed.
9
+
10
+ ## Ticket 2: Policy Confirmation System Messages
11
+ - Created E2E test `slash-policies-system-messages.test.ts` to verify confirmation messages on `/approve` and `/reject`.
12
+ - Encountered an issue where vitest was timing out, resolved by adding a `15000` ms timeout to the test blocks.
13
+ - Edited `src/daemon/routers/slash-policies.ts` to omit `subagentId` from `userNotificationMsg` for both `/reject` and `/approve` commands.
14
+ - All tests and checks passed.
15
+
16
+ ## Ticket 3: Subagent Policy Execution Routing Fix
17
+ - Created E2E test `slash-policies-subagent-execution.test.ts` to verify that when a subagent initiates a policy execution, the result is correctly routed to the subagent instead of the parent agent.
18
+ - Added `subagentId` to `executeDirectMessage` in `handleUserMessage` within `src/daemon/message.ts` using `finalState.subagentId`.
19
+ - Discovered that `finalState.subagentId` was undefined because the `PolicyRequestSchema` in `src/daemon/request-store.ts` was stripping the `subagentId` field upon load.
20
+ - Updated `PolicyRequestSchema` to include `subagentId: z.string().optional()`.
21
+ - Verified that with these fixes, the execution response has the correct `subagentId` and is logged to the subagent's session instead of the main agent's session.
22
+ - All tests and checks passed successfully.
@@ -0,0 +1,28 @@
1
+ # Investigation Notes: Subagent Job and Policy Notification Fixes
2
+
3
+ ## 1. Session-Timeout Subagent Bug
4
+ - **Issue**: The `session-timeout` router currently intercepts messages sent by subagents and schedules a session timeout job because it doesn't filter out messages with a `subagentId`.
5
+ - **Finding**: In `src/daemon/routers/session-timeout.ts`, the router checks `if (state.env?.__SESSION_TIMEOUT__ === 'true')` but ignores whether `state.subagentId` is present.
6
+ - **Fix**: The router should immediately return `state` without scheduling a timeout if `state.subagentId` is present.
7
+
8
+ ## 2. Policy Approval/Rejection Routing Bug
9
+ - **Issue**: When a policy request (created by a subagent) is approved or rejected by the user, the result is sent to the *main agent* (the subagent's parent) rather than the subagent itself.
10
+ - **Finding**:
11
+ - In `src/daemon/routers/slash-policies.ts`, `slashPolicies` correctly returns `subagentId: req.subagentId` in its updated `RouterState`.
12
+ - However, in `src/daemon/message.ts`, the `handleUserMessage` function calls `executeDirectMessage` without passing `finalState.subagentId`.
13
+ - Because `executeDirectMessage` receives `undefined` for `subagentId`, it routes the policy execution result to the main agent instead of the subagent.
14
+ - **Fix**: Update `handleUserMessage` in `src/daemon/message.ts` to pass `finalState.subagentId` as the 7th argument to `executeDirectMessage`.
15
+
16
+ ## 3. Policy Confirmation Messages to the User
17
+ - **Issue**: The user needs UI confirmation of policy approval/rejection, but the current logic misroutes or omits these messages.
18
+ - **Finding**:
19
+ - For `/reject`, `slash-policies.ts` creates a `userNotificationMsg` (with `role: 'system'` and `displayRole: 'agent'`), but it incorrectly sets `subagentId: req.subagentId`. This hides the confirmation from the main chat.
20
+ - For `/approve`, no such `userNotificationMsg` is created at all; it only creates `logMsg` (with `displayRole: 'user'`) which is sent to the subagent's context.
21
+ - **Fix**:
22
+ - Ensure both `/approve` and `/reject` branches create a `userNotificationMsg` with `role: 'system'`, `displayRole: 'agent'`, and **no** `subagentId` (meaning it goes to the main chat for the user to see).
23
+ - This ensures the confirmation goes to the user, while the actual execution result (routed via the fixed `handleUserMessage`) correctly wakes up the subagent, not the parent agent.
24
+
25
+ ## E2E Tests
26
+ We need to provide e2e tests that verify these behaviors.
27
+ - A test for session-timeout ignoring subagent messages.
28
+ - A test for proposing a policy in a subagent, approving it, and verifying the subagent gets the result while the main agent does not.
@@ -0,0 +1,59 @@
1
+ # Product Requirements Document (PRD): Subagent Job & Policy Routing Fixes
2
+
3
+ ## 1. Vision & Background
4
+ Subagents perform background tasks, execute tools, and handle nested workflows without cluttering the user's primary chat session. However, due to routing issues, subagents are currently triggering main-session side effects:
5
+ 1. Messages from subagents trigger the `session-timeout` router, which inappropriately schedules session timeouts for subagents.
6
+ 2. When a user approves or rejects a policy request created by a subagent, the resulting message is routed to the *parent* (main) agent rather than the subagent itself. This wakes up the parent agent incorrectly while leaving the subagent suspended without the result.
7
+ 3. The confirmation messages generated during policy approval and rejection are improperly scoped, causing the user to miss out on the approval/rejection feedback in their main interface.
8
+
9
+ This feature resolves these workflow bugs, ensuring subagents handle their own policy resolutions and avoid main-session lifecycle events.
10
+
11
+ ## 2. Use Cases
12
+ - **Subagent Background Execution:** A user runs a task that spawns a subagent. The subagent does not trigger a session-timeout prompt after completion.
13
+ - **Subagent Policy Approval:** A subagent requests a policy. The user approves it via `/approve`. The subagent receives the stdout/stderr, processes it, and finishes the task, while the parent agent remains unaffected.
14
+ - **User Feedback:** Upon running `/approve` or `/reject`, the user immediately sees a system notification in their main chat confirming the action.
15
+
16
+ ## 3. Requirements
17
+
18
+ ### 3.1. Session Timeout Isolation
19
+ - **Condition:** If an incoming message to the router pipeline contains a `subagentId`, the `session-timeout` router MUST ignore it.
20
+ - **Action:** Update `src/daemon/routers/session-timeout.ts` to instantly return `state` when `state.subagentId` is present.
21
+
22
+ ### 3.2. Subagent Policy Execution Routing
23
+ - **Condition:** When a router (like `slash-policies`) returns a state containing a `subagentId`, the daemon must correctly route the execution of that state to the subagent.
24
+ - **Action:** Update `handleUserMessage` in `src/daemon/message.ts` to pass `finalState.subagentId` as the 7th argument (`subagentId`) to `executeDirectMessage`. This ensures the policy's result goes to the correct subagent session instead of defaulting to the main agent.
25
+
26
+ ### 3.3. Policy Confirmation System Messages
27
+ - **Condition:** When a policy is approved or rejected, the user must receive a confirmation message in the main chat log. The message must NOT trigger a response from the parent agent.
28
+ - **Action:** Update `src/daemon/routers/slash-policies.ts`:
29
+ - For **Reject**: Modify `userNotificationMsg` to remove the `subagentId` field entirely (so `subagentId` is effectively null/undefined). Ensure `role` is `'system'` and `displayRole` is `'agent'`.
30
+ - For **Approve**: Introduce a new `userNotificationMsg` (identical in structure to the rejection one but with the approval `agentMessage`) and append it to the chat to notify the user. Ensure `subagentId` is not set on this notification.
31
+
32
+ ## 4. E2E Testing Strategy
33
+
34
+ To verify these fixes, we will write explicit E2E tests leveraging the `debug` agent template and `clawmini-lite.js` to simulate subagents and policy requests.
35
+
36
+ 1. **Session Timeout Subagent Test (e.g., in `cli/e2e/session-timeout.test.ts` or `cli/e2e/subagents.test.ts`):**
37
+ - **Setup:** Initialize the workspace with the `session-timeout` router active (via `settings.json`) and a timeout interval of 1 hour. Add a `debug-agent` using the `debug` template.
38
+ - **Action:**
39
+ - Start a chat (`runCli(['chats', 'add', 'chat-timeout-sub'])`).
40
+ - Send a message to the chat that spawns a subagent using the debug agent: `runCli(['messages', 'send', 'clawmini-lite.js subagents spawn --async "echo subagent-job-test"', '--chat', 'chat-timeout-sub', '--agent', 'debug-agent'])`.
41
+ - **Verification:**
42
+ - Wait for the subagent execution to complete (look for `[DEBUG] echo subagent-job-test:` in the `chat.jsonl`).
43
+ - Inspect the chat's `settings.json` (or verify via `chat.jsonl`) to assert that **no** `__session_timeout__` cron job was scheduled as a result of the subagent's message.
44
+
45
+ 2. **Policy Approval Routing Test (e.g., in `cli/e2e/subagents-policies.test.ts` or `cli/e2e/policies.test.ts`):**
46
+ - **Setup:**
47
+ - Add a `debug-agent` and export `clawmini-lite.js`.
48
+ - Define a custom policy in `policies.json` (e.g., `"test-policy": { "command": "echo", "args": ["policy executed"] }`).
49
+ - **Action:**
50
+ - Send a message that spawns a subagent which requests the policy: `clawmini-lite.js subagents spawn --async "clawmini-lite.js policies request test-policy"`.
51
+ - Wait until the pending request appears (the subagent will be blocked waiting).
52
+ - Have the *user* send the approval command in the main chat: `runCli(['messages', 'send', '/approve req-id', '--chat', '...'])`.
53
+ - **Verification:**
54
+ - Read the `chat.jsonl` to ensure that a `system` message confirming the approval (`Approved request, running test-policy`) was appended with `displayRole: 'agent'` and NO `subagentId` (making it visible to the user).
55
+ - Wait for the subagent to finish. The `chat.jsonl` should contain `[DEBUG] ...` output from the subagent containing the execution result (`policy executed`), proving the subagent successfully awoke and received the policy result.
56
+ - Ensure the parent main agent did NOT log any message reacting to the policy execution result (which would happen if the result was incorrectly routed to the main agent instead of the subagent).
57
+
58
+ ## 5. Security & Privacy Concerns
59
+ No new security or privacy risks are introduced. This PRD corrects internal routing logic. By ensuring policy results only go to the subagent that requested them, this actually improves isolation between agents and subagents, enforcing better security boundaries.
@@ -0,0 +1,3 @@
1
+ # Questions
2
+
3
+ No questions were required. The investigation into `src/daemon/message.ts`, `src/daemon/routers/slash-policies.ts`, and `src/daemon/routers/session-timeout.ts` yielded the exact root cause of both issues outlined in the prompt. The fix involves correcting a missing parameter in `handleUserMessage` and ensuring `userNotificationMsg` drops the `subagentId` when appending to the chat.
@@ -0,0 +1,49 @@
1
+ # Tickets: Subagent Job and Policy Notification Fixes
2
+
3
+ ## Ticket 1: Session Timeout Isolation
4
+ **Status:** completed
5
+ **Description:** Prevent the `session-timeout` router from scheduling a session timeout when processing messages from subagents.
6
+
7
+ **Tasks:**
8
+ 1. Create an E2E test (e.g., in `test-workspace/` or `src/cli/e2e/session-timeout-subagents.test.ts` as appropriate) leveraging `clawmini-lite.js subagents spawn` to verify that when a subagent sends a message, no `__session_timeout__` cron job is scheduled.
9
+ 2. Run the test and verify it fails (Red).
10
+ 3. Update `src/daemon/routers/session-timeout.ts` to immediately return `state` without scheduling a timeout if `state.subagentId` is present.
11
+ 4. Run the test and verify it passes (Green).
12
+
13
+ **Verification Steps:**
14
+ - The E2E test passes reliably.
15
+ - Run `npm run validate` to ensure all checks pass.
16
+
17
+ ---
18
+
19
+ ## Ticket 2: Policy Confirmation System Messages
20
+ **Status:** completed
21
+ **Description:** Ensure that when a user runs `/approve` or `/reject` for a subagent policy, a system message confirming the action is appended to the main chat log so the user gets UI feedback.
22
+
23
+ **Tasks:**
24
+ 1. Create an E2E test verifying that upon `/approve` and `/reject` of a policy requested by a subagent, a system message (`role: 'system'`, `displayRole: 'agent'`) is appended to the main chat log with no `subagentId`.
25
+ 2. Run the test and verify it fails (Red).
26
+ 3. Update `src/daemon/routers/slash-policies.ts`:
27
+ - For `/reject`: modify `userNotificationMsg` to remove the `subagentId` entirely.
28
+ - For `/approve`: introduce a new `userNotificationMsg` (identical structure to the rejection notification but with the approval agent message) without `subagentId`, and append it to the chat.
29
+ 4. Run the test and verify it passes (Green).
30
+
31
+ **Verification Steps:**
32
+ - The E2E test passes reliably.
33
+ - Run `npm run validate` to ensure all checks pass.
34
+
35
+ ---
36
+
37
+ ## Ticket 3: Subagent Policy Execution Routing Fix
38
+ **Status:** completed
39
+ **Description:** Fix the routing bug where policy execution results are incorrectly sent to the main agent instead of the requesting subagent.
40
+
41
+ **Tasks:**
42
+ 1. Create an E2E test where a subagent requests a policy, and the user approves it. Validate that the subagent receives the execution result (stdout) and finishes its task, while the parent agent does *not* log any reaction to the policy execution result.
43
+ 2. Run the test and verify it fails (Red) because the main agent incorrectly intercepts the policy result.
44
+ 3. Update `handleUserMessage` in `src/daemon/message.ts` to pass `finalState.subagentId` as the 7th argument when calling `executeDirectMessage`.
45
+ 4. Run the test and verify it passes (Green).
46
+
47
+ **Verification Steps:**
48
+ - The E2E test passes reliably.
49
+ - Run `npm run validate` to ensure all checks pass.
@@ -0,0 +1,30 @@
1
+ # Development Log
2
+
3
+ ## Step 1: Implement the Unified Test Environment API
4
+ - Implemented `TestEnvironment` class in `src/cli/e2e/test-environment.ts`
5
+ - It wraps E2E test setup, replacing the procedural context utilities from `utils.ts`.
6
+ - Configures isolated testing directories.
7
+ - Generates unique ID.
8
+ - Implements methods for CLI usage (`runCli`, `init`, `up`, `down`, `addAgent`, `addChat`, `updateSettings`, `writePolicies`, `setupSubagentEnv`).
9
+ - Implemented unit tests for `TestEnvironment` in `src/cli/e2e/test-environment.test.ts`.
10
+ - Fixed early teardown spawn issue when tests haven't created the `e2eDir`.
11
+ - ESLint type errors resolved by explicitly casting variables to correct types and using proper typing instead of `any`.
12
+ - Validated via `npm run validate` locally. The checks pass seamlessly.
13
+ - Marked Step 1 as complete.
14
+
15
+ ## Step 2: Implement Event-Driven State Verification (SSE)
16
+ - Updated `src/cli/e2e/test-environment.ts` to include a TRPC client connecting to the daemon socket via unix socket fetch and custom EventSource.
17
+ - Implemented `connect()`, `disconnect()` and buffered message ingestion via the `.waitForMessages.subscribe()` endpoint.
18
+ - Added a generic `waitForMessage(predicate)` function to synchronously block until a specific message is received or timeout is hit.
19
+ - Addressed TypeScript ESLint errors in the implementation by casting to proper TRPC Subscription types and explicitly importing `CommandLogMessage`.
20
+ - Marked Step 2 as complete.
21
+
22
+ ## Step 3: Migrate a Single Test to the New API
23
+ - Refactored `src/cli/e2e/messages.test.ts` to utilize the new `TestEnvironment` implementation.
24
+ - Replaced the procedural utility setup `createE2EContext` with class based `TestEnvironment`.
25
+ - Replaced raw filesystem `fs.readFileSync` polling on `.clawmini/chats/default/chat.jsonl` with deterministic real-time event checks via `await env.waitForMessage()`.
26
+ - Replaced multiple arbitrary `setTimeout` waits with reliable predicate checking, preventing race conditions or premature passing.
27
+ - Re-tested the refactored workflow with `npm run validate` which guarantees all linting, static types, unit tests, and E2E tests are intact.
28
+ - Addressed legacy linter warnings `Unexpected any` from ESLint across refactored code.
29
+ - Successfully passed `vitest` for the new rewritten E2E tests.
30
+ - Marked Step 3 as complete.
@@ -0,0 +1,29 @@
1
+ # E2E Test Improvements Research Notes
2
+
3
+ ## Current State of E2E Tests
4
+ - The e2e tests are located in `src/cli/e2e/`.
5
+ - They use Vitest.
6
+ - `global-setup.ts` runs `npm run build` once before all tests.
7
+ - `utils.ts` provides `createE2EContext` which creates isolated test directories (e.g., `e2e-policy-flows`) and sets up a new git repo.
8
+ - The `runCli` function spawns a new Node process to execute the built CLI (`dist/cli/index.mjs`). This happens for every CLI command.
9
+ - The daemon is typically started in the background (or foreground) via `runCli(['up'])`.
10
+
11
+ ## Patterns in Tests (e.g., `slash-policies.test.ts`, `messages.test.ts`)
12
+ 1. **Setup Boilerplate**: A test file typically calls `setupE2E()` then `runCli(['init'])`, then modifies `.clawmini/settings.json` via `fs.readFileSync` and `fs.writeFileSync` to inject test-specific configurations (like port number, policies, or specific agent commands).
13
+ 2. **Daemon Spawning**: Tests interact with a running daemon out-of-process.
14
+ 3. **Polling for State**: Tests heavily rely on `waitForMessage` or `vi.waitFor` to repeatedly read and parse `chat.jsonl` files from the filesystem until a specific state is reached. This is slow and prone to race conditions or timeouts.
15
+ 4. **Snapshotting**: Snapshots are used, but require custom sanitation (e.g., `sanitizeContentForSnapshot`) to replace dynamic IDs (like `requestId`) with static placeholders (`<REQ_ID>`).
16
+
17
+ ## Areas for Improvement
18
+ 1. **Speed/Performance**:
19
+ - Repeatedly spawning the Node process is slow. While `spawn` is authentic E2E, we might be able to provide a fast-path for some tests (integration level) or keep the daemon running across more tests.
20
+ - Polling the filesystem adds delay (tests wait for an interval of 200-250ms).
21
+ 2. **Reliability**:
22
+ - Filesystem polling can be flaky on different OSs or under heavy load.
23
+ - If the daemon crashes or hangs, tests might just timeout obscurely.
24
+ 3. **Developer Experience / Simplicity**:
25
+ - Mutating JSON config files inline is repetitive. A test harness could provide a `builder` or fluent API to define the workspace state upfront.
26
+ - E2E tests are verbose. We could introduce a test abstraction, similar to a Page Object Model, e.g., `const testEnv = await setupEnv({ policies: [...] }); await testEnv.cli('messages send...'); const msg = await testEnv.waitForMessage(...);`.
27
+ - Instead of polling log files, maybe the daemon could expose an event stream (WebSocket or SSE on the HTTP API) when in test mode, allowing tests to await actual events rather than polling the disk.
28
+ 4. **Realism**:
29
+ - Using real child processes is good for realism, but some tests set up "fake" subagents via `clawmini-lite.js` shell scripts. This is clever but might be fragile.