clawmini 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.0arZe_Uf.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +0 -1
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -118
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.0arZe_Uf.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/D5iV40bG.js +0 -1
  328. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  329. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  331. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/D5iV40bG.js +0 -1
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -0,0 +1,357 @@
1
+ import type { ChatMessage } from '../chats.js';
2
+
3
+ export interface TurnLogEntry {
4
+ timestamp: string;
5
+ kind: 'tool' | 'subagent' | 'policy' | 'system';
6
+ summary: string;
7
+ rawLength: number;
8
+ subagentId?: string;
9
+ messageRole: string;
10
+ }
11
+
12
+ export interface FormatOpts {
13
+ maxToolPreview?: number;
14
+ /**
15
+ * Reference time for rendering relative timestamps (e.g. `0s`, `1m5s`).
16
+ * When omitted, timestamps fall back to wall-clock `HH:MM:SS`.
17
+ */
18
+ turnStartedAt?: string;
19
+ }
20
+
21
+ export interface CondenseOpts {
22
+ maxChars: number;
23
+ }
24
+
25
+ export type CondenseResult =
26
+ | { kind: 'fits'; text: string }
27
+ | { kind: 'rollover'; finalText: string; carryEntries: TurnLogEntry[] };
28
+
29
+ const DEFAULT_MAX_TOOL_PREVIEW = 400;
30
+ const TRUNCATED_SUFFIX = '…[truncated]';
31
+ const ROLLOVER_MARKER = '• …log continues';
32
+
33
+ function pad2(n: number): string {
34
+ return n < 10 ? `0${n}` : String(n);
35
+ }
36
+
37
+ function formatRelative(deltaMs: number): string {
38
+ const sec = Math.max(0, Math.floor(deltaMs / 1000));
39
+ if (sec < 60) return `${sec}s`;
40
+ const m = Math.floor(sec / 60);
41
+ const s = sec % 60;
42
+ return s === 0 ? `${m}m` : `${m}m${s}s`;
43
+ }
44
+
45
+ function formatTimestamp(iso: string, turnStartedAt?: string): string {
46
+ if (turnStartedAt) {
47
+ const start = new Date(turnStartedAt).getTime();
48
+ const now = iso ? new Date(iso).getTime() : Date.now();
49
+ if (!Number.isNaN(start) && !Number.isNaN(now)) {
50
+ return formatRelative(now - start);
51
+ }
52
+ }
53
+ const d = iso ? new Date(iso) : new Date();
54
+ const valid = !Number.isNaN(d.getTime()) ? d : new Date();
55
+ return `${pad2(valid.getHours())}:${pad2(valid.getMinutes())}:${pad2(valid.getSeconds())}`;
56
+ }
57
+
58
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
59
+
60
+ function shortSubagentId(id: string): string {
61
+ // UUIDs are visual noise; the first hex segment is enough to disambiguate.
62
+ // User-supplied ids (e.g. `hello-sub`) are typically short already.
63
+ return UUID_RE.test(id) ? id.slice(0, 8) : id;
64
+ }
65
+
66
+ interface ToolPrincipal {
67
+ verb: string;
68
+ arg: string;
69
+ }
70
+
71
+ /**
72
+ * Extract a (verb, principal-arg) pair from a tool message so the turn log
73
+ * can render `read: foo.md` instead of `read_file({ "file_path": "foo.md" })`.
74
+ * Unknown tool names fall back to `<name>: <stringified-payload>` so we don't
75
+ * silently lose information.
76
+ */
77
+ function extractToolPrincipal(name: string, payload: unknown): ToolPrincipal {
78
+ const p = (payload ?? {}) as Record<string, unknown>;
79
+ const str = (v: unknown) => (v == null ? '' : String(v));
80
+
81
+ switch (name) {
82
+ case 'read_file':
83
+ case 'Read':
84
+ return { verb: 'read', arg: str(p.file_path ?? p.path) };
85
+ case 'write_file':
86
+ case 'create_file':
87
+ case 'Write':
88
+ return { verb: 'write', arg: str(p.file_path ?? p.path) };
89
+ case 'edit_file':
90
+ case 'Edit':
91
+ return { verb: 'edit', arg: str(p.file_path ?? p.path) };
92
+ case 'run_shell_command':
93
+ case 'shell':
94
+ case 'Bash':
95
+ return { verb: 'shell', arg: str(p.command) };
96
+ case 'activate_skill':
97
+ case 'Skill':
98
+ return { verb: 'skill', arg: str(p.name ?? p.skill) };
99
+ case 'glob':
100
+ case 'Glob':
101
+ return { verb: 'glob', arg: str(p.pattern) };
102
+ case 'grep':
103
+ case 'Grep':
104
+ return { verb: 'grep', arg: str(p.pattern) };
105
+ case 'web_fetch':
106
+ case 'WebFetch':
107
+ return { verb: 'fetch', arg: str(p.url) };
108
+ }
109
+
110
+ const arg = payload === undefined || payload === null ? '' : JSON.stringify(payload);
111
+ return { verb: name, arg };
112
+ }
113
+
114
+ function statusSigil(status: 'completed' | 'failed'): string {
115
+ return status === 'completed' ? '✅' : '❌';
116
+ }
117
+
118
+ /** Emoji for messages crossing the subagent boundary. */
119
+ const SUBAGENT_TO = '👉';
120
+ const SUBAGENT_FROM = '👈';
121
+
122
+ /**
123
+ * Emoji that stands in for a known tool verb. When present, it replaces the
124
+ * verb word entirely (`🐚 sleep 20` instead of `shell: sleep 20`). Unknown
125
+ * tools fall through to the `<name>: <arg>` form, preserving accessibility.
126
+ */
127
+ const VERB_EMOJI: Record<string, string> = {
128
+ read: '📖',
129
+ write: '✍️',
130
+ edit: '✏️',
131
+ shell: '🧑‍💻',
132
+ skill: '📚',
133
+ glob: '📁',
134
+ grep: '🔎',
135
+ fetch: '🌐',
136
+ };
137
+
138
+ const SUBAGENT_MARKER = '🤖';
139
+
140
+ /** Emoji rendered on the turn's opening entry (posted when the turn starts). */
141
+ export const TURN_START_EMOJI = '▶️';
142
+
143
+ /**
144
+ * Entries produced *inside* a subagent (a tool call, policy, system event
145
+ * with `subagentId` set) need a marker so the reader knows the activity
146
+ * happened inside the delegated turn. Boundary events (prompt, reply, status)
147
+ * already name the subagent via 👉/👈/✅, so they're excluded.
148
+ */
149
+ function needsSubagentMarker(entry: TurnLogEntry): boolean {
150
+ if (!entry.subagentId) return false;
151
+ return (
152
+ entry.messageRole !== 'user' &&
153
+ entry.messageRole !== 'agent' &&
154
+ entry.messageRole !== 'subagent_status'
155
+ );
156
+ }
157
+
158
+ function truncate(s: string, max: number): string {
159
+ if (s.length <= max) return s;
160
+ const budget = Math.max(0, max - TRUNCATED_SUFFIX.length);
161
+ return s.slice(0, budget) + TRUNCATED_SUFFIX;
162
+ }
163
+
164
+ function sanitize(s: string): string {
165
+ return s.replace(/\s*\r?\n\s*/g, ' ').trim();
166
+ }
167
+
168
+ function renderEntry(entry: TurnLogEntry): string {
169
+ const prefix = needsSubagentMarker(entry)
170
+ ? `${SUBAGENT_MARKER} ${shortSubagentId(entry.subagentId!)} `
171
+ : '';
172
+ return `• ${entry.timestamp} ${prefix}${entry.summary}`;
173
+ }
174
+
175
+ /**
176
+ * Build the first entry posted into a turn's activity log so the thread
177
+ * appears as soon as `turnStarted` fires, rather than waiting for the first
178
+ * real event.
179
+ */
180
+ export function buildTurnStartEntry(): TurnLogEntry {
181
+ const summary = `${TURN_START_EMOJI} Started processing…`;
182
+ return {
183
+ timestamp: '0s',
184
+ kind: 'system',
185
+ summary,
186
+ rawLength: summary.length,
187
+ messageRole: 'turn_start',
188
+ };
189
+ }
190
+
191
+ export function formatTurnLogEntry(
192
+ message: ChatMessage,
193
+ opts: FormatOpts = {}
194
+ ): TurnLogEntry | null {
195
+ const maxToolPreview = opts.maxToolPreview ?? DEFAULT_MAX_TOOL_PREVIEW;
196
+ const timestamp = formatTimestamp(message.timestamp, opts.turnStartedAt);
197
+
198
+ if (message.role === 'user' || message.role === 'agent') {
199
+ if (!message.subagentId) return null;
200
+ // Subagent prompts and final replies are part of the parent turn's
201
+ // activity: the prompt shows what the subagent was told to do, the reply
202
+ // shows what it produced. Render them in the log so the reader can follow
203
+ // the orchestration without switching context.
204
+ const direction = message.role === 'user' ? SUBAGENT_TO : SUBAGENT_FROM;
205
+ const content = sanitize(message.content);
206
+ const id = shortSubagentId(message.subagentId);
207
+ const summary = `${direction} ${id}: ${truncate(content, maxToolPreview)}`;
208
+ return {
209
+ timestamp,
210
+ kind: 'subagent',
211
+ summary,
212
+ rawLength: content.length,
213
+ messageRole: message.role,
214
+ subagentId: message.subagentId,
215
+ };
216
+ }
217
+
218
+ if (message.role === 'tool') {
219
+ const { verb, arg } = extractToolPrincipal(message.name, message.payload);
220
+ const cleanArg = sanitize(arg);
221
+ const emoji = VERB_EMOJI[verb];
222
+ const argPreview = cleanArg ? truncate(cleanArg, maxToolPreview) : '';
223
+ let summary: string;
224
+ if (emoji) {
225
+ summary = argPreview ? `${emoji} ${argPreview}` : emoji;
226
+ } else {
227
+ summary = argPreview ? `${verb}: ${argPreview}` : verb;
228
+ }
229
+ const entry: TurnLogEntry = {
230
+ timestamp,
231
+ kind: 'tool',
232
+ summary,
233
+ rawLength: cleanArg.length,
234
+ messageRole: message.role,
235
+ };
236
+ if (message.subagentId) entry.subagentId = message.subagentId;
237
+ return entry;
238
+ }
239
+
240
+ if (message.role === 'subagent_status') {
241
+ const id = shortSubagentId(message.subagentId);
242
+ const summary = `${statusSigil(message.status)} ${id}`;
243
+ const entry: TurnLogEntry = {
244
+ timestamp,
245
+ kind: 'subagent',
246
+ summary,
247
+ rawLength: summary.length,
248
+ messageRole: message.role,
249
+ subagentId: message.subagentId,
250
+ };
251
+ return entry;
252
+ }
253
+
254
+ if (message.role === 'policy') {
255
+ const body = `${message.commandName} ${message.args.join(' ')}`.trim();
256
+ const summary = `policy ${message.status}: ${body}`;
257
+ const entry: TurnLogEntry = {
258
+ timestamp,
259
+ kind: 'policy',
260
+ summary: truncate(sanitize(summary), maxToolPreview),
261
+ rawLength: summary.length,
262
+ messageRole: message.role,
263
+ };
264
+ if (message.subagentId) entry.subagentId = message.subagentId;
265
+ return entry;
266
+ }
267
+
268
+ if (message.role === 'system') {
269
+ // subagent_update is the wake-up signal that re-enters the parent agent
270
+ // after an async subagent completes. Its content is a `<notification>`
271
+ // envelope — internal orchestration the reader doesn't need. The ✅ from
272
+ // subagent_status already conveys completion, and the parent's follow-up
273
+ // reply shows the response.
274
+ if (message.event === 'subagent_update') return null;
275
+ const content = sanitize(message.content || '');
276
+ const summary = content ? `${message.event}: ${content}` : message.event;
277
+ const entry: TurnLogEntry = {
278
+ timestamp,
279
+ kind: 'system',
280
+ summary: truncate(summary, maxToolPreview),
281
+ rawLength: summary.length,
282
+ messageRole: message.role,
283
+ };
284
+ if (message.subagentId) entry.subagentId = message.subagentId;
285
+ return entry;
286
+ }
287
+
288
+ if (message.role === 'command') {
289
+ // The raw shell command (usually the agent template wrapping
290
+ // $CLAW_CLI_MESSAGE) isn't informative to a reader skimming the log;
291
+ // tool calls and subagent events already describe what the agent did.
292
+ return null;
293
+ }
294
+
295
+ if (message.role === 'legacy_log') {
296
+ const content = sanitize(message.content);
297
+ const summary = content ? `log: ${content}` : 'log';
298
+ const entry: TurnLogEntry = {
299
+ timestamp,
300
+ kind: 'system',
301
+ summary: truncate(summary, maxToolPreview),
302
+ rawLength: content.length,
303
+ messageRole: message.role,
304
+ };
305
+ if (message.subagentId) entry.subagentId = message.subagentId;
306
+ return entry;
307
+ }
308
+
309
+ return null;
310
+ }
311
+
312
+ function joinLines(entries: TurnLogEntry[]): string {
313
+ return entries.map(renderEntry).join('\n');
314
+ }
315
+
316
+ export function condenseTurnLog(
317
+ entries: readonly TurnLogEntry[],
318
+ opts: CondenseOpts
319
+ ): CondenseResult {
320
+ const snapshot = entries.slice();
321
+ if (snapshot.length === 0) return { kind: 'fits', text: '' };
322
+
323
+ const fullText = joinLines(snapshot);
324
+ if (fullText.length <= opts.maxChars) return { kind: 'fits', text: fullText };
325
+
326
+ const markerReserve = ROLLOVER_MARKER.length + 1;
327
+ const budget = Math.max(0, opts.maxChars - markerReserve);
328
+
329
+ const kept: TurnLogEntry[] = [];
330
+ let runningLength = 0;
331
+ for (let i = 0; i < snapshot.length; i++) {
332
+ const line = renderEntry(snapshot[i]!);
333
+ const next = runningLength === 0 ? line.length : runningLength + 1 + line.length;
334
+ if (next > budget) {
335
+ if (kept.length === 0) {
336
+ // Single entry's line is larger than the per-message budget: hard-
337
+ // truncate its rendered form so we don't get stuck in a "same carry"
338
+ // rollover loop. The continuation marker is baked into the truncated
339
+ // text rather than appended, so the reader still sees the `…` signal.
340
+ const truncatedLine = truncate(line, opts.maxChars);
341
+ return {
342
+ kind: 'rollover',
343
+ finalText: truncatedLine,
344
+ carryEntries: snapshot.slice(i + 1),
345
+ };
346
+ }
347
+ const carryEntries = snapshot.slice(i);
348
+ const finalText = `${joinLines(kept)}\n${ROLLOVER_MARKER}`;
349
+ return { kind: 'rollover', finalText, carryEntries };
350
+ }
351
+ runningLength = next;
352
+ kept.push(snapshot[i]!);
353
+ }
354
+
355
+ // Should not reach here; fallback.
356
+ return { kind: 'fits', text: fullText };
357
+ }
@@ -4,7 +4,8 @@ import {
4
4
  applyTemplateToAgent,
5
5
  readChatSettings,
6
6
  writeChatSettings,
7
- copyAgentSkills,
7
+ refreshAgentSkills,
8
+ getAgent,
8
9
  } from './workspace.js';
9
10
  import { createChat, listChats } from './chats.js';
10
11
 
@@ -12,17 +13,23 @@ export async function createAgentWithChat(
12
13
  agentId: string,
13
14
  agentData: Agent,
14
15
  template?: string,
15
- startDir = process.cwd()
16
+ startDir = process.cwd(),
17
+ opts: { fork?: boolean; force?: boolean } = {}
16
18
  ): Promise<void> {
17
19
  await writeAgentSettings(agentId, agentData, startDir);
18
20
 
19
21
  if (template) {
20
- await applyTemplateToAgent(agentId, template, agentData, startDir);
22
+ await applyTemplateToAgent(agentId, template, agentData, startDir, opts);
21
23
  }
22
24
 
23
25
  try {
24
- await copyAgentSkills(agentId, startDir);
25
- console.log(`Copied skills to agent ${agentId}.`);
26
+ const resolved = await getAgent(agentId, startDir);
27
+ if (resolved?.skillsDir === null) {
28
+ console.log(`Skipping skills for agent ${agentId} (skillsDir is null).`);
29
+ } else if (resolved) {
30
+ await refreshAgentSkills(agentId, resolved, startDir, { firstInstall: true });
31
+ console.log(`Installed skills for agent ${agentId}.`);
32
+ }
26
33
  } catch (err) {
27
34
  console.warn(
28
35
  `Warning: Failed to copy skills to agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`
@@ -58,6 +58,7 @@ describe('chats utilities', () => {
58
58
  role: 'user',
59
59
  content: 'Hello',
60
60
  timestamp: new Date().toISOString(),
61
+ sessionId: undefined,
61
62
  };
62
63
 
63
64
  const msg2: CommandLogMessage = {
@@ -71,6 +72,7 @@ describe('chats utilities', () => {
71
72
  command: 'echo output',
72
73
  cwd: '/tmp',
73
74
  exitCode: 0,
75
+ sessionId: undefined,
74
76
  };
75
77
 
76
78
  const msg3: CommandLogMessage = {
@@ -84,6 +86,7 @@ describe('chats utilities', () => {
84
86
  command: 'router',
85
87
  cwd: '/tmp',
86
88
  exitCode: 0,
89
+ sessionId: undefined,
87
90
  };
88
91
 
89
92
  await appendMessage('chat1', msg1, TEST_DIR);
@@ -129,6 +132,7 @@ describe('chats utilities', () => {
129
132
  role: 'user',
130
133
  content: `Message ${i + 1}`,
131
134
  timestamp: new Date().toISOString(),
135
+ sessionId: undefined,
132
136
  }));
133
137
 
134
138
  for (const msg of msgs) {
@@ -14,6 +14,8 @@ export interface BaseMessage {
14
14
  content: string;
15
15
  timestamp: string;
16
16
  subagentId?: string;
17
+ sessionId: string | undefined;
18
+ turnId?: string;
17
19
  }
18
20
 
19
21
  export interface UserMessage extends BaseMessage {
@@ -48,6 +50,12 @@ export interface SystemMessage extends BaseMessage {
48
50
  role: 'system';
49
51
  event: 'cron' | 'policy_approved' | 'policy_rejected' | 'subagent_update' | 'router' | 'other';
50
52
  messageId?: string;
53
+ /**
54
+ * Populated on `event === 'cron'` with the CronJob id that fired. Used by
55
+ * adapters (gchat `visibility.jobs: 'header'`) to render a terse header
56
+ * instead of the agent-facing prompt.
57
+ */
58
+ jobId?: string;
51
59
  }
52
60
 
53
61
  export interface ToolMessage extends BaseMessage {
@@ -70,6 +78,7 @@ export interface SubagentStatusMessage extends BaseMessage {
70
78
  role: 'subagent_status';
71
79
  subagentId: string;
72
80
  status: 'completed' | 'failed';
81
+ turnId?: string;
73
82
  }
74
83
 
75
84
  export interface LegacyLogMessage extends BaseMessage {
@@ -1,5 +1,13 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ const PolicyDefinitionSchema = z.looseObject({
4
+ description: z.string().optional(),
5
+ command: z.string(),
6
+ args: z.array(z.string()).optional(),
7
+ allowHelp: z.boolean().optional(),
8
+ autoApprove: z.boolean().optional(),
9
+ });
10
+
3
11
  export const FallbackSchema = z.looseObject({
4
12
  commands: z
5
13
  .looseObject({
@@ -15,6 +23,7 @@ export const FallbackSchema = z.looseObject({
15
23
  });
16
24
 
17
25
  export const AgentSchema = z.looseObject({
26
+ extends: z.string().optional(),
18
27
  commands: z
19
28
  .looseObject({
20
29
  new: z.string().optional(),
@@ -27,8 +36,11 @@ export const AgentSchema = z.looseObject({
27
36
  apiUrlEnvVar: z.string().optional(),
28
37
  env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),
29
38
  subagentEnv: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),
39
+ modelShorthands: z.record(z.string(), z.string()).optional(),
30
40
  directory: z.string().optional(),
31
- skillsDir: z.string().optional(),
41
+ // `null` explicitly disables skill install/refresh for this agent.
42
+ // `undefined` (omitted) falls back to the template's value or `.agents/skills`.
43
+ skillsDir: z.string().nullable().optional(),
32
44
  fallbacks: z.array(FallbackSchema).optional(),
33
45
  files: z.string().default('./attachments').optional(),
34
46
  });
@@ -116,13 +128,16 @@ export const AgentSessionSettingsSchema = z.looseObject({
116
128
  export type AgentSessionSettings = z.infer<typeof AgentSessionSettingsSchema>;
117
129
 
118
130
  export const EnvironmentSchema = z.looseObject({
131
+ extends: z.string().optional(),
119
132
  init: z.string().optional(),
120
133
  up: z.string().optional(),
121
134
  down: z.string().optional(),
122
135
  prefix: z.string().optional(),
123
136
  envFormat: z.string().optional(),
124
137
  exportLiteTo: z.string().optional(),
138
+ baseDir: z.string().optional(),
125
139
  env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),
140
+ policies: z.record(z.string(), PolicyDefinitionSchema).optional(),
126
141
  });
127
142
 
128
143
  export type Environment = z.infer<typeof EnvironmentSchema>;
@@ -1,9 +1,20 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import { readSettings, readEnvironment, getWorkspaceRoot } from './workspace.js';
5
6
  import type { Environment } from './config.js';
6
7
 
8
+ const LITE_MARKER = 'clawmini-lite';
9
+
10
+ function sha256(content: string): string {
11
+ return createHash('sha256').update(content).digest('hex');
12
+ }
13
+
14
+ function looksLikeLiteScript(content: string): boolean {
15
+ return content.startsWith('#!') && content.includes(LITE_MARKER);
16
+ }
17
+
7
18
  export async function resolveCompiledScript(scriptName: string, metaUrl: string): Promise<string> {
8
19
  const __dirname = path.dirname(fileURLToPath(metaUrl));
9
20
  const filename = scriptName.endsWith('.mjs') ? scriptName : `${scriptName}.mjs`;
@@ -77,6 +88,63 @@ export async function writeLiteScript(outPath: string): Promise<string> {
77
88
  return finalPath;
78
89
  }
79
90
 
91
+ async function resolveLiteTargetPath(outPath: string): Promise<string> {
92
+ const isProbablyFile =
93
+ path.extname(outPath) === '.js' ||
94
+ path.extname(outPath) === '.mjs' ||
95
+ path.basename(outPath) === 'clawmini-lite';
96
+
97
+ try {
98
+ const stat = await fs.stat(outPath);
99
+ if (stat.isDirectory()) {
100
+ return path.join(outPath, 'clawmini-lite.js');
101
+ }
102
+ return outPath;
103
+ } catch {
104
+ if (isProbablyFile) return outPath;
105
+ // No extension and path doesn't yet exist — treat as a directory target.
106
+ return path.join(outPath, 'clawmini-lite.js');
107
+ }
108
+ }
109
+
110
+ // Content-hashed write that refuses to clobber an arbitrary user file at the
111
+ // export path. Returns:
112
+ // - 'written' when the file was created or updated
113
+ // - 'unchanged' when the on-disk content already matches (no mtime touch)
114
+ // - 'refused' when the existing file looks like it isn't a clawmini-lite
115
+ export async function refreshLiteAt(
116
+ outPath: string,
117
+ opts: { label?: string } = {}
118
+ ): Promise<{ status: 'written' | 'unchanged' | 'refused'; path: string }> {
119
+ const content = await getLiteScriptContent();
120
+ const finalPath = await resolveLiteTargetPath(outPath);
121
+
122
+ let existing: string | null = null;
123
+ try {
124
+ existing = await fs.readFile(finalPath, 'utf8');
125
+ } catch {
126
+ // missing — write below
127
+ }
128
+
129
+ if (existing !== null) {
130
+ if (sha256(existing) === sha256(content)) {
131
+ return { status: 'unchanged', path: finalPath };
132
+ }
133
+ if (!looksLikeLiteScript(existing)) {
134
+ const label = opts.label ? ` (${opts.label})` : '';
135
+ console.warn(
136
+ `Refusing to overwrite ${finalPath}${label}: existing file is not a clawmini-lite script`
137
+ );
138
+ return { status: 'refused', path: finalPath };
139
+ }
140
+ }
141
+
142
+ const dir = path.dirname(finalPath);
143
+ await fs.mkdir(dir, { recursive: true });
144
+ await fs.writeFile(finalPath, content, { mode: 0o755 });
145
+ return { status: 'written', path: finalPath };
146
+ }
147
+
80
148
  export async function exportLiteToEnvironment(
81
149
  envName: string,
82
150
  envConfig: Environment,
@@ -98,8 +166,14 @@ export async function exportLiteToEnvironment(
98
166
  }
99
167
 
100
168
  try {
101
- const writtenPath = await writeLiteScript(finalExportPath);
102
- console.log(`Successfully exported clawmini-lite to ${writtenPath} (Environment: ${envName})`);
169
+ const result = await refreshLiteAt(finalExportPath, { label: `Environment: ${envName}` });
170
+ if (result.status === 'written') {
171
+ console.log(
172
+ `Successfully exported clawmini-lite to ${result.path} (Environment: ${envName})`
173
+ );
174
+ } else if (result.status === 'refused') {
175
+ return false;
176
+ }
103
177
  return true;
104
178
  } catch (err) {
105
179
  console.error(
@@ -6,10 +6,35 @@ export interface PolicyDefinition {
6
6
  autoApprove?: boolean;
7
7
  }
8
8
 
9
+ // Resolved policy config: built-ins merged in, `false` overrides stripped.
10
+ // This is what every consumer should see.
9
11
  export interface PolicyConfig {
10
12
  policies: Record<string, PolicyDefinition>;
11
13
  }
12
14
 
15
+ // On-disk shape: `false` opts a built-in out, a definition opts in / overrides.
16
+ export interface PolicyConfigFile {
17
+ policies: Record<string, PolicyDefinition | false>;
18
+ }
19
+
20
+ export const BUILTIN_POLICY_SCRIPTS_DIR = '.clawmini/policy-scripts';
21
+
22
+ export const BUILTIN_POLICIES: Record<string, PolicyDefinition> = {
23
+ 'manage-policies': {
24
+ description:
25
+ 'Add, update, or remove clawmini policies (subcommands: add, update, remove). Reads are unrestricted via `requests show`.',
26
+ command: `./${BUILTIN_POLICY_SCRIPTS_DIR}/manage-policies.js`,
27
+ allowHelp: true,
28
+ autoApprove: false,
29
+ },
30
+ 'run-host': {
31
+ description: 'Run an arbitrary shell command on the host via `sh -c`',
32
+ command: `./${BUILTIN_POLICY_SCRIPTS_DIR}/run-host.js`,
33
+ allowHelp: true,
34
+ autoApprove: false,
35
+ },
36
+ };
37
+
13
38
  export type RequestState = 'Pending' | 'Approved' | 'Rejected';
14
39
 
15
40
  export interface PolicyRequest {
@@ -17,6 +42,7 @@ export interface PolicyRequest {
17
42
  commandName: string;
18
43
  args: string[];
19
44
  fileMappings: Record<string, string>;
45
+ cwd?: string;
20
46
  state: RequestState;
21
47
  createdAt: number;
22
48
  rejectionReason?: string;