clawmini 0.0.1

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 (469) hide show
  1. package/.gemini/settings.json +46 -0
  2. package/.prettierrc +7 -0
  3. package/GEMINI.md +11 -0
  4. package/README.md +137 -0
  5. package/dist/adapter-discord/index.d.mts +5 -0
  6. package/dist/adapter-discord/index.d.mts.map +1 -0
  7. package/dist/adapter-discord/index.mjs +456 -0
  8. package/dist/adapter-discord/index.mjs.map +1 -0
  9. package/dist/chats-DKgTeU7i.mjs +91 -0
  10. package/dist/chats-DKgTeU7i.mjs.map +1 -0
  11. package/dist/chats-Zd_HXDHx.mjs +29 -0
  12. package/dist/chats-Zd_HXDHx.mjs.map +1 -0
  13. package/dist/cli/index.d.mts +1 -0
  14. package/dist/cli/index.mjs +850 -0
  15. package/dist/cli/index.mjs.map +1 -0
  16. package/dist/cli/lite.d.mts +1 -0
  17. package/dist/cli/lite.mjs +4434 -0
  18. package/dist/cli/lite.mjs.map +1 -0
  19. package/dist/daemon/index.d.mts +5 -0
  20. package/dist/daemon/index.d.mts.map +1 -0
  21. package/dist/daemon/index.mjs +1222 -0
  22. package/dist/daemon/index.mjs.map +1 -0
  23. package/dist/fetch-BjZVyU3Z.mjs +37 -0
  24. package/dist/fetch-BjZVyU3Z.mjs.map +1 -0
  25. package/dist/fs-B5wW0oaH.mjs +14 -0
  26. package/dist/fs-B5wW0oaH.mjs.map +1 -0
  27. package/dist/lite-Dl7WXyaH.mjs +80 -0
  28. package/dist/lite-Dl7WXyaH.mjs.map +1 -0
  29. package/dist/rolldown-runtime-95iHPtFO.mjs +18 -0
  30. package/dist/web/_app/env.js +1 -0
  31. package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +1 -0
  32. package/dist/web/_app/immutable/chunks/B5abRDXp.js +1 -0
  33. package/dist/web/_app/immutable/chunks/B8yYFADm.js +1 -0
  34. package/dist/web/_app/immutable/chunks/BPy8HLo7.js +5 -0
  35. package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
  36. package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +2 -0
  37. package/dist/web/_app/immutable/chunks/C3k55nDF.js +1 -0
  38. package/dist/web/_app/immutable/chunks/COekwvP2.js +1 -0
  39. package/dist/web/_app/immutable/chunks/CSvS_NwK.js +1 -0
  40. package/dist/web/_app/immutable/chunks/CpaGRn9L.js +1 -0
  41. package/dist/web/_app/immutable/chunks/CyNaE55B.js +1 -0
  42. package/dist/web/_app/immutable/chunks/DG5RZBw-.js +2 -0
  43. package/dist/web/_app/immutable/chunks/Dc-UOHw9.js +1 -0
  44. package/dist/web/_app/immutable/chunks/DcrmIfTj.js +1 -0
  45. package/dist/web/_app/immutable/chunks/ZkLyk0mE.js +1 -0
  46. package/dist/web/_app/immutable/entry/app.B-vZe7PN.js +2 -0
  47. package/dist/web/_app/immutable/entry/start.oP1AgKhs.js +1 -0
  48. package/dist/web/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
  49. package/dist/web/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
  50. package/dist/web/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
  51. package/dist/web/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
  52. package/dist/web/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
  53. package/dist/web/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
  54. package/dist/web/_app/version.json +1 -0
  55. package/dist/web/index.html +37 -0
  56. package/dist/web/robots.txt +3 -0
  57. package/dist/workspace-CSgfo_2J.mjs +383 -0
  58. package/dist/workspace-CSgfo_2J.mjs.map +1 -0
  59. package/docs/01_chats/development_log.md +36 -0
  60. package/docs/01_chats/notes.md +27 -0
  61. package/docs/01_chats/prd.md +47 -0
  62. package/docs/01_chats/questions.md +19 -0
  63. package/docs/01_chats/tickets.md +67 -0
  64. package/docs/02_sessions/development_log.md +79 -0
  65. package/docs/02_sessions/notes.md +40 -0
  66. package/docs/02_sessions/prd.md +75 -0
  67. package/docs/02_sessions/questions.md +7 -0
  68. package/docs/02_sessions/tickets.md +68 -0
  69. package/docs/03_web_interface/development_log.md +60 -0
  70. package/docs/03_web_interface/notes.md +29 -0
  71. package/docs/03_web_interface/prd.md +42 -0
  72. package/docs/03_web_interface/questions.md +8 -0
  73. package/docs/03_web_interface/tickets.md +59 -0
  74. package/docs/04_agents/development_log.md +54 -0
  75. package/docs/04_agents/notes.md +45 -0
  76. package/docs/04_agents/prd.md +47 -0
  77. package/docs/04_agents/questions.md +13 -0
  78. package/docs/04_agents/tickets.md +107 -0
  79. package/docs/05_routers/development_log.md +13 -0
  80. package/docs/05_routers/notes.md +40 -0
  81. package/docs/05_routers/prd.md +55 -0
  82. package/docs/05_routers/questions.md +21 -0
  83. package/docs/05_routers/tickets.md +109 -0
  84. package/docs/06_agent_templates/development_log.md +38 -0
  85. package/docs/06_agent_templates/notes.md +25 -0
  86. package/docs/06_agent_templates/prd.md +34 -0
  87. package/docs/06_agent_templates/questions.md +11 -0
  88. package/docs/06_agent_templates/tickets.md +49 -0
  89. package/docs/06_cron/development_log.md +51 -0
  90. package/docs/06_cron/notes.md +14 -0
  91. package/docs/06_cron/prd.md +92 -0
  92. package/docs/06_cron/questions.md +15 -0
  93. package/docs/06_cron/tickets.md +75 -0
  94. package/docs/07_web_chat_ux/development_log.md +30 -0
  95. package/docs/07_web_chat_ux/notes.md +25 -0
  96. package/docs/07_web_chat_ux/prd.md +46 -0
  97. package/docs/07_web_chat_ux/questions.md +7 -0
  98. package/docs/07_web_chat_ux/tickets.md +48 -0
  99. package/docs/08_agent_api/development_log.md +52 -0
  100. package/docs/08_agent_api/notes.md +31 -0
  101. package/docs/08_agent_api/prd.md +56 -0
  102. package/docs/08_agent_api/questions.md +14 -0
  103. package/docs/08_agent_api/tickets.md +104 -0
  104. package/docs/09_agent_fallbacks/development_log.md +52 -0
  105. package/docs/09_agent_fallbacks/notes.md +40 -0
  106. package/docs/09_agent_fallbacks/prd.md +55 -0
  107. package/docs/09_agent_fallbacks/questions.md +10 -0
  108. package/docs/09_agent_fallbacks/tickets.md +88 -0
  109. package/docs/09_discord_adapter/development_log.md +95 -0
  110. package/docs/09_discord_adapter/notes.md +18 -0
  111. package/docs/09_discord_adapter/prd.md +57 -0
  112. package/docs/09_discord_adapter/questions.md +16 -0
  113. package/docs/09_discord_adapter/tickets.md +116 -0
  114. package/docs/10_file_attachments/development_log.md +55 -0
  115. package/docs/10_file_attachments/notes.md +59 -0
  116. package/docs/10_file_attachments/prd.md +73 -0
  117. package/docs/10_file_attachments/questions.md +15 -0
  118. package/docs/10_file_attachments/tickets.md +88 -0
  119. package/docs/11_message_verbosity/development_log.md +43 -0
  120. package/docs/11_message_verbosity/notes.md +26 -0
  121. package/docs/11_message_verbosity/prd.md +44 -0
  122. package/docs/11_message_verbosity/questions.md +8 -0
  123. package/docs/11_message_verbosity/tickets.md +33 -0
  124. package/docs/12_environments/development_log.md +43 -0
  125. package/docs/12_environments/notes.md +45 -0
  126. package/docs/12_environments/prd.md +113 -0
  127. package/docs/12_environments/questions.md +17 -0
  128. package/docs/12_environments/tickets.md +87 -0
  129. package/docs/12_setup_flow_improvements/development_log.md +40 -0
  130. package/docs/12_setup_flow_improvements/notes.md +34 -0
  131. package/docs/12_setup_flow_improvements/prd.md +35 -0
  132. package/docs/12_setup_flow_improvements/questions.md +8 -0
  133. package/docs/12_setup_flow_improvements/tickets.md +122 -0
  134. package/docs/13_discord_typing_indicators/development_log.md +38 -0
  135. package/docs/13_discord_typing_indicators/notes.md +18 -0
  136. package/docs/13_discord_typing_indicators/prd.md +41 -0
  137. package/docs/13_discord_typing_indicators/questions.md +6 -0
  138. package/docs/13_discord_typing_indicators/tickets.md +60 -0
  139. package/docs/14_interruptions/development_log.md +50 -0
  140. package/docs/14_interruptions/notes.md +38 -0
  141. package/docs/14_interruptions/prd.md +46 -0
  142. package/docs/14_interruptions/questions.md +12 -0
  143. package/docs/14_interruptions/tickets.md +69 -0
  144. package/docs/15_sandbox_policies/development_log.md +95 -0
  145. package/docs/15_sandbox_policies/notes.md +33 -0
  146. package/docs/15_sandbox_policies/prd.md +163 -0
  147. package/docs/15_sandbox_policies/questions.md +10 -0
  148. package/docs/15_sandbox_policies/tickets.md +196 -0
  149. package/docs/CHECKS.md +9 -0
  150. package/docs/guides/discord_adapter_setup.md +69 -0
  151. package/docs/guides/sandbox_policies.md +76 -0
  152. package/eslint.config.js +47 -0
  153. package/napkin.md +21 -0
  154. package/package.json +50 -0
  155. package/scripts/create_worktree.sh +49 -0
  156. package/scripts/get_pr_comments.sh +36 -0
  157. package/src/adapter-discord/client.test.ts +65 -0
  158. package/src/adapter-discord/client.ts +41 -0
  159. package/src/adapter-discord/config.test.ts +156 -0
  160. package/src/adapter-discord/config.ts +61 -0
  161. package/src/adapter-discord/forwarder.test.ts +493 -0
  162. package/src/adapter-discord/forwarder.ts +246 -0
  163. package/src/adapter-discord/index.test.ts +399 -0
  164. package/src/adapter-discord/index.ts +147 -0
  165. package/src/adapter-discord/state.test.ts +65 -0
  166. package/src/adapter-discord/state.ts +44 -0
  167. package/src/cli/client.ts +46 -0
  168. package/src/cli/commands/agents.ts +138 -0
  169. package/src/cli/commands/chats.ts +79 -0
  170. package/src/cli/commands/down.ts +32 -0
  171. package/src/cli/commands/environments.ts +39 -0
  172. package/src/cli/commands/export-lite.ts +62 -0
  173. package/src/cli/commands/init.ts +79 -0
  174. package/src/cli/commands/jobs.ts +141 -0
  175. package/src/cli/commands/messages.ts +103 -0
  176. package/src/cli/commands/up.ts +26 -0
  177. package/src/cli/commands/web-api/agents.ts +138 -0
  178. package/src/cli/commands/web-api/chats.ts +213 -0
  179. package/src/cli/commands/web-api/utils.ts +27 -0
  180. package/src/cli/commands/web.ts +105 -0
  181. package/src/cli/e2e/adapter-discord.test.ts +76 -0
  182. package/src/cli/e2e/agents.test.ts +140 -0
  183. package/src/cli/e2e/basic.test.ts +43 -0
  184. package/src/cli/e2e/cron.test.ts +132 -0
  185. package/src/cli/e2e/daemon.test.ts +293 -0
  186. package/src/cli/e2e/environments.test.ts +66 -0
  187. package/src/cli/e2e/export-lite-func.test.ts +155 -0
  188. package/src/cli/e2e/export-lite.test.ts +51 -0
  189. package/src/cli/e2e/fallbacks.test.ts +169 -0
  190. package/src/cli/e2e/global-setup.ts +15 -0
  191. package/src/cli/e2e/init.test.ts +70 -0
  192. package/src/cli/e2e/messages.test.ts +294 -0
  193. package/src/cli/e2e/requests.test.ts +165 -0
  194. package/src/cli/e2e/utils.ts +66 -0
  195. package/src/cli/index.test.ts +7 -0
  196. package/src/cli/index.ts +29 -0
  197. package/src/cli/lite.ts +247 -0
  198. package/src/cli/utils.ts +4 -0
  199. package/src/daemon/auth.test.ts +50 -0
  200. package/src/daemon/auth.ts +69 -0
  201. package/src/daemon/chats.ts +26 -0
  202. package/src/daemon/cron.test.ts +28 -0
  203. package/src/daemon/cron.ts +159 -0
  204. package/src/daemon/events.ts +15 -0
  205. package/src/daemon/index.ts +212 -0
  206. package/src/daemon/message-agent.test.ts +132 -0
  207. package/src/daemon/message-extraction.test.ts +166 -0
  208. package/src/daemon/message-fallbacks.test.ts +313 -0
  209. package/src/daemon/message-interruption.test.ts +125 -0
  210. package/src/daemon/message-queue.test.ts +143 -0
  211. package/src/daemon/message-router.test.ts +106 -0
  212. package/src/daemon/message-session.test.ts +127 -0
  213. package/src/daemon/message-test-utils.ts +41 -0
  214. package/src/daemon/message-typing.test.ts +93 -0
  215. package/src/daemon/message-verbosity.test.ts +127 -0
  216. package/src/daemon/message.ts +600 -0
  217. package/src/daemon/observation.test.ts +118 -0
  218. package/src/daemon/policy-request-service.test.ts +87 -0
  219. package/src/daemon/policy-request-service.ts +62 -0
  220. package/src/daemon/policy-utils.test.ts +138 -0
  221. package/src/daemon/policy-utils.ts +152 -0
  222. package/src/daemon/queue.test.ts +89 -0
  223. package/src/daemon/queue.ts +87 -0
  224. package/src/daemon/request-store.test.ts +103 -0
  225. package/src/daemon/request-store.ts +96 -0
  226. package/src/daemon/router-policy-request.test.ts +99 -0
  227. package/src/daemon/router.test.ts +380 -0
  228. package/src/daemon/router.ts +510 -0
  229. package/src/daemon/routers/slash-command.test.ts +145 -0
  230. package/src/daemon/routers/slash-command.ts +58 -0
  231. package/src/daemon/routers/slash-interrupt.test.ts +30 -0
  232. package/src/daemon/routers/slash-interrupt.ts +7 -0
  233. package/src/daemon/routers/slash-new.test.ts +59 -0
  234. package/src/daemon/routers/slash-new.ts +14 -0
  235. package/src/daemon/routers/slash-policies.test.ts +167 -0
  236. package/src/daemon/routers/slash-policies.ts +131 -0
  237. package/src/daemon/routers/slash-stop.test.ts +30 -0
  238. package/src/daemon/routers/slash-stop.ts +3 -0
  239. package/src/daemon/routers/types.ts +10 -0
  240. package/src/daemon/routers/utils.ts +22 -0
  241. package/src/daemon/routers.test.ts +141 -0
  242. package/src/daemon/routers.ts +115 -0
  243. package/src/daemon/utils/spawn.ts +61 -0
  244. package/src/shared/agent-utils.ts +30 -0
  245. package/src/shared/chats.test.ts +112 -0
  246. package/src/shared/chats.ts +164 -0
  247. package/src/shared/config.test.ts +90 -0
  248. package/src/shared/config.ts +100 -0
  249. package/src/shared/event-source.ts +121 -0
  250. package/src/shared/fetch.ts +45 -0
  251. package/src/shared/lite.ts +129 -0
  252. package/src/shared/policies.ts +24 -0
  253. package/src/shared/utils/env.ts +27 -0
  254. package/src/shared/utils/fs.ts +13 -0
  255. package/src/shared/workspace.test.ts +345 -0
  256. package/src/shared/workspace.ts +500 -0
  257. package/templates/environments/cladding/env.json +7 -0
  258. package/templates/environments/macos/env.json +8 -0
  259. package/templates/environments/macos/sandbox.sb +21 -0
  260. package/templates/environments/macos-proxy/allowlist.txt +1 -0
  261. package/templates/environments/macos-proxy/env.json +14 -0
  262. package/templates/environments/macos-proxy/proxy.mjs +86 -0
  263. package/templates/environments/macos-proxy/sandbox.sb +34 -0
  264. package/templates/gemini/settings.json +11 -0
  265. package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +17 -0
  266. package/templates/gemini-claw/.gemini/settings.json +24 -0
  267. package/templates/gemini-claw/.gemini/skills/clawmini-jobs/SKILL.md +40 -0
  268. package/templates/gemini-claw/.gemini/system.md +98 -0
  269. package/templates/gemini-claw/BOOTSTRAP.md +54 -0
  270. package/templates/gemini-claw/GEMINI.md +107 -0
  271. package/templates/gemini-claw/HEARTBEAT.md +3 -0
  272. package/templates/gemini-claw/MEMORY.md +2 -0
  273. package/templates/gemini-claw/SOUL.md +42 -0
  274. package/templates/gemini-claw/TOOLS.md +38 -0
  275. package/templates/gemini-claw/USER.md +15 -0
  276. package/templates/gemini-claw/memory/.gitkeep +0 -0
  277. package/templates/gemini-claw/settings.json +24 -0
  278. package/templates/opencode/settings.json +11 -0
  279. package/tsconfig.json +42 -0
  280. package/tsdown.config.ts +19 -0
  281. package/vitest.config.ts +9 -0
  282. package/web/.svelte-kit/ambient.d.ts +382 -0
  283. package/web/.svelte-kit/generated/client/app.js +35 -0
  284. package/web/.svelte-kit/generated/client/matchers.js +1 -0
  285. package/web/.svelte-kit/generated/client/nodes/0.js +3 -0
  286. package/web/.svelte-kit/generated/client/nodes/1.js +1 -0
  287. package/web/.svelte-kit/generated/client/nodes/2.js +1 -0
  288. package/web/.svelte-kit/generated/client/nodes/3.js +1 -0
  289. package/web/.svelte-kit/generated/client/nodes/4.js +3 -0
  290. package/web/.svelte-kit/generated/client/nodes/5.js +3 -0
  291. package/web/.svelte-kit/generated/client-optimized/app.js +35 -0
  292. package/web/.svelte-kit/generated/client-optimized/matchers.js +1 -0
  293. package/web/.svelte-kit/generated/client-optimized/nodes/0.js +3 -0
  294. package/web/.svelte-kit/generated/client-optimized/nodes/1.js +1 -0
  295. package/web/.svelte-kit/generated/client-optimized/nodes/2.js +1 -0
  296. package/web/.svelte-kit/generated/client-optimized/nodes/3.js +1 -0
  297. package/web/.svelte-kit/generated/client-optimized/nodes/4.js +3 -0
  298. package/web/.svelte-kit/generated/client-optimized/nodes/5.js +3 -0
  299. package/web/.svelte-kit/generated/root.js +3 -0
  300. package/web/.svelte-kit/generated/root.svelte +68 -0
  301. package/web/.svelte-kit/generated/server/internal.js +53 -0
  302. package/web/.svelte-kit/non-ambient.d.ts +46 -0
  303. package/web/.svelte-kit/output/client/.vite/manifest.json +251 -0
  304. package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +1 -0
  305. package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +1 -0
  306. package/web/.svelte-kit/output/client/_app/immutable/chunks/B8yYFADm.js +1 -0
  307. package/web/.svelte-kit/output/client/_app/immutable/chunks/BPy8HLo7.js +5 -0
  308. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +1 -0
  309. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +2 -0
  310. package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +1 -0
  311. package/web/.svelte-kit/output/client/_app/immutable/chunks/COekwvP2.js +1 -0
  312. package/web/.svelte-kit/output/client/_app/immutable/chunks/CSvS_NwK.js +1 -0
  313. package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +1 -0
  314. package/web/.svelte-kit/output/client/_app/immutable/chunks/CyNaE55B.js +1 -0
  315. package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +2 -0
  316. package/web/.svelte-kit/output/client/_app/immutable/chunks/Dc-UOHw9.js +1 -0
  317. package/web/.svelte-kit/output/client/_app/immutable/chunks/DcrmIfTj.js +1 -0
  318. package/web/.svelte-kit/output/client/_app/immutable/chunks/ZkLyk0mE.js +1 -0
  319. package/web/.svelte-kit/output/client/_app/immutable/entry/app.B-vZe7PN.js +2 -0
  320. package/web/.svelte-kit/output/client/_app/immutable/entry/start.oP1AgKhs.js +1 -0
  321. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B5WFN0zw.js +1 -0
  322. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D1wtJb2k.js +1 -0
  323. package/web/.svelte-kit/output/client/_app/immutable/nodes/2.CK3CLC0f.js +1 -0
  324. package/web/.svelte-kit/output/client/_app/immutable/nodes/3.BB5wCoBf.js +4 -0
  325. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.Dr2jvAXK.js +1 -0
  326. package/web/.svelte-kit/output/client/_app/immutable/nodes/5.BJl7oM3b.js +1 -0
  327. package/web/.svelte-kit/output/client/_app/version.json +1 -0
  328. package/web/.svelte-kit/output/client/robots.txt +3 -0
  329. package/web/.svelte-kit/output/prerendered/dependencies/_app/env.js +1 -0
  330. package/web/.svelte-kit/output/server/.vite/manifest.json +215 -0
  331. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +1 -0
  332. package/web/.svelte-kit/output/server/chunks/Icon.js +153 -0
  333. package/web/.svelte-kit/output/server/chunks/bot.js +2753 -0
  334. package/web/.svelte-kit/output/server/chunks/client.js +47 -0
  335. package/web/.svelte-kit/output/server/chunks/environment.js +34 -0
  336. package/web/.svelte-kit/output/server/chunks/exports.js +231 -0
  337. package/web/.svelte-kit/output/server/chunks/false.js +4 -0
  338. package/web/.svelte-kit/output/server/chunks/index-server.js +20 -0
  339. package/web/.svelte-kit/output/server/chunks/index.js +24 -0
  340. package/web/.svelte-kit/output/server/chunks/internal.js +133 -0
  341. package/web/.svelte-kit/output/server/chunks/plus.js +81 -0
  342. package/web/.svelte-kit/output/server/chunks/root.js +4076 -0
  343. package/web/.svelte-kit/output/server/chunks/shared.js +789 -0
  344. package/web/.svelte-kit/output/server/chunks/utils.js +43 -0
  345. package/web/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +11 -0
  346. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +3944 -0
  347. package/web/.svelte-kit/output/server/entries/pages/_layout.ts.js +28 -0
  348. package/web/.svelte-kit/output/server/entries/pages/_page.svelte.js +7 -0
  349. package/web/.svelte-kit/output/server/entries/pages/agents/_page.svelte.js +379 -0
  350. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +292 -0
  351. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.ts.js +17 -0
  352. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.svelte.js +259 -0
  353. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/settings/_page.ts.js +17 -0
  354. package/web/.svelte-kit/output/server/index.js +3748 -0
  355. package/web/.svelte-kit/output/server/internal.js +14 -0
  356. package/web/.svelte-kit/output/server/manifest-full.js +63 -0
  357. package/web/.svelte-kit/output/server/manifest.js +63 -0
  358. package/web/.svelte-kit/output/server/nodes/0.js +13 -0
  359. package/web/.svelte-kit/output/server/nodes/1.js +8 -0
  360. package/web/.svelte-kit/output/server/nodes/2.js +8 -0
  361. package/web/.svelte-kit/output/server/nodes/3.js +8 -0
  362. package/web/.svelte-kit/output/server/nodes/4.js +13 -0
  363. package/web/.svelte-kit/output/server/nodes/5.js +13 -0
  364. package/web/.svelte-kit/output/server/remote-entry.js +557 -0
  365. package/web/.svelte-kit/tsconfig.json +67 -0
  366. package/web/.svelte-kit/types/route_meta_data.json +17 -0
  367. package/web/.svelte-kit/types/src/routes/$types.d.ts +26 -0
  368. package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +18 -0
  369. package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +21 -0
  370. package/web/.svelte-kit/types/src/routes/chats/[id]/proxy+page.ts +20 -0
  371. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +21 -0
  372. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/proxy+page.ts +19 -0
  373. package/web/.svelte-kit/types/src/routes/proxy+layout.ts +35 -0
  374. package/web/README.md +42 -0
  375. package/web/components.json +16 -0
  376. package/web/package.json +41 -0
  377. package/web/src/app.css +121 -0
  378. package/web/src/app.d.ts +13 -0
  379. package/web/src/app.html +11 -0
  380. package/web/src/demo.spec.ts +7 -0
  381. package/web/src/lib/app-state.svelte.ts +3 -0
  382. package/web/src/lib/assets/favicon.svg +1 -0
  383. package/web/src/lib/components/app/app-sidebar-test-wrapper.svelte +10 -0
  384. package/web/src/lib/components/app/app-sidebar.svelte +171 -0
  385. package/web/src/lib/components/app/app-sidebar.svelte.spec.ts +13 -0
  386. package/web/src/lib/components/ui/button/button.svelte +82 -0
  387. package/web/src/lib/components/ui/button/index.ts +17 -0
  388. package/web/src/lib/components/ui/dialog/dialog-close.svelte +7 -0
  389. package/web/src/lib/components/ui/dialog/dialog-content.svelte +45 -0
  390. package/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  391. package/web/src/lib/components/ui/dialog/dialog-footer.svelte +20 -0
  392. package/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  393. package/web/src/lib/components/ui/dialog/dialog-overlay.svelte +20 -0
  394. package/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  395. package/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  396. package/web/src/lib/components/ui/dialog/dialog-trigger.svelte +7 -0
  397. package/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  398. package/web/src/lib/components/ui/dialog/index.ts +34 -0
  399. package/web/src/lib/components/ui/input/index.ts +7 -0
  400. package/web/src/lib/components/ui/input/input.svelte +52 -0
  401. package/web/src/lib/components/ui/separator/index.ts +7 -0
  402. package/web/src/lib/components/ui/separator/separator.svelte +21 -0
  403. package/web/src/lib/components/ui/sheet/index.ts +34 -0
  404. package/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  405. package/web/src/lib/components/ui/sheet/sheet-content.svelte +60 -0
  406. package/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  407. package/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  408. package/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  409. package/web/src/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
  410. package/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  411. package/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  412. package/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  413. package/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
  414. package/web/src/lib/components/ui/sidebar/constants.ts +6 -0
  415. package/web/src/lib/components/ui/sidebar/context.svelte.ts +79 -0
  416. package/web/src/lib/components/ui/sidebar/index.ts +75 -0
  417. package/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
  418. package/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
  419. package/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +36 -0
  420. package/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
  421. package/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +34 -0
  422. package/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
  423. package/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
  424. package/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
  425. package/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +24 -0
  426. package/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +43 -0
  427. package/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +29 -0
  428. package/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +103 -0
  429. package/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
  430. package/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
  431. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +43 -0
  432. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
  433. package/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +25 -0
  434. package/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
  435. package/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
  436. package/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
  437. package/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
  438. package/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +35 -0
  439. package/web/src/lib/components/ui/sidebar/sidebar.svelte +104 -0
  440. package/web/src/lib/components/ui/skeleton/index.ts +7 -0
  441. package/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  442. package/web/src/lib/components/ui/switch/index.ts +7 -0
  443. package/web/src/lib/components/ui/switch/switch.svelte +29 -0
  444. package/web/src/lib/components/ui/textarea/index.ts +7 -0
  445. package/web/src/lib/components/ui/textarea/textarea.svelte +23 -0
  446. package/web/src/lib/components/ui/tooltip/index.ts +19 -0
  447. package/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  448. package/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  449. package/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  450. package/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  451. package/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  452. package/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
  453. package/web/src/lib/index.ts +1 -0
  454. package/web/src/lib/types.ts +23 -0
  455. package/web/src/lib/utils.ts +13 -0
  456. package/web/src/routes/+layout.svelte +67 -0
  457. package/web/src/routes/+layout.ts +34 -0
  458. package/web/src/routes/+page.svelte +7 -0
  459. package/web/src/routes/agents/+page.svelte +206 -0
  460. package/web/src/routes/chats/[id]/+page.svelte +406 -0
  461. package/web/src/routes/chats/[id]/+page.ts +19 -0
  462. package/web/src/routes/chats/[id]/page.svelte.spec.ts +102 -0
  463. package/web/src/routes/chats/[id]/settings/+page.svelte +165 -0
  464. package/web/src/routes/chats/[id]/settings/+page.ts +18 -0
  465. package/web/src/routes/page.svelte.spec.ts +13 -0
  466. package/web/static/robots.txt +3 -0
  467. package/web/svelte.config.js +21 -0
  468. package/web/tsconfig.json +20 -0
  469. package/web/vite.config.ts +41 -0
@@ -0,0 +1,58 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { RouterState } from './types.js';
4
+ import { getClawminiDir } from '../../shared/workspace.js';
5
+ import { pathIsInsideDir } from '../../shared/utils/fs.js';
6
+
7
+ export async function slashCommand(state: RouterState): Promise<RouterState> {
8
+ const commandsDir = path.resolve(getClawminiDir(), 'commands');
9
+ let currentMessage = state.message;
10
+
11
+ // Regex to match slash commands (e.g., /foo or /foo:bar) that appear as whole words.
12
+ // We use lookbehind and lookahead to ensure it's bounded by whitespace or string start/end.
13
+ const commandRegex = /(?<=^|\s)\/([a-zA-Z0-9_\-:.]+)(?=\s|$)/g;
14
+ const matches = [...currentMessage.matchAll(commandRegex)];
15
+
16
+ if (matches.length === 0) {
17
+ return state;
18
+ }
19
+
20
+ for (const match of matches) {
21
+ const fullMatch = match[0];
22
+ const commandName = match[1];
23
+ if (!commandName) continue;
24
+
25
+ const targetPathMd = path.resolve(commandsDir, `${commandName}.md`);
26
+ const targetPathTxt = path.resolve(commandsDir, `${commandName}.txt`);
27
+
28
+ // Strict path traversal protection
29
+ const baseTargetPath = path.resolve(commandsDir, commandName);
30
+ if (!pathIsInsideDir(baseTargetPath, commandsDir)) {
31
+ continue;
32
+ }
33
+
34
+ let content: string;
35
+
36
+ try {
37
+ content = await fs.readFile(targetPathMd, 'utf8');
38
+ } catch {
39
+ try {
40
+ content = await fs.readFile(targetPathTxt, 'utf8');
41
+ } catch {
42
+ // If file doesn't exist or can't be read, leave it as is.
43
+ continue;
44
+ }
45
+ }
46
+
47
+ // Replace the command with the content. We only replace the exact occurrence.
48
+ // Since replace replaces the first occurrence, and we are iterating over all matches,
49
+ // it should replace them sequentially. If there are multiple identical commands,
50
+ // it's fine, each will be replaced in turn.
51
+ currentMessage = currentMessage.replace(fullMatch, content.trim());
52
+ }
53
+
54
+ return {
55
+ ...state,
56
+ message: currentMessage,
57
+ };
58
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { slashInterrupt } from './slash-interrupt.js';
3
+ import type { RouterState } from './types.js';
4
+
5
+ describe('slashInterrupt', () => {
6
+ it('should not modify state if message does not start with /interrupt', () => {
7
+ const state: RouterState = { message: 'hello world', messageId: 'mock-msg-id', chatId: '123' };
8
+ const newState = slashInterrupt(state);
9
+ expect(newState).toEqual(state);
10
+ });
11
+
12
+ it('should set action to interrupt and reply if message is /interrupt', () => {
13
+ const state: RouterState = { message: '/interrupt', messageId: 'mock-msg-id', chatId: '123' };
14
+ const newState = slashInterrupt(state);
15
+ expect(newState.action).toBe('interrupt');
16
+ expect(newState.reply).toBe('Interrupting current task...');
17
+ expect(newState.message).toBe('');
18
+ });
19
+
20
+ it('should preserve remainder of message', () => {
21
+ const state: RouterState = {
22
+ message: '/interrupt extra text',
23
+ messageId: 'mock-msg-id',
24
+ chatId: '123',
25
+ };
26
+ const newState = slashInterrupt(state);
27
+ expect(newState.action).toBe('interrupt');
28
+ expect(newState.message).toBe('extra text');
29
+ });
30
+ });
@@ -0,0 +1,7 @@
1
+ import { createSlashActionRouter } from './utils.js';
2
+
3
+ export const slashInterrupt = createSlashActionRouter(
4
+ 'interrupt',
5
+ 'interrupt',
6
+ 'Interrupting current task...'
7
+ );
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { slashNew } from './slash-new.js';
3
+
4
+ vi.stubGlobal('crypto', { randomUUID: () => 'mock-uuid-1234' });
5
+
6
+ describe('slashNew router', () => {
7
+ it('should remove /new from the beginning and set a new sessionId', () => {
8
+ const initialState = {
9
+ message: '/new hello world',
10
+ messageId: 'mock-msg-id',
11
+ chatId: 'test-chat',
12
+ sessionId: 'old-session',
13
+ };
14
+
15
+ const newState = slashNew(initialState);
16
+ expect(newState.message).toBe('hello world');
17
+ expect(newState.sessionId).toBe('mock-uuid-1234');
18
+ expect(newState.chatId).toBe('test-chat');
19
+ });
20
+
21
+ it('should handle /new by itself', () => {
22
+ const initialState = {
23
+ message: '/new',
24
+ messageId: 'mock-msg-id',
25
+ chatId: 'test-chat',
26
+ sessionId: 'old-session',
27
+ };
28
+
29
+ const newState = slashNew(initialState);
30
+ expect(newState.message).toBe('');
31
+ expect(newState.sessionId).toBe('mock-uuid-1234');
32
+ });
33
+
34
+ it('should do nothing if /new is not at the start', () => {
35
+ const initialState = {
36
+ message: 'hello /new world',
37
+ messageId: 'mock-msg-id',
38
+ chatId: 'test-chat',
39
+ sessionId: 'old-session',
40
+ };
41
+
42
+ const newState = slashNew(initialState);
43
+ expect(newState.message).toBe('hello /new world');
44
+ expect(newState.sessionId).toBe('old-session');
45
+ });
46
+
47
+ it('should do nothing if the message just starts with /newly', () => {
48
+ const initialState = {
49
+ message: '/newly minted',
50
+ messageId: 'mock-msg-id',
51
+ chatId: 'test-chat',
52
+ sessionId: 'old-session',
53
+ };
54
+
55
+ const newState = slashNew(initialState);
56
+ expect(newState.message).toBe('/newly minted');
57
+ expect(newState.sessionId).toBe('old-session');
58
+ });
59
+ });
@@ -0,0 +1,14 @@
1
+ import type { RouterState } from './types.js';
2
+
3
+ export function slashNew(state: RouterState): RouterState {
4
+ if (/^\/new(\s|$)/.test(state.message)) {
5
+ const newMessage = state.message.replace(/^\/new(\s+|$)/, '').trim();
6
+ return {
7
+ ...state,
8
+ message: newMessage,
9
+ sessionId: crypto.randomUUID(),
10
+ reply: '[@clawmini/slash-new] Starting a new session...',
11
+ };
12
+ }
13
+ return state;
14
+ }
@@ -0,0 +1,167 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import { slashPolicies } from './slash-policies.js';
4
+ import { RequestStore } from '../request-store.js';
5
+ import { readPolicies } from '../../shared/workspace.js';
6
+ import { executeSafe, interpolateArgs } from '../policy-utils.js';
7
+ import { appendMessage } from '../chats.js';
8
+ import type { PolicyRequest } from '../../shared/policies.js';
9
+
10
+ vi.mock('../request-store.js');
11
+ vi.mock('../../shared/workspace.js');
12
+ vi.mock('../policy-utils.js');
13
+ vi.mock('../chats.js');
14
+ vi.mock('node:crypto', () => ({
15
+ randomUUID: vi.fn(() => 'mock-uuid'),
16
+ }));
17
+
18
+ describe('slashPolicies', () => {
19
+ let mockStore: any;
20
+
21
+ beforeEach(() => {
22
+ mockStore = {
23
+ list: vi.fn(),
24
+ load: vi.fn(),
25
+ save: vi.fn(),
26
+ };
27
+ vi.mocked(RequestStore).mockImplementation(function (this: any) {
28
+ this.list = mockStore.list;
29
+ this.load = mockStore.load;
30
+ this.save = mockStore.save;
31
+ return this;
32
+ } as any);
33
+
34
+ vi.mocked(appendMessage).mockResolvedValue(undefined);
35
+ vi.mocked(readPolicies).mockResolvedValue({
36
+ policies: {
37
+ 'test-cmd': {
38
+ command: 'echo',
39
+ args: ['hello'],
40
+ },
41
+ },
42
+ });
43
+ vi.mocked(interpolateArgs).mockReturnValue(['hello', 'world']);
44
+ vi.mocked(executeSafe).mockResolvedValue({
45
+ stdout: 'hello world',
46
+ stderr: '',
47
+ exitCode: 0,
48
+ });
49
+ });
50
+
51
+ afterEach(() => {
52
+ vi.clearAllMocks();
53
+ });
54
+
55
+ it('should ignore non-matching messages', async () => {
56
+ const state = { message: 'hello world', messageId: 'mock-msg-id', chatId: 'chat-1' };
57
+ const result = await slashPolicies(state);
58
+ expect(result).toEqual(state);
59
+ });
60
+
61
+ it('should list pending requests on /pending', async () => {
62
+ const pendingReq: PolicyRequest = {
63
+ id: 'req-1',
64
+ commandName: 'test-cmd',
65
+ args: ['world'],
66
+ fileMappings: {},
67
+ state: 'Pending',
68
+ createdAt: Date.now(),
69
+ chatId: 'chat-1',
70
+ agentId: 'agent-1',
71
+ };
72
+ const approvedReq: PolicyRequest = { ...pendingReq, id: 'req-2', state: 'Approved' };
73
+ mockStore.list.mockResolvedValue([pendingReq, approvedReq]);
74
+
75
+ const state = { message: '/pending', messageId: 'mock-msg-id', chatId: 'chat-1' };
76
+ const result = await slashPolicies(state);
77
+
78
+ expect(result.action).toBe('stop');
79
+ expect(result.reply).toContain('Pending Requests (1):');
80
+ expect(result.reply).toContain('- ID: req-1 | Command: test-cmd world');
81
+ expect(result.reply).not.toContain('req-2');
82
+ });
83
+
84
+ it('should approve a pending request on /approve, execute it, and inject feedback', async () => {
85
+ const pendingReq: PolicyRequest = {
86
+ id: 'req-1',
87
+ commandName: 'test-cmd',
88
+ args: ['world'],
89
+ fileMappings: {},
90
+ state: 'Pending',
91
+ createdAt: Date.now(),
92
+ chatId: 'chat-1',
93
+ agentId: 'agent-1',
94
+ };
95
+ mockStore.load.mockResolvedValue(pendingReq);
96
+
97
+ const state = { message: '/approve req-1', messageId: 'mock-msg-id', chatId: 'chat-1' };
98
+ const result = await slashPolicies(state);
99
+
100
+ expect(mockStore.save).toHaveBeenCalledWith({ ...pendingReq, state: 'Approved' });
101
+ expect(executeSafe).toHaveBeenCalledWith('echo', ['hello', 'world'], expect.any(Object));
102
+ expect(appendMessage).toHaveBeenCalledWith(
103
+ 'chat-1',
104
+ expect.objectContaining({
105
+ role: 'log',
106
+ content: 'Request req-1 approved and executed.',
107
+ command: 'echo hello world',
108
+ stdout: 'hello world',
109
+ exitCode: 0,
110
+ })
111
+ );
112
+ expect(result.action).toBeUndefined();
113
+ expect(result.message).toContain('Request req-1 approved.');
114
+ expect(result.message).toContain('<stdout>\nhello world\n</stdout>');
115
+ });
116
+
117
+ it('should reject a pending request on /reject with reason and inject feedback', async () => {
118
+ const pendingReq: PolicyRequest = {
119
+ id: 'req-1',
120
+ commandName: 'test-cmd',
121
+ args: [],
122
+ fileMappings: {},
123
+ state: 'Pending',
124
+ createdAt: Date.now(),
125
+ chatId: 'chat-1',
126
+ agentId: 'agent-1',
127
+ };
128
+ mockStore.load.mockResolvedValue(pendingReq);
129
+
130
+ const state = {
131
+ message: '/reject req-1 Not allowed',
132
+ messageId: 'mock-msg-id',
133
+ chatId: 'chat-1',
134
+ };
135
+ const result = await slashPolicies(state);
136
+
137
+ expect(mockStore.save).toHaveBeenCalledWith({
138
+ ...pendingReq,
139
+ state: 'Rejected',
140
+ rejectionReason: 'Not allowed',
141
+ });
142
+ expect(appendMessage).toHaveBeenCalledWith(
143
+ 'chat-1',
144
+ expect.objectContaining({
145
+ role: 'log',
146
+ content: 'Request req-1 rejected. Reason: Not allowed',
147
+ command: 'policy-request-reject req-1',
148
+ exitCode: 1,
149
+ })
150
+ );
151
+ expect(result.action).toBeUndefined();
152
+ expect(result.message).toBe('Request req-1 rejected. Reason: Not allowed');
153
+ });
154
+
155
+ it('should not act if request is not found', async () => {
156
+ mockStore.load.mockResolvedValue(null);
157
+
158
+ const state = { message: '/approve req-1', messageId: 'mock-msg-id', chatId: 'chat-1' };
159
+ const result = await slashPolicies(state);
160
+
161
+ expect(mockStore.save).not.toHaveBeenCalled();
162
+ expect(appendMessage).not.toHaveBeenCalled();
163
+ expect(result.action).toBeUndefined();
164
+ expect(result.message).toBe('');
165
+ expect(result.reply).toBe('Request not found: req-1');
166
+ });
167
+ });
@@ -0,0 +1,131 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type { RouterState } from './types.js';
3
+ import { RequestStore } from '../request-store.js';
4
+ import { readPolicies, getWorkspaceRoot } from '../../shared/workspace.js';
5
+ import { executeSafe, interpolateArgs } from '../policy-utils.js';
6
+ import { appendMessage } from '../chats.js';
7
+ import type { CommandLogMessage } from '../../shared/chats.js';
8
+
9
+ async function loadAndValidateRequest(id: string, state: RouterState) {
10
+ const store = new RequestStore(getWorkspaceRoot());
11
+ const req = await store.load(id);
12
+ if (!req) return { error: { ...state, message: '', reply: `Request not found: ${id}` } };
13
+ if (req.chatId && req.chatId !== state.chatId)
14
+ return {
15
+ error: { ...state, message: '', reply: `Request belongs to a different chat: ${req.chatId}` },
16
+ };
17
+ if (req.state !== 'Pending')
18
+ return { error: { ...state, message: '', reply: `Request is not pending: ${id}` } };
19
+ return { req, store };
20
+ }
21
+
22
+ export async function slashPolicies(state: RouterState): Promise<RouterState> {
23
+ const message = state.message.trim();
24
+
25
+ if (message === '/pending') {
26
+ const store = new RequestStore(getWorkspaceRoot());
27
+ const requests = await store.list();
28
+ const pending = requests.filter((r) => r.state === 'Pending');
29
+
30
+ let reply = `Pending Requests (${pending.length}):\n`;
31
+ for (const req of pending) {
32
+ reply += `- ID: ${req.id} | Command: ${req.commandName} ${req.args.join(' ')}\n`;
33
+ }
34
+
35
+ return {
36
+ ...state,
37
+ reply,
38
+ action: 'stop',
39
+ };
40
+ }
41
+
42
+ const approveMatch = message.match(/^\/approve\s+([^\s]+)/);
43
+ if (approveMatch) {
44
+ const id = approveMatch[1];
45
+ if (!id) return state;
46
+ const { req, store, error } = await loadAndValidateRequest(id, state);
47
+ if (error) return error;
48
+ if (!req || !store) return state; // Should not happen if error is undefined
49
+
50
+ const config = await readPolicies();
51
+ const policy = config?.policies?.[req.commandName];
52
+ if (!policy) {
53
+ return { ...state, message: '', reply: `Policy not found: ${req.commandName}` };
54
+ }
55
+
56
+ req.state = 'Approved';
57
+ await store.save(req);
58
+
59
+ const fullArgs = [...(policy.args || []), ...req.args];
60
+ const interpolatedArgs = interpolateArgs(fullArgs, req.fileMappings);
61
+
62
+ const { stdout, stderr, exitCode } = await executeSafe(policy.command, interpolatedArgs, {
63
+ cwd: getWorkspaceRoot(),
64
+ });
65
+
66
+ const commandStr = `${policy.command} ${interpolatedArgs.join(' ')}`;
67
+ const logMsg: CommandLogMessage = {
68
+ id: randomUUID(),
69
+ messageId: state.messageId,
70
+ role: 'log',
71
+ source: 'router',
72
+ content: `Request ${id} approved and executed.`,
73
+ stderr,
74
+ stdout,
75
+ timestamp: new Date().toISOString(),
76
+ command: commandStr,
77
+ cwd: getWorkspaceRoot(),
78
+ exitCode,
79
+ };
80
+
81
+ await appendMessage(state.chatId, logMsg);
82
+
83
+ const agentMessage = `Request ${id} approved.\n\n${wrapInHtml('stdout', stdout)}\n\n${wrapInHtml('stderr', stderr)}\n\nExit Code: ${exitCode}`;
84
+ return {
85
+ ...state,
86
+ message: agentMessage,
87
+ reply: `Approved request, running ${req.commandName}`,
88
+ };
89
+ }
90
+
91
+ const rejectMatch = message.match(/^\/reject\s+([^\s]+)(?:\s+(.*))?/);
92
+ if (rejectMatch) {
93
+ const id = rejectMatch[1];
94
+ if (!id) return state;
95
+ const reason = rejectMatch[2] || 'No reason provided';
96
+ const { req, store, error } = await loadAndValidateRequest(id, state);
97
+ if (error) return error;
98
+ if (!req || !store) return state; // Should not happen if error is undefined
99
+
100
+ req.state = 'Rejected';
101
+ req.rejectionReason = reason;
102
+ await store.save(req);
103
+
104
+ const logMsg: CommandLogMessage = {
105
+ id: randomUUID(),
106
+ messageId: state.messageId,
107
+ role: 'log',
108
+ source: 'router',
109
+ content: `Request ${id} rejected. Reason: ${reason}`,
110
+ stderr: '',
111
+ timestamp: new Date().toISOString(),
112
+ command: `policy-request-reject ${id}`,
113
+ cwd: getWorkspaceRoot(),
114
+ exitCode: 1,
115
+ };
116
+
117
+ await appendMessage(state.chatId, logMsg);
118
+
119
+ const agentMessage = `Request ${id} rejected. Reason: ${reason}`;
120
+ return { ...state, message: agentMessage };
121
+ }
122
+
123
+ return state;
124
+ }
125
+
126
+ function wrapInHtml(tag: string, text: string): string {
127
+ if (text.trim().length === 0) {
128
+ return `<${tag}></${tag}>`;
129
+ }
130
+ return `<${tag}>\n${text.trim()}\n</${tag}>`;
131
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { slashStop } from './slash-stop.js';
3
+ import type { RouterState } from './types.js';
4
+
5
+ describe('slashStop', () => {
6
+ it('should not modify state if message does not start with /stop', () => {
7
+ const state: RouterState = { message: 'hello world', messageId: 'mock-msg-id', chatId: '123' };
8
+ const newState = slashStop(state);
9
+ expect(newState).toEqual(state);
10
+ });
11
+
12
+ it('should set action to stop and reply if message is /stop', () => {
13
+ const state: RouterState = { message: '/stop', messageId: 'mock-msg-id', chatId: '123' };
14
+ const newState = slashStop(state);
15
+ expect(newState.action).toBe('stop');
16
+ expect(newState.reply).toBe('Stopping current task...');
17
+ expect(newState.message).toBe('');
18
+ });
19
+
20
+ it('should preserve remainder of message', () => {
21
+ const state: RouterState = {
22
+ message: '/stop extra text',
23
+ messageId: 'mock-msg-id',
24
+ chatId: '123',
25
+ };
26
+ const newState = slashStop(state);
27
+ expect(newState.action).toBe('stop');
28
+ expect(newState.message).toBe('extra text');
29
+ });
30
+ });
@@ -0,0 +1,3 @@
1
+ import { createSlashActionRouter } from './utils.js';
2
+
3
+ export const slashStop = createSlashActionRouter('stop', 'stop', 'Stopping current task...');
@@ -0,0 +1,10 @@
1
+ export interface RouterState {
2
+ messageId: string;
3
+ message: string;
4
+ chatId: string;
5
+ agentId?: string;
6
+ sessionId?: string;
7
+ env?: Record<string, string>;
8
+ reply?: string;
9
+ action?: 'stop' | 'interrupt' | 'continue';
10
+ }
@@ -0,0 +1,22 @@
1
+ import type { RouterState } from './types.js';
2
+
3
+ export function createSlashActionRouter(
4
+ command: string,
5
+ action: NonNullable<RouterState['action']>,
6
+ replyMessage: string
7
+ ) {
8
+ return function (state: RouterState): RouterState {
9
+ const regex = new RegExp(`^\\/${command}(\\s|$)`);
10
+ if (regex.test(state.message)) {
11
+ const replaceRegex = new RegExp(`^\\/${command}(\\s+|$)`);
12
+ const newMessage = state.message.replace(replaceRegex, '').trim();
13
+ return {
14
+ ...state,
15
+ message: newMessage,
16
+ action,
17
+ reply: replyMessage,
18
+ };
19
+ }
20
+ return state;
21
+ };
22
+ }
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { executeRouterPipeline } from './routers.js';
3
+ import type { RouterState } from './routers/types.js';
4
+
5
+ vi.mock('./routers/slash-new.js', () => ({
6
+ slashNew: vi.fn((state: RouterState) => ({ ...state, message: 'slash-new-called' })),
7
+ }));
8
+
9
+ vi.mock('./routers/slash-command.js', () => ({
10
+ slashCommand: vi.fn(async (state: RouterState) => ({
11
+ ...state,
12
+ message: 'slash-command-called',
13
+ })),
14
+ }));
15
+
16
+ vi.mock('./routers/slash-stop.js', () => ({
17
+ slashStop: vi.fn((state: RouterState) => ({ ...state, action: 'stop' })),
18
+ }));
19
+
20
+ vi.mock('./routers/slash-interrupt.js', () => ({
21
+ slashInterrupt: vi.fn((state: RouterState) => ({ ...state, action: 'interrupt' })),
22
+ }));
23
+
24
+ vi.mock('./routers/slash-policies.js', () => ({
25
+ slashPolicies: vi.fn(async (state: RouterState) => state),
26
+ }));
27
+
28
+ describe('Router Pipeline Execution', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.restoreAllMocks();
35
+ });
36
+
37
+ it('should pass state unchanged when no routers are provided', async () => {
38
+ const initialState: RouterState = {
39
+ message: 'hello',
40
+ messageId: 'mock-msg-id',
41
+ chatId: 'chat-1',
42
+ };
43
+ const finalState = await executeRouterPipeline(initialState, []);
44
+ expect(finalState).toEqual(initialState);
45
+ });
46
+
47
+ it('should call built-in @clawmini/slash-new router', async () => {
48
+ const initialState: RouterState = {
49
+ message: 'hello',
50
+ messageId: 'mock-msg-id',
51
+ chatId: 'chat-1',
52
+ };
53
+ const finalState = await executeRouterPipeline(initialState, ['@clawmini/slash-new']);
54
+ expect(finalState.message).toBe('slash-new-called');
55
+ expect(finalState.chatId).toBe('chat-1');
56
+ });
57
+
58
+ it('should call built-in @clawmini/slash-command router', async () => {
59
+ const initialState: RouterState = {
60
+ message: 'hello',
61
+ messageId: 'mock-msg-id',
62
+ chatId: 'chat-1',
63
+ };
64
+ const finalState = await executeRouterPipeline(initialState, ['@clawmini/slash-command']);
65
+ expect(finalState.message).toBe('slash-command-called');
66
+ });
67
+
68
+ it('should call built-in @clawmini/slash-stop router', async () => {
69
+ const initialState: RouterState = {
70
+ message: 'hello',
71
+ messageId: 'mock-msg-id',
72
+ chatId: 'chat-1',
73
+ };
74
+ const finalState = await executeRouterPipeline(initialState, ['@clawmini/slash-stop']);
75
+ expect(finalState.action).toBe('stop');
76
+ });
77
+
78
+ it('should call built-in @clawmini/slash-interrupt router', async () => {
79
+ const initialState: RouterState = {
80
+ message: 'hello',
81
+ messageId: 'mock-msg-id',
82
+ chatId: 'chat-1',
83
+ };
84
+ const finalState = await executeRouterPipeline(initialState, ['@clawmini/slash-interrupt']);
85
+ expect(finalState.action).toBe('interrupt');
86
+ });
87
+
88
+ it('should execute custom shell command router and merge state correctly', async () => {
89
+ const initialState: RouterState = {
90
+ message: 'hello',
91
+ messageId: 'mock-msg-id',
92
+ chatId: 'chat-1',
93
+ env: { FOO: 'bar' },
94
+ };
95
+
96
+ // Command that parses stdin and outputs a modified JSON object
97
+ const command = `node -e "
98
+ let input = '';
99
+ process.stdin.on('data', d => input += d);
100
+ process.stdin.on('end', () => {
101
+ const data = JSON.parse(input);
102
+ console.log(JSON.stringify({
103
+ message: data.message + ' world',
104
+ agent: 'new-agent',
105
+ session: 'new-session',
106
+ env: { BAZ: 'qux' },
107
+ reply: 'Custom router reply'
108
+ }));
109
+ });
110
+ "`;
111
+
112
+ const finalState = await executeRouterPipeline(initialState, [command]);
113
+
114
+ expect(finalState.message).toBe('hello world');
115
+ expect(finalState.agentId).toBe('new-agent');
116
+ expect(finalState.sessionId).toBe('new-session');
117
+ expect(finalState.env).toEqual({ FOO: 'bar', BAZ: 'qux' });
118
+ expect(finalState.reply).toBe('Custom router reply');
119
+ expect(finalState.chatId).toBe('chat-1'); // Unchanged
120
+ });
121
+
122
+ it('should handle silent failure of custom shell command router', async () => {
123
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
124
+ const initialState: RouterState = {
125
+ message: 'hello',
126
+ messageId: 'mock-msg-id',
127
+ chatId: 'chat-1',
128
+ };
129
+
130
+ // Command that intentionally fails with a non-zero exit code
131
+ const command = `node -e "process.exit(1);"`;
132
+
133
+ const finalState = await executeRouterPipeline(initialState, [command]);
134
+
135
+ // State should remain unchanged
136
+ expect(finalState).toEqual(initialState);
137
+
138
+ // The error should have been logged
139
+ expect(consoleErrorSpy).toHaveBeenCalled();
140
+ });
141
+ });