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
@@ -1,10 +1,24 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import type { RouterState } from './types.js';
3
3
  import { RequestStore } from '../request-store.js';
4
- import { readPolicies, getWorkspaceRoot } from '../../shared/workspace.js';
5
- import { executeRequest } from '../policy-utils.js';
4
+ import { readChatSettings, readPoliciesForPath, getWorkspaceRoot } from '../../shared/workspace.js';
5
+ import { resolveAgentDir } from '../api/router-utils.js';
6
+ import { executeRequest, resolveRequestCwd, truncateLargeOutput } from '../policy-utils.js';
6
7
  import { appendMessage } from '../chats.js';
7
8
  import type { SystemMessage } from '../../shared/chats.js';
9
+ import type { PolicyRequest } from '../../shared/policies.js';
10
+ import { executeDirectMessage } from '../message.js';
11
+
12
+ // Resolve which session the approval/rejection should be replayed on. The
13
+ // request may have been created in an earlier session (session-timeout, /new),
14
+ // so we always consult the chat's *current* session for that agent/subagent.
15
+ async function resolveTargetSessionId(chatId: string, req: PolicyRequest): Promise<string> {
16
+ const chatSettings = await readChatSettings(chatId);
17
+ if (req.subagentId) {
18
+ return chatSettings?.subagents?.[req.subagentId]?.sessionId ?? 'default';
19
+ }
20
+ return chatSettings?.sessions?.[req.agentId] ?? 'default';
21
+ }
8
22
 
9
23
  async function loadAndValidateRequest(id: string, state: RouterState) {
10
24
  const store = new RequestStore(getWorkspaceRoot());
@@ -47,39 +61,68 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
47
61
  if (error) return error;
48
62
  if (!req || !store) return state; // Should not happen if error is undefined
49
63
 
50
- const config = await readPolicies();
64
+ const workspaceRoot = getWorkspaceRoot();
65
+ const agentDir = await resolveAgentDir(req.agentId, workspaceRoot);
66
+ const config = await readPoliciesForPath(agentDir, workspaceRoot);
51
67
  const policy = config?.policies?.[req.commandName];
52
68
  if (!policy) {
53
69
  return { ...state, message: '', reply: `Policy not found: ${req.commandName}` };
54
70
  }
55
71
 
56
- req.state = 'Approved';
72
+ const hostCwd = await resolveRequestCwd(req.cwd, state.agentId, workspaceRoot);
57
73
 
58
- const { stdout, stderr, exitCode } = await executeRequest(req, policy, getWorkspaceRoot());
74
+ const result = await executeRequest(req, policy, hostCwd);
75
+ const { exitCode } = result;
76
+ const { stdout, stderr } = await truncateLargeOutput(
77
+ result.stdout,
78
+ result.stderr,
79
+ req.id,
80
+ state.agentId
81
+ );
59
82
 
60
- req.executionResult = { stdout, stderr, exitCode };
61
- await store.save(req);
83
+ await store.delete(req.id);
62
84
 
63
85
  const agentMessage = `Request ${id} approved.\n\n${wrapInHtml('stdout', stdout)}\n\n${wrapInHtml('stderr', stderr)}\n\nExit Code: ${exitCode}`;
64
86
 
65
- const logMsg: SystemMessage = {
87
+ const targetSessionId = await resolveTargetSessionId(state.chatId, req);
88
+
89
+ const userNotificationMsg: SystemMessage = {
66
90
  id: randomUUID(),
67
91
  messageId: state.messageId,
68
92
  role: 'system',
69
93
  event: 'policy_approved',
70
- displayRole: 'user',
71
- content: agentMessage,
94
+ displayRole: 'agent',
95
+ content: `Request ${id} (\`${req.commandName}\`) approved.`,
72
96
  timestamp: new Date().toISOString(),
73
- ...(req.subagentId ? { subagentId: req.subagentId } : {}),
97
+ // Explicitly omitted subagentId to show in main chat
98
+ sessionId: state.sessionId,
74
99
  };
75
100
 
76
- await appendMessage(state.chatId, logMsg);
101
+ await appendMessage(state.chatId, userNotificationMsg);
102
+
103
+ await executeDirectMessage(
104
+ state.chatId,
105
+ {
106
+ messageId: randomUUID(),
107
+ message: agentMessage,
108
+ chatId: state.chatId,
109
+ agentId: req.agentId,
110
+ sessionId: targetSessionId,
111
+ ...(req.subagentId ? { subagentId: req.subagentId } : {}),
112
+ env: state.env || {},
113
+ },
114
+ undefined,
115
+ getWorkspaceRoot(),
116
+ true, // noWait
117
+ agentMessage,
118
+ req.subagentId,
119
+ 'policy_approved',
120
+ 'user'
121
+ );
77
122
 
78
123
  return {
79
124
  ...state,
80
- message: agentMessage,
81
- reply: `Approved request, running ${req.commandName}`,
82
- ...(req.subagentId ? { subagentId: req.subagentId } : {}),
125
+ message: '', // Prevents further router processing or duplicate user message logs
83
126
  };
84
127
  }
85
128
 
@@ -92,22 +135,11 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
92
135
  if (error) return error;
93
136
  if (!req || !store) return state; // Should not happen if error is undefined
94
137
 
95
- req.state = 'Rejected';
96
- req.rejectionReason = reason;
97
- await store.save(req);
138
+ await store.delete(req.id);
98
139
 
99
140
  const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
100
141
 
101
- const logMsg: SystemMessage = {
102
- id: randomUUID(),
103
- messageId: state.messageId,
104
- role: 'system',
105
- event: 'policy_rejected',
106
- displayRole: 'user',
107
- content: agentMessage,
108
- timestamp: new Date().toISOString(),
109
- ...(req.subagentId ? { subagentId: req.subagentId } : {}),
110
- };
142
+ const targetSessionId = await resolveTargetSessionId(state.chatId, req);
111
143
 
112
144
  const userNotificationMsg: SystemMessage = {
113
145
  id: randomUUID(),
@@ -115,18 +147,37 @@ export async function slashPolicies(state: RouterState): Promise<RouterState> {
115
147
  role: 'system',
116
148
  event: 'policy_rejected',
117
149
  displayRole: 'agent',
118
- content: agentMessage,
150
+ content: `Request ${id} (\`${req.commandName}\`) rejected. Reason: ${reason}`,
119
151
  timestamp: new Date().toISOString(),
120
- ...(req.subagentId ? { subagentId: req.subagentId } : {}),
152
+ // Explicitly omitted subagentId to show in main chat
153
+ sessionId: state.sessionId,
121
154
  };
122
155
 
123
- await appendMessage(state.chatId, logMsg);
124
156
  await appendMessage(state.chatId, userNotificationMsg);
125
157
 
158
+ await executeDirectMessage(
159
+ state.chatId,
160
+ {
161
+ messageId: randomUUID(),
162
+ message: agentMessage,
163
+ chatId: state.chatId,
164
+ agentId: req.agentId,
165
+ sessionId: targetSessionId,
166
+ ...(req.subagentId ? { subagentId: req.subagentId } : {}),
167
+ env: state.env || {},
168
+ },
169
+ undefined,
170
+ getWorkspaceRoot(),
171
+ true, // noWait
172
+ agentMessage,
173
+ req.subagentId,
174
+ 'policy_rejected',
175
+ 'user'
176
+ );
177
+
126
178
  return {
127
179
  ...state,
128
- message: agentMessage,
129
- ...(req.subagentId ? { subagentId: req.subagentId } : {}),
180
+ message: '', // Prevents further router processing or duplicate user message logs
130
181
  };
131
182
  }
132
183
 
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { slashRestart } from './slash-restart.js';
3
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
4
+ import type { RouterState } from './types.js';
5
+
6
+ vi.mock('../../cli/supervisor-control.js');
7
+
8
+ const baseState: RouterState = {
9
+ message: '',
10
+ messageId: 'mock-msg-id',
11
+ chatId: 'chat-1',
12
+ };
13
+
14
+ describe('slashRestart', () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
18
+ });
19
+
20
+ it('passes through unrelated messages', async () => {
21
+ const state = { ...baseState, message: 'hello' };
22
+ const result = await slashRestart(state);
23
+ expect(result).toEqual(state);
24
+ expect(sendControlRequest).not.toHaveBeenCalled();
25
+ });
26
+
27
+ it('forwards chatId/messageId to the supervisor for /restart', async () => {
28
+ const state = { ...baseState, message: '/restart' };
29
+ const result = await slashRestart(state);
30
+ expect(result.action).toBe('stop');
31
+ expect(result.reply).toBe('Restarting clawmini...');
32
+ expect(result.message).toBe('');
33
+ expect(sendControlRequest).toHaveBeenCalledWith({
34
+ action: 'restart',
35
+ chatId: 'chat-1',
36
+ messageId: 'mock-msg-id',
37
+ });
38
+ });
39
+
40
+ it('matches /restart with trailing whitespace', async () => {
41
+ const state = { ...baseState, message: '/restart ' };
42
+ const result = await slashRestart(state);
43
+ expect(result.action).toBe('stop');
44
+ expect(sendControlRequest).toHaveBeenCalled();
45
+ });
46
+
47
+ it('does not match /restartfoo', async () => {
48
+ const state = { ...baseState, message: '/restartfoo' };
49
+ const result = await slashRestart(state);
50
+ expect(result).toEqual(state);
51
+ expect(sendControlRequest).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('returns an error reply when the supervisor request rejects', async () => {
55
+ vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
56
+ const state = { ...baseState, message: '/restart' };
57
+ const result = await slashRestart(state);
58
+ expect(result.action).toBe('stop');
59
+ expect(result.reply).toBe('Could not reach supervisor: socket missing.');
60
+ });
61
+
62
+ it('returns an error reply when the supervisor reports !ok', async () => {
63
+ vi.mocked(sendControlRequest).mockResolvedValueOnce({ ok: false, error: 'busy' });
64
+ const state = { ...baseState, message: '/restart' };
65
+ const result = await slashRestart(state);
66
+ expect(result.action).toBe('stop');
67
+ expect(result.reply).toBe('Restart aborted: busy.');
68
+ });
69
+ });
@@ -0,0 +1,36 @@
1
+ import type { RouterState } from './types.js';
2
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
3
+
4
+ export async function slashRestart(state: RouterState): Promise<RouterState> {
5
+ if (!/^\/restart(\s|$)/.test(state.message)) return state;
6
+
7
+ // Gated to user messages by USER_ROUTERS in resolveRouters — an agent
8
+ // tool output that happens to start with "/restart" never reaches us.
9
+ // The supervisor enqueues the post-restart "Clawmini restarted vX.Y.Z"
10
+ // SystemMessage on its side so the on-disk record reflects whether the
11
+ // restart was actually scheduled. We just plumb chatId + messageId
12
+ // through and surface any control-channel error to the user.
13
+ let res;
14
+ try {
15
+ res = await sendControlRequest({
16
+ action: 'restart',
17
+ chatId: state.chatId,
18
+ messageId: state.messageId,
19
+ });
20
+ } catch (err) {
21
+ return stop(
22
+ state,
23
+ `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
24
+ );
25
+ }
26
+
27
+ if (!res.ok) {
28
+ return stop(state, `Restart aborted: ${res.error ?? 'unknown error'}.`);
29
+ }
30
+
31
+ return stop(state, 'Restarting clawmini...');
32
+ }
33
+
34
+ function stop(state: RouterState, reply: string): RouterState {
35
+ return { ...state, message: '', action: 'stop', reply };
36
+ }
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { slashShutdown } from './slash-shutdown.js';
3
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
4
+ import type { RouterState } from './types.js';
5
+
6
+ vi.mock('../../cli/supervisor-control.js');
7
+
8
+ const baseState: RouterState = {
9
+ message: '',
10
+ messageId: 'mock-msg-id',
11
+ chatId: 'chat-1',
12
+ };
13
+
14
+ describe('slashShutdown', () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
18
+ });
19
+
20
+ it('passes through unrelated messages', async () => {
21
+ const state = { ...baseState, message: 'hello' };
22
+ const result = await slashShutdown(state);
23
+ expect(result).toEqual(state);
24
+ expect(sendControlRequest).not.toHaveBeenCalled();
25
+ });
26
+
27
+ it('triggers shutdown for /shutdown', async () => {
28
+ const state = { ...baseState, message: '/shutdown' };
29
+ const result = await slashShutdown(state);
30
+ expect(result.action).toBe('stop');
31
+ expect(result.reply).toBe('Shutting down clawmini supervisor...');
32
+ expect(result.message).toBe('');
33
+ expect(sendControlRequest).toHaveBeenCalledWith({ action: 'shutdown' });
34
+ });
35
+
36
+ it('does not match /shutdownz', async () => {
37
+ const state = { ...baseState, message: '/shutdownz' };
38
+ const result = await slashShutdown(state);
39
+ expect(result).toEqual(state);
40
+ expect(sendControlRequest).not.toHaveBeenCalled();
41
+ });
42
+
43
+ it('returns an error reply when the supervisor request rejects', async () => {
44
+ vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
45
+ const state = { ...baseState, message: '/shutdown' };
46
+ const result = await slashShutdown(state);
47
+ expect(result.action).toBe('stop');
48
+ expect(result.reply).toBe('Could not reach supervisor: socket missing.');
49
+ });
50
+ });
@@ -0,0 +1,28 @@
1
+ import type { RouterState } from './types.js';
2
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
3
+
4
+ export async function slashShutdown(state: RouterState): Promise<RouterState> {
5
+ if (!/^\/shutdown(\s|$)/.test(state.message)) return state;
6
+
7
+ // Gated to user messages by USER_ROUTERS in resolveRouters — agents cannot
8
+ // trigger this even via tool output that happens to start with /shutdown.
9
+ let res;
10
+ try {
11
+ res = await sendControlRequest({ action: 'shutdown' });
12
+ } catch (err) {
13
+ return stop(
14
+ state,
15
+ `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
16
+ );
17
+ }
18
+
19
+ if (!res.ok) {
20
+ return stop(state, `Shutdown aborted: ${res.error ?? 'unknown error'}.`);
21
+ }
22
+
23
+ return stop(state, 'Shutting down clawmini supervisor...');
24
+ }
25
+
26
+ function stop(state: RouterState, reply: string): RouterState {
27
+ return { ...state, message: '', action: 'stop', reply };
28
+ }
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { slashUpgrade } from './slash-upgrade.js';
3
+ import { detectInstall } from '../../cli/install-detection.js';
4
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
5
+ import type { RouterState } from './types.js';
6
+
7
+ vi.mock('../../cli/install-detection.js');
8
+ vi.mock('../../cli/supervisor-control.js');
9
+
10
+ const baseState: RouterState = {
11
+ message: '',
12
+ messageId: 'mock-msg-id',
13
+ chatId: 'chat-1',
14
+ };
15
+
16
+ const npmInstall = {
17
+ isNpmGlobal: true,
18
+ entryRealPath: '/usr/local/lib/node_modules/clawmini/dist/cli.js',
19
+ npmRootRealPath: '/usr/local/lib/node_modules',
20
+ };
21
+
22
+ describe('slashUpgrade', () => {
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ vi.mocked(sendControlRequest).mockResolvedValue({ ok: true });
26
+ vi.mocked(detectInstall).mockReturnValue(npmInstall);
27
+ });
28
+
29
+ it('passes through unrelated messages', async () => {
30
+ const state = { ...baseState, message: 'hello' };
31
+ const result = await slashUpgrade(state);
32
+ expect(result).toEqual(state);
33
+ expect(sendControlRequest).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it('refuses upgrade when not installed via npm (e.g. npm link)', async () => {
37
+ vi.mocked(detectInstall).mockReturnValueOnce({
38
+ isNpmGlobal: false,
39
+ entryRealPath: '/Users/me/projects/clawmini/dist/cli/index.mjs',
40
+ npmRootRealPath: '/usr/local/lib/node_modules',
41
+ });
42
+ const state = { ...baseState, message: '/upgrade latest' };
43
+ const result = await slashUpgrade(state);
44
+ expect(result.action).toBe('stop');
45
+ expect(result.reply).toContain('not installed via');
46
+ expect(result.reply).toContain('/Users/me/projects/clawmini/dist/cli/index.mjs');
47
+ expect(sendControlRequest).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('shows usage hint for bare /upgrade (does NOT trigger an install)', async () => {
51
+ const state = { ...baseState, message: '/upgrade' };
52
+ const result = await slashUpgrade(state);
53
+ expect(result.action).toBe('stop');
54
+ expect(result.reply).toContain('/upgrade requires an explicit target');
55
+ expect(result.reply).toContain('/upgrade latest');
56
+ expect(sendControlRequest).not.toHaveBeenCalled();
57
+ });
58
+
59
+ it('forwards version, chatId, and messageId to the supervisor', async () => {
60
+ const state = { ...baseState, message: '/upgrade 1.2.3' };
61
+ const result = await slashUpgrade(state);
62
+ expect(result.action).toBe('stop');
63
+ expect(result.reply).toBe('Upgrading clawmini to 1.2.3... services will restart shortly.');
64
+ expect(sendControlRequest).toHaveBeenCalledWith({
65
+ action: 'upgrade',
66
+ version: '1.2.3',
67
+ chatId: 'chat-1',
68
+ messageId: 'mock-msg-id',
69
+ });
70
+ });
71
+
72
+ it('accepts the literal "latest"', async () => {
73
+ const state = { ...baseState, message: '/upgrade latest' };
74
+ const result = await slashUpgrade(state);
75
+ expect(result.action).toBe('stop');
76
+ expect(result.reply).toContain('Upgrading clawmini to latest');
77
+ expect(sendControlRequest).toHaveBeenCalledWith({
78
+ action: 'upgrade',
79
+ version: 'latest',
80
+ chatId: 'chat-1',
81
+ messageId: 'mock-msg-id',
82
+ });
83
+ });
84
+
85
+ it('rejects multi-token arguments to avoid smuggling extra npm flags', async () => {
86
+ const state = { ...baseState, message: '/upgrade 1.2.3 --registry=evil' };
87
+ const result = await slashUpgrade(state);
88
+ expect(result.action).toBe('stop');
89
+ expect(result.reply).toBe('Usage: /upgrade <version>');
90
+ expect(sendControlRequest).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it('rejects shell-meta versions even as a single token', async () => {
94
+ const state = { ...baseState, message: '/upgrade $(rm)' };
95
+ const result = await slashUpgrade(state);
96
+ expect(result.action).toBe('stop');
97
+ expect(result.reply).toContain('Invalid version');
98
+ expect(sendControlRequest).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it('returns an error reply when the supervisor reports !ok', async () => {
102
+ vi.mocked(sendControlRequest).mockResolvedValueOnce({ ok: false, error: 'busy' });
103
+ const state = { ...baseState, message: '/upgrade latest' };
104
+ const result = await slashUpgrade(state);
105
+ expect(result.action).toBe('stop');
106
+ expect(result.reply).toBe('Upgrade aborted: busy.');
107
+ });
108
+
109
+ it('returns an error reply when the supervisor request rejects', async () => {
110
+ vi.mocked(sendControlRequest).mockRejectedValueOnce(new Error('socket missing'));
111
+ const state = { ...baseState, message: '/upgrade latest' };
112
+ const result = await slashUpgrade(state);
113
+ expect(result.action).toBe('stop');
114
+ expect(result.reply).toBe('Could not reach supervisor: socket missing.');
115
+ });
116
+ });
@@ -0,0 +1,76 @@
1
+ import type { RouterState } from './types.js';
2
+ import { detectInstall } from '../../cli/install-detection.js';
3
+ import { sendControlRequest } from '../../cli/supervisor-control.js';
4
+ import { isAcceptableVersion } from '../../cli/supervisor-actions.js';
5
+ import { getClawminiVersion } from '../../shared/version.js';
6
+
7
+ export async function slashUpgrade(state: RouterState): Promise<RouterState> {
8
+ const trimmed = state.message.trim();
9
+ if (!/^\/upgrade(\s|$)/.test(trimmed)) return state;
10
+
11
+ // Gated to user messages by USER_ROUTERS in resolveRouters.
12
+ const info = detectInstall();
13
+ if (!info.isNpmGlobal) {
14
+ return stop(
15
+ state,
16
+ `Cannot upgrade: clawmini is not installed via \`npm install -g\` ` +
17
+ `(running from ${info.entryRealPath}). Skipping.`
18
+ );
19
+ }
20
+
21
+ const rest = trimmed.slice('/upgrade'.length).trim();
22
+
23
+ // Bare `/upgrade` is informational. Requiring an explicit version means a
24
+ // misclick (or a malicious tool output that somehow reached this router)
25
+ // can't silently install whatever the npm registry currently calls
26
+ // `latest`. The user has to opt in to the version they want.
27
+ if (rest === '') {
28
+ return stop(
29
+ state,
30
+ [
31
+ `Currently running clawmini v${getClawminiVersion()}.`,
32
+ '/upgrade requires an explicit target:',
33
+ ' /upgrade latest — install whatever npm reports as the latest tag',
34
+ ' /upgrade <version> — install a specific version (e.g. /upgrade 0.0.7)',
35
+ ].join('\n')
36
+ );
37
+ }
38
+
39
+ // Single token only — extra args could be smuggled into the npm command
40
+ // line otherwise (npm install -g clawmini@<rest> via shell).
41
+ if (!/^\S+$/.test(rest)) {
42
+ return stop(state, 'Usage: /upgrade <version>');
43
+ }
44
+
45
+ const version = rest;
46
+ if (!isAcceptableVersion(version)) {
47
+ return stop(state, `Invalid version: ${version}`);
48
+ }
49
+
50
+ // The supervisor enqueues the post-upgrade reply on its side based on the
51
+ // outcome of `npm install -g`, so we don't pre-queue anything here.
52
+ let res;
53
+ try {
54
+ res = await sendControlRequest({
55
+ action: 'upgrade',
56
+ version,
57
+ chatId: state.chatId,
58
+ messageId: state.messageId,
59
+ });
60
+ } catch (err) {
61
+ return stop(
62
+ state,
63
+ `Could not reach supervisor: ${err instanceof Error ? err.message : String(err)}.`
64
+ );
65
+ }
66
+
67
+ if (!res.ok) {
68
+ return stop(state, `Upgrade aborted: ${res.error ?? 'unknown error'}.`);
69
+ }
70
+
71
+ return stop(state, `Upgrading clawmini to ${version}... services will restart shortly.`);
72
+ }
73
+
74
+ function stop(state: RouterState, reply: string): RouterState {
75
+ return { ...state, message: '', action: 'stop', reply };
76
+ }
@@ -11,8 +11,15 @@ export interface RouterState {
11
11
  env?: Record<string, string>;
12
12
  reply?: string;
13
13
  action?: 'stop' | 'interrupt' | 'continue';
14
+ externalRef?: string;
14
15
  jobs?: {
15
16
  add?: CronJob[];
16
17
  remove?: string[];
17
18
  };
19
+ /**
20
+ * CronJob id that fired this turn, when the router state was seeded by
21
+ * `cron.executeJob`. Threaded through to the `SystemMessage` so adapters
22
+ * can render a terse header instead of the prompt text.
23
+ */
24
+ jobId?: string;
18
25
  }
@@ -5,6 +5,10 @@ import { slashCommand } from './routers/slash-command.js';
5
5
  import { slashStop } from './routers/slash-stop.js';
6
6
  import { slashInterrupt } from './routers/slash-interrupt.js';
7
7
  import { slashPolicies } from './routers/slash-policies.js';
8
+ import { slashModel } from './routers/slash-model.js';
9
+ import { slashRestart } from './routers/slash-restart.js';
10
+ import { slashShutdown } from './routers/slash-shutdown.js';
11
+ import { slashUpgrade } from './routers/slash-upgrade.js';
8
12
  import { createSessionTimeoutRouter } from './routers/session-timeout.js';
9
13
  import type { RouterConfig } from '../shared/config.js';
10
14
 
@@ -16,6 +20,10 @@ export const USER_ROUTERS: RouterConfig[] = [
16
20
  '@clawmini/slash-stop',
17
21
  '@clawmini/slash-interrupt',
18
22
  '@clawmini/slash-policies',
23
+ '@clawmini/slash-model',
24
+ '@clawmini/slash-restart',
25
+ '@clawmini/slash-shutdown',
26
+ '@clawmini/slash-upgrade',
19
27
  ];
20
28
 
21
29
  export function resolveRouters(
@@ -87,6 +95,14 @@ export async function executeRouterPipeline(
87
95
  state = slashInterrupt(state);
88
96
  } else if (router === '@clawmini/slash-policies') {
89
97
  state = await slashPolicies(state);
98
+ } else if (router === '@clawmini/slash-model') {
99
+ state = await slashModel(state);
100
+ } else if (router === '@clawmini/slash-restart') {
101
+ state = await slashRestart(state);
102
+ } else if (router === '@clawmini/slash-shutdown') {
103
+ state = await slashShutdown(state);
104
+ } else if (router === '@clawmini/slash-upgrade') {
105
+ state = await slashUpgrade(state);
90
106
  } else if (router === '@clawmini/session-timeout') {
91
107
  state = createSessionTimeoutRouter(config)(state);
92
108
  } else {
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { prependBlockquote } from './blockquote.js';
3
+
4
+ describe('prependBlockquote', () => {
5
+ it('prefixes each line of the quote with "> " and separates body with a blank line', () => {
6
+ expect(prependBlockquote('hello\nworld', 'reply')).toBe('> hello\n> world\n\nreply');
7
+ });
8
+
9
+ it('handles single-line quotes', () => {
10
+ expect(prependBlockquote('hello', 'reply')).toBe('> hello\n\nreply');
11
+ });
12
+
13
+ it('handles multi-line replies', () => {
14
+ expect(prependBlockquote('q', 'a\nb')).toBe('> q\n\na\nb');
15
+ });
16
+
17
+ it('trims surrounding whitespace from quoted text and body', () => {
18
+ expect(prependBlockquote(' hello\nworld \n', '\n\nreply\n')).toBe(
19
+ '> hello\n> world\n\nreply'
20
+ );
21
+ });
22
+
23
+ it('renders an attribution line when sender is provided', () => {
24
+ expect(prependBlockquote('hello\nworld', 'reply', 'Tom')).toBe(
25
+ '> **Tom said:**\n> hello\n> world\n\nreply'
26
+ );
27
+ });
28
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Format `quoted` as a markdown blockquote prefixed before `body`.
3
+ *
4
+ * Each line of `quoted` is prefixed with `> `, and a blank line separates the
5
+ * quote from the body — CommonMark requires the blank line to terminate the
6
+ * blockquote, otherwise the body is lazily folded into it. If `sender` is
7
+ * provided, an attribution line (`> **{sender} said:**`) is rendered as the
8
+ * first line of the quote. Both inputs are trimmed.
9
+ */
10
+ export function prependBlockquote(quoted: string, body: string, sender?: string): string {
11
+ const trimmedBody = body.trim();
12
+ const lines = quoted
13
+ .trim()
14
+ .split('\n')
15
+ .map((line) => `> ${line}`);
16
+ if (sender) {
17
+ lines.unshift(`> **${sender} said:**`);
18
+ }
19
+ return `${lines.join('\n')}\n\n${trimmedBody}`;
20
+ }