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,510 @@
1
+ /* eslint-disable max-lines */
2
+ import { initTRPC, TRPCError } from '@trpc/server';
3
+ import { z } from 'zod';
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, DAEMON_EVENT_TYPING } from './events.js';
7
+ import {
8
+ getSettingsPath,
9
+ readChatSettings,
10
+ writeChatSettings,
11
+ getAgent,
12
+ getWorkspaceRoot,
13
+ readPolicies,
14
+ getClawminiDir,
15
+ } from '../shared/workspace.js';
16
+ import { PolicyRequestService } from './policy-request-service.js';
17
+ import { RequestStore } from './request-store.js';
18
+ import { CronJobSchema } from '../shared/config.js';
19
+ import { handleUserMessage } from './message.js';
20
+ import { getDefaultChatId, getMessages } from './chats.js';
21
+ import { runCommand } from './utils/spawn.js';
22
+ import { cronManager } from './cron.js';
23
+ import type { IncomingMessage, ServerResponse } from 'node:http';
24
+ import type { TokenPayload } from './auth.js';
25
+
26
+ export interface Context {
27
+ req?: IncomingMessage | undefined;
28
+ res?: ServerResponse | undefined;
29
+ isApiServer?: boolean | undefined;
30
+ tokenPayload?: TokenPayload | null | undefined;
31
+ }
32
+
33
+ const t = initTRPC.context<Context>().create();
34
+ export const router = t.router;
35
+ export const publicProcedure = t.procedure;
36
+
37
+ const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
38
+ if (ctx.isApiServer) {
39
+ if (!ctx.tokenPayload) {
40
+ throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing or invalid token' });
41
+ }
42
+ }
43
+ return next({
44
+ ctx: {
45
+ ...ctx,
46
+ tokenPayload: ctx.tokenPayload,
47
+ },
48
+ });
49
+ });
50
+
51
+ export const apiProcedure = t.procedure.use(apiAuthMiddleware);
52
+
53
+ export async function getUniquePath(p: string): Promise<string> {
54
+ let currentPath = p;
55
+ let counter = 1;
56
+ while (true) {
57
+ try {
58
+ await fs.stat(currentPath);
59
+ const ext = path.extname(p);
60
+ const base = path.basename(p, ext);
61
+ currentPath = path.join(path.dirname(p), `${base}-${counter}${ext}`);
62
+ counter++;
63
+ } catch {
64
+ return currentPath;
65
+ }
66
+ }
67
+ }
68
+
69
+ async function resolveAgentDir(
70
+ agentId: string | undefined | null,
71
+ workspaceRoot: string
72
+ ): Promise<string> {
73
+ if (agentId && agentId !== 'default') {
74
+ try {
75
+ const agent = await getAgent(agentId, workspaceRoot);
76
+ if (agent && agent.directory) {
77
+ return path.resolve(workspaceRoot, agent.directory);
78
+ }
79
+ } catch (err: unknown) {
80
+ console.warn(`Could not load custom agent '${agentId}' for resolving directory:`, err);
81
+ }
82
+ return path.resolve(workspaceRoot, agentId);
83
+ }
84
+ return workspaceRoot;
85
+ }
86
+
87
+ async function resolveAndCheckChatId(ctx: Context, inputChatId?: string): Promise<string> {
88
+ const chatId =
89
+ inputChatId ??
90
+ (ctx.isApiServer && ctx.tokenPayload ? ctx.tokenPayload.chatId : await getDefaultChatId());
91
+
92
+ if (ctx.isApiServer && ctx.tokenPayload) {
93
+ if (ctx.tokenPayload.chatId !== chatId) {
94
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'Token not authorized for this chat' });
95
+ }
96
+ }
97
+
98
+ return chatId;
99
+ }
100
+
101
+ async function getAgentFilesDir(
102
+ agentId: string | undefined,
103
+ chatId: string,
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ settings: any,
106
+ workspaceRoot: string
107
+ ): Promise<string> {
108
+ const chatSettings = (await readChatSettings(chatId)) ?? {};
109
+ const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
110
+ let agentFilesDir = settings?.defaultAgent?.files || './attachments';
111
+ const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
112
+
113
+ if (targetAgentId !== 'default') {
114
+ try {
115
+ const customAgent = await getAgent(targetAgentId, workspaceRoot);
116
+ if (customAgent?.files) {
117
+ agentFilesDir = customAgent.files;
118
+ }
119
+ } catch (err: unknown) {
120
+ console.warn(
121
+ `Could not load custom agent '${targetAgentId}' for resolving files directory:`,
122
+ err
123
+ );
124
+ }
125
+ }
126
+
127
+ return path.resolve(agentDir, agentFilesDir);
128
+ }
129
+
130
+ async function validateAttachments(files: string[]): Promise<void> {
131
+ const { pathIsInsideDir } = await import('../shared/utils/fs.js');
132
+ const { getClawminiDir } = await import('../shared/workspace.js');
133
+ const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp');
134
+
135
+ for (const file of files) {
136
+ const absoluteFile = path.resolve(process.cwd(), file);
137
+ if (!pathIsInsideDir(absoluteFile, tmpDir)) {
138
+ throw new TRPCError({
139
+ code: 'BAD_REQUEST',
140
+ message: 'File must be inside the temporary directory.',
141
+ });
142
+ }
143
+ try {
144
+ await fs.access(absoluteFile);
145
+ } catch {
146
+ throw new TRPCError({
147
+ code: 'BAD_REQUEST',
148
+ message: `File does not exist: ${file}`,
149
+ });
150
+ }
151
+ }
152
+ }
153
+
154
+ async function validateLogFile(
155
+ file: string,
156
+ agentDir: string,
157
+ workspaceRoot: string
158
+ ): Promise<string> {
159
+ const { pathIsInsideDir } = await import('../shared/utils/fs.js');
160
+ // The agent sends paths relative to its working directory.
161
+ // We resolve to an absolute path to verify it is within the agent's directory.
162
+ const resolvedPath = path.resolve(agentDir, file);
163
+
164
+ if (!pathIsInsideDir(resolvedPath, agentDir, { allowSameDir: true })) {
165
+ throw new TRPCError({
166
+ code: 'BAD_REQUEST',
167
+ message: 'File must be within the agent workspace.',
168
+ });
169
+ }
170
+
171
+ try {
172
+ await fs.access(resolvedPath);
173
+ } catch {
174
+ throw new TRPCError({
175
+ code: 'BAD_REQUEST',
176
+ message: `File does not exist: ${file}`,
177
+ });
178
+ }
179
+
180
+ // Convert the absolute path to a path relative to the WORKSPACE directory.
181
+ // This allows adapters (like discord-adapter) to easily resolve it against their own view of the workspace.
182
+ return path.relative(workspaceRoot, resolvedPath);
183
+ }
184
+
185
+ const AppRouter = router({
186
+ sendMessage: apiProcedure
187
+ .input(
188
+ z.object({
189
+ type: z.literal('send-message'),
190
+ client: z.literal('cli'),
191
+ data: z.object({
192
+ message: z.string(),
193
+ chatId: z.string().optional(),
194
+ sessionId: z.string().optional(),
195
+ agentId: z.string().optional(),
196
+ noWait: z.boolean().optional(),
197
+ files: z.array(z.string()).optional(),
198
+ adapter: z.string().optional(),
199
+ }),
200
+ })
201
+ )
202
+ .mutation(async ({ input, ctx }) => {
203
+ let message = input.data.message;
204
+ const chatId = await resolveAndCheckChatId(ctx, input.data.chatId);
205
+ const noWait = input.data.noWait ?? false;
206
+ const sessionId = input.data.sessionId;
207
+ const agentId = input.data.agentId;
208
+ const settingsPath = getSettingsPath();
209
+
210
+ let settings;
211
+ try {
212
+ const settingsStr = await fs.readFile(settingsPath, 'utf8');
213
+ settings = JSON.parse(settingsStr);
214
+ } catch (err) {
215
+ throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
216
+ }
217
+
218
+ const files = input.data.files;
219
+ if (files && files.length > 0) {
220
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
221
+ const chatSettings = (await readChatSettings(chatId)) ?? {};
222
+ const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
223
+ const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
224
+ const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
225
+
226
+ const adapterNamespace = input.data.adapter || 'cli';
227
+ const targetDir = path.join(absoluteFilesDir, adapterNamespace);
228
+
229
+ const { pathIsInsideDir } = await import('../shared/utils/fs.js');
230
+
231
+ if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) {
232
+ throw new TRPCError({
233
+ code: 'BAD_REQUEST',
234
+ message: 'Target directory must be within the workspace.',
235
+ });
236
+ }
237
+
238
+ await validateAttachments(files);
239
+
240
+ await fs.mkdir(targetDir, { recursive: true });
241
+
242
+ const finalPaths: string[] = [];
243
+ for (const file of files) {
244
+ const fileName = path.basename(file);
245
+ const targetPath = await getUniquePath(path.join(targetDir, fileName));
246
+
247
+ try {
248
+ await fs.rename(file, targetPath);
249
+ } catch {
250
+ await fs.copyFile(file, targetPath);
251
+ await fs.unlink(file);
252
+ }
253
+
254
+ finalPaths.push(path.relative(agentDir, targetPath));
255
+ }
256
+
257
+ const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join('\n')}`;
258
+ message = message ? `${message}\n\n${fileList}` : fileList;
259
+ }
260
+
261
+ await handleUserMessage(
262
+ chatId,
263
+ message,
264
+ settings,
265
+ undefined,
266
+ noWait,
267
+ (args) => runCommand({ ...args, logToTerminal: true }),
268
+ sessionId,
269
+ agentId
270
+ );
271
+
272
+ return { success: true };
273
+ }),
274
+ getMessages: apiProcedure
275
+ .input(z.object({ chatId: z.string().optional(), limit: z.number().optional() }))
276
+ .query(async ({ input, ctx }) => {
277
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
278
+ return getMessages(chatId, input.limit);
279
+ }),
280
+ waitForMessages: apiProcedure
281
+ .input(
282
+ z.object({
283
+ chatId: z.string().optional(),
284
+ lastMessageId: z.string().optional(),
285
+ })
286
+ )
287
+ .subscription(async function* ({ input, ctx, signal }) {
288
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
289
+
290
+ // 1. Check if there are already new messages
291
+ if (input.lastMessageId) {
292
+ const messages = await getMessages(chatId);
293
+ const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
294
+ if (lastIndex !== -1 && lastIndex < messages.length - 1) {
295
+ yield messages.slice(lastIndex + 1);
296
+ }
297
+ }
298
+
299
+ // 2. Listen for new messages
300
+ const { on } = await import('node:events');
301
+ try {
302
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) {
303
+ if (event.chatId === chatId) {
304
+ yield [event.message];
305
+ }
306
+ }
307
+ } catch (err) {
308
+ if (err instanceof Error && err.name === 'AbortError') {
309
+ return;
310
+ }
311
+ throw err;
312
+ }
313
+ }),
314
+ waitForTyping: apiProcedure
315
+ .input(
316
+ z.object({
317
+ chatId: z.string().optional(),
318
+ })
319
+ )
320
+ .subscription(async function* ({ input, ctx, signal }) {
321
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
322
+
323
+ const { on } = await import('node:events');
324
+ try {
325
+ for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) {
326
+ if (event.chatId === chatId) {
327
+ yield event;
328
+ }
329
+ }
330
+ } catch (err) {
331
+ if (err instanceof Error && err.name === 'AbortError') {
332
+ return;
333
+ }
334
+ throw err;
335
+ }
336
+ }),
337
+ ping: publicProcedure.query(() => {
338
+ return { status: 'ok' };
339
+ }),
340
+ shutdown: publicProcedure.mutation(() => {
341
+ // Schedule a shutdown shortly after the response is sent
342
+ setTimeout(() => {
343
+ console.log('Shutting down daemon...');
344
+ process.kill(process.pid, 'SIGTERM');
345
+ }, 100);
346
+ return { success: true };
347
+ }),
348
+ logMessage: apiProcedure
349
+ .input(
350
+ z.object({
351
+ chatId: z.string().optional(),
352
+ message: z.string().optional(),
353
+ files: z.array(z.string()).optional(),
354
+ })
355
+ )
356
+ .mutation(async ({ input, ctx }) => {
357
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
358
+ const timestamp = new Date().toISOString();
359
+ const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
360
+
361
+ const filePaths: string[] = [];
362
+ if (input.files && input.files.length > 0) {
363
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
364
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
365
+
366
+ for (const file of input.files) {
367
+ const validPath = await validateLogFile(file, agentDir, workspaceRoot);
368
+ filePaths.push(validPath);
369
+ }
370
+ }
371
+
372
+ const filesArgStr = filePaths.map((p) => ` --file ${p}`).join('');
373
+ const messageStr = input.message || '';
374
+ const logMsg: import('./chats.js').CommandLogMessage = {
375
+ id,
376
+ messageId: id,
377
+ role: 'log',
378
+ source: 'router',
379
+ content: messageStr,
380
+ stderr: '',
381
+ timestamp,
382
+ command: `clawmini-lite log${filesArgStr}`,
383
+ cwd: process.cwd(),
384
+ exitCode: 0,
385
+ ...(filePaths.length > 0 ? { files: filePaths } : {}),
386
+ };
387
+
388
+ await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
389
+ return { success: true };
390
+ }),
391
+ listCronJobs: apiProcedure
392
+ .input(z.object({ chatId: z.string().optional() }))
393
+ .query(async ({ input, ctx }) => {
394
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
395
+ const settings = await readChatSettings(chatId);
396
+ return settings?.jobs ?? [];
397
+ }),
398
+ addCronJob: apiProcedure
399
+ .input(z.object({ chatId: z.string().optional(), job: CronJobSchema }))
400
+ .mutation(async ({ input, ctx }) => {
401
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
402
+ const settings = (await readChatSettings(chatId)) || {};
403
+ const cronJobs = settings.jobs ?? [];
404
+ const existingIndex = cronJobs.findIndex((j) => j.id === input.job.id);
405
+ if (existingIndex >= 0) {
406
+ cronJobs[existingIndex] = input.job;
407
+ } else {
408
+ cronJobs.push(input.job);
409
+ }
410
+ settings.jobs = cronJobs;
411
+ await writeChatSettings(chatId, settings);
412
+ cronManager.scheduleJob(chatId, input.job);
413
+ return { success: true };
414
+ }),
415
+ deleteCronJob: apiProcedure
416
+ .input(z.object({ chatId: z.string().optional(), id: z.string() }))
417
+ .mutation(async ({ input, ctx }) => {
418
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
419
+ const settings = await readChatSettings(chatId);
420
+ if (!settings || !settings.jobs) {
421
+ return { success: true, deleted: false };
422
+ }
423
+ const initialLength = settings.jobs.length;
424
+ settings.jobs = settings.jobs.filter((j) => j.id !== input.id);
425
+ if (settings.jobs.length !== initialLength) {
426
+ await writeChatSettings(chatId, settings);
427
+ cronManager.unscheduleJob(chatId, input.id);
428
+ return { success: true, deleted: true };
429
+ }
430
+ return { success: true, deleted: false };
431
+ }),
432
+ listPolicies: apiProcedure.query(async () => {
433
+ return await readPolicies();
434
+ }),
435
+ executePolicyHelp: apiProcedure
436
+ .input(z.object({ commandName: z.string() }))
437
+ .query(async ({ input }) => {
438
+ const config = await readPolicies();
439
+ const policy = config?.policies?.[input.commandName];
440
+
441
+ if (!policy) {
442
+ throw new TRPCError({
443
+ code: 'NOT_FOUND',
444
+ message: `Policy not found: ${input.commandName}`,
445
+ });
446
+ }
447
+
448
+ if (!policy.allowHelp) {
449
+ return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
450
+ }
451
+
452
+ const { executeSafe } = await import('./policy-utils.js');
453
+ const fullArgs = [...(policy.args || []), '--help'];
454
+ const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
455
+ cwd: getWorkspaceRoot(),
456
+ });
457
+
458
+ return { stdout, stderr, exitCode };
459
+ }),
460
+ createPolicyRequest: apiProcedure
461
+ .input(
462
+ z.object({
463
+ commandName: z.string(),
464
+ args: z.array(z.string()),
465
+ fileMappings: z.record(z.string(), z.string()),
466
+ chatId: z.string().optional(),
467
+ })
468
+ )
469
+ .mutation(async ({ input, ctx }) => {
470
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
471
+ const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
472
+ const store = new RequestStore(process.cwd());
473
+ const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
474
+ const service = new PolicyRequestService(store, agentDir, snapshotDir);
475
+
476
+ const chatId = await resolveAndCheckChatId(ctx, input.chatId);
477
+ const agentId = ctx.tokenPayload?.agentId ?? 'unknown';
478
+
479
+ const request = await service.createRequest(
480
+ input.commandName,
481
+ input.args,
482
+ input.fileMappings,
483
+ chatId,
484
+ agentId
485
+ );
486
+
487
+ const { generateRequestPreview } = await import('./policy-utils.js');
488
+ const previewContent = await generateRequestPreview(request);
489
+
490
+ const logMsg = {
491
+ id: (await import('node:crypto')).randomUUID(),
492
+ // TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
493
+ messageId: (await import('node:crypto')).randomUUID(),
494
+ role: 'log' as const,
495
+ source: 'router' as const,
496
+ content: previewContent,
497
+ stderr: '',
498
+ timestamp: new Date().toISOString(),
499
+ command: 'policy-request',
500
+ cwd: process.cwd(),
501
+ exitCode: 0,
502
+ };
503
+
504
+ await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
505
+ return request;
506
+ }),
507
+ });
508
+
509
+ export type AppRouter = typeof AppRouter;
510
+ export const appRouter = AppRouter;
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { slashCommand } from './slash-command.js';
3
+ import * as workspace from '../../shared/workspace.js';
4
+ import * as fsUtils from '../../shared/utils/fs.js';
5
+ import fs from 'node:fs/promises';
6
+
7
+ vi.mock('node:fs/promises');
8
+ vi.mock('../../shared/workspace.js');
9
+ vi.mock('../../shared/utils/fs.js');
10
+
11
+ describe('slashCommand router', () => {
12
+ beforeEach(() => {
13
+ vi.resetAllMocks();
14
+ vi.mocked(workspace.getClawminiDir).mockReturnValue('/mock/workspace/.clawmini');
15
+ });
16
+
17
+ it('should replace a matching slash command with .md file contents', async () => {
18
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
19
+ vi.mocked(fs.readFile).mockResolvedValue('Hello from command!\n');
20
+
21
+ const initialState = {
22
+ message: 'Please run /test for me',
23
+ messageId: 'mock-msg-id',
24
+ chatId: 'test-chat',
25
+ };
26
+
27
+ const newState = await slashCommand(initialState);
28
+
29
+ expect(fsUtils.pathIsInsideDir).toHaveBeenCalledWith(
30
+ '/mock/workspace/.clawmini/commands/test',
31
+ '/mock/workspace/.clawmini/commands'
32
+ );
33
+ expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.md', 'utf8');
34
+ expect(newState.message).toBe('Please run Hello from command! for me');
35
+ });
36
+
37
+ it('should fallback to .txt file contents if .md is not found', async () => {
38
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
39
+ vi.mocked(fs.readFile).mockImplementation(async (path) => {
40
+ if (path === '/mock/workspace/.clawmini/commands/test.txt')
41
+ return 'Hello from txt command!\n';
42
+ throw new Error('Not found');
43
+ });
44
+
45
+ const initialState = {
46
+ message: 'Please run /test for me',
47
+ messageId: 'mock-msg-id',
48
+ chatId: 'test-chat',
49
+ };
50
+
51
+ const newState = await slashCommand(initialState);
52
+
53
+ expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.md', 'utf8');
54
+ expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/test.txt', 'utf8');
55
+ expect(newState.message).toBe('Please run Hello from txt command! for me');
56
+ });
57
+
58
+ it('should handle multiple slash commands', async () => {
59
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
60
+ vi.mocked(fs.readFile).mockImplementation(async (path) => {
61
+ if (path === '/mock/workspace/.clawmini/commands/cmd1.md') return 'first';
62
+ if (path === '/mock/workspace/.clawmini/commands/cmd2.txt') return 'second';
63
+ throw new Error('Not found');
64
+ });
65
+
66
+ const initialState = {
67
+ message: '/cmd1 and /cmd2',
68
+ messageId: 'mock-msg-id',
69
+ chatId: 'test-chat',
70
+ };
71
+
72
+ const newState = await slashCommand(initialState);
73
+ expect(newState.message).toBe('first and second');
74
+ });
75
+
76
+ it('should leave the message unchanged if command file is not found', async () => {
77
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
78
+ vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
79
+
80
+ const initialState = {
81
+ message: 'Run /missing please',
82
+ messageId: 'mock-msg-id',
83
+ chatId: 'test-chat',
84
+ };
85
+
86
+ const newState = await slashCommand(initialState);
87
+ expect(newState.message).toBe('Run /missing please');
88
+ });
89
+
90
+ it('should prevent path traversal attacks', async () => {
91
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(false); // Simulate resolving outside
92
+
93
+ const initialState = {
94
+ message: 'Run /.. please',
95
+ messageId: 'mock-msg-id',
96
+ chatId: 'test-chat',
97
+ };
98
+
99
+ const newState = await slashCommand(initialState);
100
+
101
+ expect(fsUtils.pathIsInsideDir).toHaveBeenCalled();
102
+ expect(fs.readFile).not.toHaveBeenCalled();
103
+ expect(newState.message).toBe('Run /.. please');
104
+ });
105
+
106
+ it('should support colons in command names', async () => {
107
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
108
+ vi.mocked(fs.readFile).mockImplementation(async (path) => {
109
+ if (path === '/mock/workspace/.clawmini/commands/foo:bar.md') return 'colon command result';
110
+ throw new Error('Not found');
111
+ });
112
+
113
+ const initialState = {
114
+ message: '/foo:bar is cool',
115
+ messageId: 'mock-msg-id',
116
+ chatId: 'test-chat',
117
+ };
118
+
119
+ const newState = await slashCommand(initialState);
120
+
121
+ expect(fs.readFile).toHaveBeenCalledWith(
122
+ '/mock/workspace/.clawmini/commands/foo:bar.md',
123
+ 'utf8'
124
+ );
125
+ expect(newState.message).toBe('colon command result is cool');
126
+ });
127
+
128
+ it('should not match commands embedded in words', async () => {
129
+ const initialState = {
130
+ message: 'https://example.com/foo /bar',
131
+ messageId: 'mock-msg-id',
132
+ chatId: 'test-chat',
133
+ };
134
+
135
+ vi.mocked(fsUtils.pathIsInsideDir).mockReturnValue(true);
136
+ vi.mocked(fs.readFile).mockImplementation(async (path) => {
137
+ if (path === '/mock/workspace/.clawmini/commands/bar.md') return 'bar result';
138
+ throw new Error('Not found');
139
+ });
140
+
141
+ const newState = await slashCommand(initialState);
142
+ expect(fs.readFile).toHaveBeenCalledWith('/mock/workspace/.clawmini/commands/bar.md', 'utf8');
143
+ expect(newState.message).toBe('https://example.com/foo bar result');
144
+ });
145
+ });