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,103 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { RequestStore } from './request-store.js';
3
+ import type { PolicyRequest } from '../shared/policies.js';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import * as workspace from '../shared/workspace.js';
7
+
8
+ vi.mock('../shared/workspace.js', () => ({
9
+ getClawminiDir: vi.fn(),
10
+ }));
11
+
12
+ describe('RequestStore', () => {
13
+ const TEST_DIR = path.join(process.cwd(), '.test-requests');
14
+ let store: RequestStore;
15
+
16
+ beforeEach(async () => {
17
+ vi.mocked(workspace.getClawminiDir).mockReturnValue(TEST_DIR);
18
+ store = new RequestStore();
19
+ await fs.rm(TEST_DIR, { recursive: true, force: true });
20
+ await fs.mkdir(TEST_DIR, { recursive: true });
21
+ });
22
+
23
+ afterEach(async () => {
24
+ await fs.rm(TEST_DIR, { recursive: true, force: true });
25
+ vi.restoreAllMocks();
26
+ });
27
+
28
+ it('should save and load a request', async () => {
29
+ const req: PolicyRequest = {
30
+ id: 'req-123',
31
+ commandName: 'test-cmd',
32
+ args: ['arg1'],
33
+ fileMappings: {},
34
+ state: 'Pending',
35
+ createdAt: Date.now(),
36
+ chatId: 'chat-1',
37
+ agentId: 'agent-1',
38
+ };
39
+
40
+ await store.save(req);
41
+ const loaded = await store.load('req-123');
42
+ expect(loaded).toEqual(req);
43
+ });
44
+
45
+ it('should return null for non-existent request', async () => {
46
+ const loaded = await store.load('missing-req');
47
+ expect(loaded).toBeNull();
48
+ });
49
+
50
+ it('should list requests sorted by createdAt descending', async () => {
51
+ const req1: PolicyRequest = {
52
+ id: 'req-1',
53
+ commandName: 'cmd1',
54
+ args: [],
55
+ fileMappings: {},
56
+ state: 'Pending',
57
+ createdAt: 1000,
58
+ chatId: 'chat-1',
59
+ agentId: 'agent-1',
60
+ };
61
+ const req2: PolicyRequest = {
62
+ id: 'req-2',
63
+ commandName: 'cmd2',
64
+ args: [],
65
+ fileMappings: {},
66
+ state: 'Pending',
67
+ createdAt: 2000,
68
+ chatId: 'chat-2',
69
+ agentId: 'agent-2',
70
+ };
71
+
72
+ await store.save(req1);
73
+ await store.save(req2);
74
+
75
+ const list = await store.list();
76
+ expect(list).toHaveLength(2);
77
+ expect(list[0]?.id).toBe('req-2');
78
+ expect(list[1]?.id).toBe('req-1');
79
+ });
80
+
81
+ it('should gracefully handle corrupted files during load', async () => {
82
+ const req: PolicyRequest = {
83
+ id: 'req-good',
84
+ commandName: 'cmd',
85
+ args: [],
86
+ fileMappings: {},
87
+ state: 'Pending',
88
+ createdAt: 1000,
89
+ chatId: 'chat-1',
90
+ agentId: 'agent-1',
91
+ };
92
+ await store.save(req);
93
+
94
+ await fs.writeFile(path.join(TEST_DIR, 'tmp', 'requests', 'req-corrupt.json'), 'invalid json');
95
+
96
+ const loadedCorrupt = await store.load('req-corrupt');
97
+ expect(loadedCorrupt).toBeNull();
98
+
99
+ const list = await store.list();
100
+ expect(list).toHaveLength(1);
101
+ expect(list[0]?.id).toBe('req-good');
102
+ });
103
+ });
@@ -0,0 +1,96 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { z } from 'zod';
4
+ import { getClawminiDir } from '../shared/workspace.js';
5
+ import type { PolicyRequest } from '../shared/policies.js';
6
+ import { randomInt } from 'crypto';
7
+
8
+ const PolicyRequestSchema = z.object({
9
+ id: z.string(),
10
+ commandName: z.string(),
11
+ args: z.array(z.string()),
12
+ fileMappings: z.record(z.string(), z.string()),
13
+ state: z.enum(['Pending', 'Approved', 'Rejected']),
14
+ createdAt: z.number(),
15
+ rejectionReason: z.string().optional(),
16
+ chatId: z.string(),
17
+ agentId: z.string(),
18
+ });
19
+
20
+ function isENOENT(err: unknown): boolean {
21
+ return Boolean(
22
+ err && typeof err === 'object' && 'code' in err && (err as { code: string }).code === 'ENOENT'
23
+ );
24
+ }
25
+
26
+ export class RequestStore {
27
+ private baseDir: string;
28
+
29
+ constructor(startDir = process.cwd()) {
30
+ this.baseDir = path.join(getClawminiDir(startDir), 'tmp', 'requests');
31
+ }
32
+
33
+ async init(): Promise<void> {
34
+ await fs.mkdir(this.baseDir, { recursive: true });
35
+ }
36
+
37
+ private getFilePath(id: string): string {
38
+ return path.join(this.baseDir, `${id}.json`);
39
+ }
40
+
41
+ async save(request: PolicyRequest): Promise<void> {
42
+ await this.init();
43
+ const filePath = this.getFilePath(request.id);
44
+ await fs.writeFile(filePath, JSON.stringify(request, null, 2), 'utf8');
45
+ }
46
+
47
+ async load(id: string): Promise<PolicyRequest | null> {
48
+ const normalizedId = normalizePolicyId(id);
49
+ const filePath = this.getFilePath(normalizedId);
50
+ try {
51
+ const data = await fs.readFile(filePath, 'utf8');
52
+ return PolicyRequestSchema.parse(JSON.parse(data)) as PolicyRequest;
53
+ } catch (err: unknown) {
54
+ if (isENOENT(err)) {
55
+ return null;
56
+ }
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ console.warn(`Failed to parse request file ${filePath}:`, msg);
59
+ return null;
60
+ }
61
+ }
62
+
63
+ async list(): Promise<PolicyRequest[]> {
64
+ await this.init();
65
+ const requests: PolicyRequest[] = [];
66
+ try {
67
+ const files = await fs.readdir(this.baseDir);
68
+ for (const file of files) {
69
+ if (!file.endsWith('.json')) continue;
70
+ const id = path.basename(file, '.json');
71
+ const req = await this.load(id);
72
+ if (req) {
73
+ requests.push(req);
74
+ }
75
+ }
76
+ } catch (err: unknown) {
77
+ if (!isENOENT(err)) {
78
+ throw err;
79
+ }
80
+ }
81
+ return requests.sort((a, b) => b.createdAt - a.createdAt);
82
+ }
83
+ }
84
+
85
+ export function generateRandomAlphaNumericString(length: number): string {
86
+ const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
87
+ let result = '';
88
+ for (let i = 0; i < length; i++) {
89
+ result += characters[Math.floor(randomInt(characters.length))];
90
+ }
91
+ return result;
92
+ }
93
+
94
+ function normalizePolicyId(id: string): string {
95
+ return id.toLocaleUpperCase().trim();
96
+ }
@@ -0,0 +1,99 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { appRouter } from './router.js';
4
+ import * as chats from '../shared/chats.js';
5
+
6
+ vi.mock('../shared/chats.js', () => ({
7
+ getDefaultChatId: vi.fn().mockResolvedValue('default-chat'),
8
+ appendMessage: vi.fn().mockResolvedValue(undefined),
9
+ }));
10
+
11
+ vi.mock('../shared/workspace.js', () => ({
12
+ getWorkspaceRoot: vi.fn().mockReturnValue('/mock/workspace'),
13
+ getClawminiDir: vi.fn().mockReturnValue('/mock/.clawmini'),
14
+ }));
15
+
16
+ vi.mock('./policy-request-service.js', () => {
17
+ return {
18
+ PolicyRequestService: class {
19
+ async createRequest() {
20
+ return {
21
+ id: 'req-123',
22
+ commandName: 'test-cmd',
23
+ args: ['arg1', 'arg2'],
24
+ fileMappings: {
25
+ file1: '/mock/.clawmini/tmp/snapshots/file1.txt',
26
+ file2: '/mock/.clawmini/tmp/snapshots/file2.txt',
27
+ },
28
+ state: 'Pending',
29
+ createdAt: Date.now(),
30
+ chatId: 'chat-1',
31
+ agentId: 'agent-1',
32
+ };
33
+ }
34
+ },
35
+ };
36
+ });
37
+
38
+ const { mockReadFile } = vi.hoisted(() => {
39
+ return { mockReadFile: vi.fn() };
40
+ });
41
+
42
+ vi.mock('node:fs/promises', () => ({
43
+ default: {
44
+ readFile: mockReadFile,
45
+ },
46
+ readFile: mockReadFile,
47
+ }));
48
+
49
+ describe('createPolicyRequest preview message', () => {
50
+ beforeEach(() => {
51
+ vi.clearAllMocks();
52
+ });
53
+
54
+ it('should create a request and append a preview message truncating long files', async () => {
55
+ const caller = appRouter.createCaller({});
56
+
57
+ // file1 is short, file2 is long
58
+ const shortContent = 'Hello world!';
59
+ const longContent = 'A'.repeat(600);
60
+
61
+ mockReadFile.mockImplementation(async (filePath: any) => {
62
+ if (filePath.toString().includes('file1')) return shortContent;
63
+ if (filePath.toString().includes('file2')) return longContent;
64
+ return '';
65
+ });
66
+
67
+ const result = await caller.createPolicyRequest({
68
+ commandName: 'test-cmd',
69
+ args: ['arg1', 'arg2'],
70
+ fileMappings: {
71
+ file1: '/some/path1',
72
+ file2: '/some/path2',
73
+ },
74
+ });
75
+
76
+ expect(result.id).toBe('req-123');
77
+
78
+ expect(chats.appendMessage).toHaveBeenCalledTimes(1);
79
+ const callArgs = vi.mocked(chats.appendMessage).mock.calls[0]!;
80
+ const chatId = callArgs[0];
81
+ const logMsg = callArgs[1] as any;
82
+
83
+ expect(chatId).toBe('default-chat');
84
+ expect(logMsg.role).toBe('log');
85
+ expect(logMsg.command).toBe('policy-request');
86
+
87
+ // Assert preview content format
88
+ const content = logMsg.content;
89
+ expect(content).toContain('Sandbox Policy Request: test-cmd');
90
+ expect(content).toContain('ID: req-123');
91
+ expect(content).toContain('Args: arg1 arg2');
92
+
93
+ expect(content).toContain('File [file1]:\n' + shortContent);
94
+
95
+ // The long file should be truncated to 500 chars + suffix
96
+ expect(content).toContain('File [file2]:\n' + 'A'.repeat(500) + '\n... (truncated)');
97
+ expect(content).toContain('Use /approve req-123 or /reject req-123 [reason]');
98
+ });
99
+ });
@@ -0,0 +1,380 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { appRouter } from './router.js';
4
+ import * as workspace from '../shared/workspace.js';
5
+ import * as chats from '../shared/chats.js';
6
+ import type { CronJob } from '../shared/config.js';
7
+ import * as message from './message.js';
8
+ import * as fs from 'node:fs/promises';
9
+ import path from 'node:path';
10
+
11
+ vi.mock('node:fs/promises', async (importOriginal) => {
12
+ const actual = await importOriginal<typeof import('node:fs/promises')>();
13
+ return {
14
+ ...actual,
15
+ default: {
16
+ ...actual,
17
+ readFile: vi.fn(),
18
+ mkdir: vi.fn(),
19
+ stat: vi.fn(),
20
+ rename: vi.fn(),
21
+ copyFile: vi.fn(),
22
+ unlink: vi.fn(),
23
+ access: vi.fn(),
24
+ },
25
+ readFile: vi.fn(),
26
+ mkdir: vi.fn(),
27
+ stat: vi.fn(),
28
+ rename: vi.fn(),
29
+ copyFile: vi.fn(),
30
+ unlink: vi.fn(),
31
+ access: vi.fn(),
32
+ };
33
+ });
34
+
35
+ vi.mock('./message.js', () => ({
36
+ handleUserMessage: vi.fn(),
37
+ }));
38
+
39
+ vi.mock('../shared/workspace.js', async (importOriginal) => {
40
+ const actual = await importOriginal<typeof import('../shared/workspace.js')>();
41
+ return {
42
+ ...actual,
43
+ readChatSettings: vi.fn(),
44
+ writeChatSettings: vi.fn(),
45
+ getSettingsPath: vi.fn().mockReturnValue('/mock/settings.json'),
46
+ getAgent: vi.fn(),
47
+ getWorkspaceRoot: vi.fn().mockReturnValue(process.cwd()),
48
+ getActiveEnvironmentName: vi.fn().mockResolvedValue(null),
49
+ getActiveEnvironmentInfo: vi.fn().mockResolvedValue(null),
50
+ getEnvironmentPath: vi.fn().mockReturnValue(''),
51
+ readEnvironment: vi.fn().mockResolvedValue(null),
52
+ };
53
+ });
54
+
55
+ vi.mock('../shared/chats.js', async (importOriginal) => {
56
+ const actual = await importOriginal<typeof import('../shared/chats.js')>();
57
+ return {
58
+ ...actual,
59
+ getDefaultChatId: vi.fn(),
60
+ appendMessage: vi.fn(),
61
+ };
62
+ });
63
+
64
+ describe('Daemon TRPC Router', () => {
65
+ beforeEach(() => {
66
+ vi.clearAllMocks();
67
+ });
68
+
69
+ describe('Cron Jobs Endpoints', () => {
70
+ const mockJob: CronJob = {
71
+ id: 'job-1',
72
+ message: 'test message',
73
+ schedule: { cron: '* * * * *' },
74
+ };
75
+
76
+ it('listCronJobs should return empty array if no jobs exist', async () => {
77
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
78
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({});
79
+
80
+ const caller = appRouter.createCaller({});
81
+ const jobs = await caller.listCronJobs({});
82
+ expect(jobs).toEqual([]);
83
+ expect(workspace.readChatSettings).toHaveBeenCalledWith('default-chat');
84
+ });
85
+
86
+ it('listCronJobs should return existing jobs', async () => {
87
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
88
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ jobs: [mockJob] });
89
+
90
+ const caller = appRouter.createCaller({});
91
+ const jobs = await caller.listCronJobs({ chatId: 'custom-chat' });
92
+ expect(jobs).toEqual([mockJob]);
93
+ expect(workspace.readChatSettings).toHaveBeenCalledWith('custom-chat');
94
+ });
95
+
96
+ it('addCronJob should add a new job', async () => {
97
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
98
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({});
99
+
100
+ const caller = appRouter.createCaller({});
101
+ const result = await caller.addCronJob({ job: mockJob });
102
+
103
+ expect(result.success).toBe(true);
104
+ expect(workspace.writeChatSettings).toHaveBeenCalledWith('default-chat', {
105
+ jobs: [mockJob],
106
+ });
107
+ });
108
+
109
+ it('addCronJob should update an existing job', async () => {
110
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
111
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ jobs: [mockJob] });
112
+
113
+ const caller = appRouter.createCaller({});
114
+ const updatedJob = { ...mockJob, message: 'updated' };
115
+ const result = await caller.addCronJob({ job: updatedJob });
116
+
117
+ expect(result.success).toBe(true);
118
+ expect(workspace.writeChatSettings).toHaveBeenCalledWith('default-chat', {
119
+ jobs: [updatedJob],
120
+ });
121
+ });
122
+
123
+ it('deleteCronJob should delete an existing job', async () => {
124
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
125
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ jobs: [mockJob] });
126
+
127
+ const caller = appRouter.createCaller({});
128
+ const result = await caller.deleteCronJob({ id: 'job-1' });
129
+
130
+ expect(result.success).toBe(true);
131
+ expect(result.deleted).toBe(true);
132
+ expect(workspace.writeChatSettings).toHaveBeenCalledWith('default-chat', { jobs: [] });
133
+ });
134
+
135
+ it('deleteCronJob should return deleted: false if job not found', async () => {
136
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
137
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ jobs: [mockJob] });
138
+
139
+ const caller = appRouter.createCaller({});
140
+ const result = await caller.deleteCronJob({ id: 'non-existent' });
141
+
142
+ expect(result.success).toBe(true);
143
+ expect(result.deleted).toBe(false);
144
+ expect(workspace.writeChatSettings).not.toHaveBeenCalled();
145
+ });
146
+ });
147
+
148
+ describe('sendMessage with files processing', () => {
149
+ it('should pass message through when no files are provided', async () => {
150
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
151
+ vi.mocked((fs as any).default.readFile).mockResolvedValue('{}');
152
+
153
+ const caller = appRouter.createCaller({});
154
+ await caller.sendMessage({
155
+ type: 'send-message',
156
+ client: 'cli',
157
+ data: { message: 'hello', chatId: 'default-chat' },
158
+ });
159
+
160
+ expect(message.handleUserMessage).toHaveBeenCalledWith(
161
+ 'default-chat',
162
+ 'hello',
163
+ {},
164
+ undefined,
165
+ false,
166
+ expect.any(Function),
167
+ undefined,
168
+ undefined
169
+ );
170
+ expect((fs as any).default.mkdir).not.toHaveBeenCalled();
171
+ });
172
+
173
+ it('should process files and format message correctly', async () => {
174
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
175
+ vi.mocked((fs as any).default.readFile).mockResolvedValue('{}');
176
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ defaultAgent: 'default' });
177
+ vi.mocked((fs as any).default.stat).mockRejectedValue(new Error('not found')); // Files do not exist (no collision)
178
+ vi.mocked((fs as any).default.rename).mockResolvedValue(undefined);
179
+ vi.mocked((fs as any).default.access).mockResolvedValue(undefined);
180
+
181
+ const caller = appRouter.createCaller({});
182
+ await caller.sendMessage({
183
+ type: 'send-message',
184
+ client: 'cli',
185
+ data: {
186
+ message: 'hello',
187
+ chatId: 'default-chat',
188
+ files: ['.clawmini/tmp/file1.txt', '.clawmini/tmp/file2.png'],
189
+ adapter: 'discord',
190
+ },
191
+ });
192
+
193
+ expect((fs as any).default.mkdir).toHaveBeenCalledWith(
194
+ expect.stringContaining(path.join('attachments', 'discord')),
195
+ { recursive: true }
196
+ );
197
+ expect((fs as any).default.rename).toHaveBeenCalledTimes(2);
198
+
199
+ const handleUserMessageCall = vi.mocked(message.handleUserMessage).mock.calls[0];
200
+ expect(handleUserMessageCall).toBeDefined();
201
+ const formattedMessage = handleUserMessageCall![1];
202
+ expect(formattedMessage).toContain('Attached files:');
203
+ expect(formattedMessage).toContain('- ' + path.normalize('attachments/discord/file1.txt'));
204
+ expect(formattedMessage).toContain('- ' + path.normalize('attachments/discord/file2.png'));
205
+ });
206
+
207
+ it('should handle file collision by appending timestamp', async () => {
208
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
209
+ vi.mocked((fs as any).default.readFile).mockResolvedValue('{}');
210
+ vi.mocked(workspace.readChatSettings).mockResolvedValue({ defaultAgent: 'default' });
211
+ vi.mocked((fs as any).default.access).mockResolvedValue(undefined);
212
+
213
+ // Simulate file already exists for collision
214
+ vi.mocked((fs as any).default.stat)
215
+ .mockResolvedValueOnce({} as import('node:fs').Stats)
216
+ .mockRejectedValue(new Error('not found'));
217
+ vi.mocked((fs as any).default.rename).mockResolvedValue(undefined);
218
+
219
+ const caller = appRouter.createCaller({});
220
+ await caller.sendMessage({
221
+ type: 'send-message',
222
+ client: 'cli',
223
+ data: {
224
+ message: 'hello',
225
+ chatId: 'default-chat',
226
+ files: ['.clawmini/tmp/file1.txt'],
227
+ adapter: 'discord',
228
+ },
229
+ });
230
+
231
+ expect((fs as any).default.rename).toHaveBeenCalledWith(
232
+ '.clawmini/tmp/file1.txt',
233
+ expect.stringMatching(/file1-\d+\.txt$/)
234
+ );
235
+
236
+ const handleUserMessageCall = vi.mocked(message.handleUserMessage).mock.calls[0];
237
+ expect(handleUserMessageCall).toBeDefined();
238
+ const formattedMessage = handleUserMessageCall![1];
239
+ expect(formattedMessage).toMatch(/- .*file1-\d+\.txt/);
240
+ });
241
+
242
+ it('should reject file path outside .clawmini/tmp for sendMessage', async () => {
243
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
244
+ vi.mocked((fs as any).default.readFile).mockResolvedValue('{}');
245
+
246
+ const caller = appRouter.createCaller({});
247
+ await expect(
248
+ caller.sendMessage({
249
+ type: 'send-message',
250
+ client: 'cli',
251
+ data: {
252
+ message: 'hello',
253
+ chatId: 'default-chat',
254
+ files: ['/etc/passwd'],
255
+ },
256
+ })
257
+ ).rejects.toThrow('File must be inside the temporary directory.');
258
+ });
259
+
260
+ it('should reject non-existent file for sendMessage', async () => {
261
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
262
+ vi.mocked((fs as any).default.readFile).mockResolvedValue('{}');
263
+ vi.mocked((fs as any).default.access).mockRejectedValue(new Error('ENOENT'));
264
+
265
+ const caller = appRouter.createCaller({});
266
+ await expect(
267
+ caller.sendMessage({
268
+ type: 'send-message',
269
+ client: 'cli',
270
+ data: {
271
+ message: 'hello',
272
+ chatId: 'default-chat',
273
+ files: ['.clawmini/tmp/missing.txt'],
274
+ },
275
+ })
276
+ ).rejects.toThrow('File does not exist: .clawmini/tmp/missing.txt');
277
+ });
278
+ });
279
+
280
+ describe('logMessage', () => {
281
+ it('should save a log message without a file', async () => {
282
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
283
+ vi.mocked(chats.appendMessage).mockResolvedValue(undefined);
284
+
285
+ const caller = appRouter.createCaller({});
286
+ const result = await caller.logMessage({
287
+ chatId: 'default-chat',
288
+ message: 'Test log',
289
+ });
290
+
291
+ expect(result.success).toBe(true);
292
+ expect(chats.appendMessage).toHaveBeenCalledWith(
293
+ 'default-chat',
294
+ expect.objectContaining({
295
+ role: 'log',
296
+ content: 'Test log',
297
+ }),
298
+ expect.any(String)
299
+ );
300
+ });
301
+
302
+ it('should validate and save a log message with a valid file path', async () => {
303
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
304
+ vi.mocked(chats.appendMessage).mockResolvedValue(undefined);
305
+ vi.mocked((fs as any).default.access).mockResolvedValue(undefined);
306
+
307
+ const caller = appRouter.createCaller({});
308
+ const result = await caller.logMessage({
309
+ chatId: 'default-chat',
310
+ message: 'Test log with file',
311
+ files: ['attachments/discord/image.png'],
312
+ });
313
+
314
+ expect(result.success).toBe(true);
315
+ expect(chats.appendMessage).toHaveBeenCalledWith(
316
+ 'default-chat',
317
+ expect.objectContaining({
318
+ role: 'log',
319
+ content: 'Test log with file',
320
+ files: [path.normalize('attachments/discord/image.png')],
321
+ }),
322
+ expect.any(String)
323
+ );
324
+ });
325
+ it('should reject file path with directory traversal (..)', async () => {
326
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
327
+
328
+ const caller = appRouter.createCaller({});
329
+ await expect(
330
+ caller.logMessage({
331
+ chatId: 'default-chat',
332
+ message: 'Malicious log',
333
+ files: ['../secret.txt'],
334
+ })
335
+ ).rejects.toThrow('File must be within the agent workspace.');
336
+ });
337
+
338
+ it('should reject file path with absolute path', async () => {
339
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
340
+
341
+ const caller = appRouter.createCaller({});
342
+ await expect(
343
+ caller.logMessage({
344
+ chatId: 'default-chat',
345
+ message: 'Malicious log',
346
+ files: ['/etc/passwd'],
347
+ })
348
+ ).rejects.toThrow('File must be within the agent workspace.');
349
+ });
350
+ });
351
+
352
+ describe('Subscriptions', () => {
353
+ it('waitForTyping should yield typing events for the correct chatId', async () => {
354
+ vi.mocked(chats.getDefaultChatId).mockResolvedValue('default-chat');
355
+
356
+ const caller = appRouter.createCaller({});
357
+ const iterable = await caller.waitForTyping({ chatId: 'default-chat' });
358
+ const iterator = iterable[Symbol.asyncIterator]();
359
+
360
+ const events: any[] = [];
361
+ const iteratePromise = (async () => {
362
+ const e1 = await iterator.next();
363
+ if (e1.value) events.push(e1.value);
364
+ const e2 = await iterator.next();
365
+ if (e2.value) events.push(e2.value);
366
+ })();
367
+
368
+ const { daemonEvents, DAEMON_EVENT_TYPING } = await import('./events.js');
369
+ await new Promise((resolve) => setTimeout(resolve, 10));
370
+
371
+ daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId: 'default-chat' });
372
+ daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId: 'other-chat' });
373
+ daemonEvents.emit(DAEMON_EVENT_TYPING, { chatId: 'default-chat' });
374
+
375
+ await iteratePromise;
376
+
377
+ expect(events).toEqual([{ chatId: 'default-chat' }, { chatId: 'default-chat' }]);
378
+ });
379
+ });
380
+ });