clawmini 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.Dr0ot9sV.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +42 -13
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -176
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +127 -137
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{G_zz-Gou.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CYS8iApT.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.Dr0ot9sV.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BBGQ_i84.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  328. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  329. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  331. package/dist/web/_app/immutable/chunks/bBmtyQMj.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.CJmSwntr.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/bBmtyQMj.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CJmSwntr.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.ZpUrT2ak.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.Bli0Hqzn.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.oBhvQhcA.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -19,13 +19,28 @@ vi.mock('./state.js', () => ({
19
19
  updateDiscordState: vi.fn().mockResolvedValue(undefined),
20
20
  }));
21
21
 
22
+ const mockRestPut = vi.fn().mockResolvedValue(true);
23
+
22
24
  vi.mock('discord.js', () => {
23
25
  return {
26
+ REST: class {
27
+ setToken = vi.fn().mockReturnThis();
28
+ put = mockRestPut;
29
+ },
30
+ Routes: {
31
+ applicationCommands: vi.fn().mockReturnValue('/mock/commands'),
32
+ },
24
33
  Client: class {
25
34
  constructor() {
26
35
  return mockClientInstance;
27
36
  }
28
37
  },
38
+ SlashCommandBuilder: class {
39
+ setName = vi.fn().mockReturnThis();
40
+ setDescription = vi.fn().mockReturnThis();
41
+ addStringOption = vi.fn().mockReturnThis();
42
+ toJSON = vi.fn().mockReturnValue({ name: 'mocked_command' });
43
+ },
29
44
  Events: {
30
45
  ClientReady: 'ready',
31
46
  MessageCreate: 'messageCreate',
@@ -79,6 +94,12 @@ describe('Discord Adapter Entry Point', () => {
79
94
  sendMessage: {
80
95
  mutate: vi.fn().mockResolvedValue({ success: true }),
81
96
  },
97
+ waitForMessages: {
98
+ subscribe: vi.fn(),
99
+ },
100
+ waitForTyping: {
101
+ subscribe: vi.fn(),
102
+ },
82
103
  } as unknown as ReturnType<typeof import('./client.js').getTRPCClient>;
83
104
  vi.mocked(getTRPCClient).mockReturnValue(mockTrpc);
84
105
  vi.mocked(readDiscordConfig).mockResolvedValue({
@@ -94,6 +115,36 @@ describe('Discord Adapter Entry Point', () => {
94
115
  vi.mocked(mockClientInstance.once).mockReturnValue(mockClientInstance);
95
116
  });
96
117
 
118
+ it('should register slash commands on ClientReady', async () => {
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ let readyHandler: ((client: any) => Promise<void>) | undefined;
121
+ vi.mocked(mockClientInstance.once).mockImplementation(
122
+ (event: string, cb: (...args: unknown[]) => void) => {
123
+ if (event === 'ready') {
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ readyHandler = cb as any;
126
+ }
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ return mockClientInstance as any;
129
+ }
130
+ );
131
+
132
+ const { main } = await import('./index.js');
133
+ await main();
134
+
135
+ expect(readyHandler).toBeDefined();
136
+
137
+ if (readyHandler) {
138
+ await readyHandler({ user: { id: 'bot-id', tag: 'bot#1234' } });
139
+ }
140
+
141
+ const { slashCommands } = await import('./commands.js');
142
+
143
+ expect(mockRestPut).toHaveBeenCalledWith('/mock/commands', {
144
+ body: slashCommands.map((cmd) => cmd.toJSON()),
145
+ });
146
+ });
147
+
97
148
  it('should initialize Discord config and exit if init argument is provided', async () => {
98
149
  process.argv = ['node', 'index.js', 'init'];
99
150
  const { initDiscordConfig } = await import('./config.js');
@@ -208,6 +259,7 @@ describe('Discord Adapter Entry Point', () => {
208
259
  files: undefined,
209
260
  adapter: 'discord',
210
261
  noWait: true,
262
+ externalRef: 'msg-1',
211
263
  },
212
264
  });
213
265
  expect(mockTrpc.sendMessage.mutate).toHaveBeenNthCalledWith(2, {
@@ -219,6 +271,7 @@ describe('Discord Adapter Entry Point', () => {
219
271
  files: undefined,
220
272
  adapter: 'discord',
221
273
  noWait: true,
274
+ externalRef: 'msg-2',
222
275
  },
223
276
  });
224
277
  });
@@ -484,7 +537,12 @@ describe('Discord Adapter Entry Point', () => {
484
537
  await vi.runAllTimersAsync();
485
538
 
486
539
  expect(global.fetch).not.toHaveBeenCalled();
487
- expect(replyMock).toHaveBeenCalledWith(expect.stringContaining('exceeds the size limit'));
540
+ expect(replyMock).toHaveBeenCalledWith(
541
+ expect.objectContaining({
542
+ content: expect.stringContaining('exceeds the size limit'),
543
+ allowedMentions: { parse: [] },
544
+ })
545
+ );
488
546
 
489
547
  expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith({
490
548
  type: 'send-message',
@@ -523,6 +581,7 @@ describe('Discord Adapter Entry Point', () => {
523
581
 
524
582
  const mockReferencedMessage = {
525
583
  content: 'Would anyone like to get dinner Sunday?\nOr maybe lunch?',
584
+ author: { id: 'other-user', username: 'other_user' },
526
585
  };
527
586
 
528
587
  if (messageHandler) {
@@ -545,7 +604,8 @@ describe('Discord Adapter Entry Point', () => {
545
604
  type: 'send-message',
546
605
  client: 'cli',
547
606
  data: {
548
- message: "> Would anyone like to get dinner Sunday?\n> Or maybe lunch?\nYes, I'm in!",
607
+ message:
608
+ "> **other_user said:**\n> Would anyone like to get dinner Sunday?\n> Or maybe lunch?\n\nYes, I'm in!",
549
609
  chatId: 'default',
550
610
  files: undefined,
551
611
  adapter: 'discord',
@@ -580,6 +640,7 @@ describe('Discord Adapter Entry Point', () => {
580
640
  const mockInteraction = {
581
641
  isButton: () => false,
582
642
  isModalSubmit: () => false,
643
+ isChatInputCommand: () => false,
583
644
  };
584
645
  if (interactionHandler) await interactionHandler(mockInteraction);
585
646
  expect(mockTrpc.sendMessage.mutate).not.toHaveBeenCalled();
@@ -591,6 +652,7 @@ describe('Discord Adapter Entry Point', () => {
591
652
  const mockInteraction = {
592
653
  isButton: () => true,
593
654
  isModalSubmit: () => false,
655
+ isChatInputCommand: () => false,
594
656
  isRepliable: () => true,
595
657
  user: { id: 'unauth' },
596
658
  reply: vi.fn(),
@@ -607,6 +669,7 @@ describe('Discord Adapter Entry Point', () => {
607
669
  const mockInteraction = {
608
670
  isButton: () => true,
609
671
  isModalSubmit: () => false,
672
+ isChatInputCommand: () => false,
610
673
  user: { id: 'user-123' },
611
674
  customId: 'approve_123',
612
675
  update: vi.fn(),
@@ -634,6 +697,7 @@ describe('Discord Adapter Entry Point', () => {
634
697
  const mockInteraction = {
635
698
  isButton: () => true,
636
699
  isModalSubmit: () => false,
700
+ isChatInputCommand: () => false,
637
701
  user: { id: 'user-123' },
638
702
  customId: 'reject_123',
639
703
  showModal: vi.fn(),
@@ -646,6 +710,7 @@ describe('Discord Adapter Entry Point', () => {
646
710
  const mockInteraction = {
647
711
  isButton: () => false,
648
712
  isModalSubmit: () => true,
713
+ isChatInputCommand: () => false,
649
714
  isFromMessage: () => true,
650
715
  user: { id: 'user-123' },
651
716
  customId: 'modal_reject_123',
@@ -1,17 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Client, Events, GatewayIntentBits, Partials } from 'discord.js';
4
- import { readDiscordConfig, isAuthorized, initDiscordConfig } from './config.js';
5
- import { readDiscordState, updateDiscordState } from './state.js';
3
+ import { Client, Events, GatewayIntentBits, Partials, REST, Routes } from 'discord.js';
4
+ import { readDiscordConfig, initDiscordConfig } from './config.js';
5
+ import { readDiscordState } from './state.js';
6
6
  import { handleDiscordInteraction } from './interactions.js';
7
7
  import { getTRPCClient } from './client.js';
8
8
  import { startDaemonToDiscordForwarder } from './forwarder.js';
9
- import { getClawminiDir } from '../shared/workspace.js';
10
- import { handleAdapterCommand, type CommandTrpcClient } from '../shared/adapters/commands.js';
11
- import { formatMessage, type FilteringConfig } from '../shared/adapters/filtering.js';
12
- import fs from 'node:fs/promises';
13
- import path from 'node:path';
14
- import { handleRoutingCommand, type RoutingTrpcClient } from '../shared/adapters/routing.js';
9
+ import { slashCommands } from './commands.js';
10
+ import { type CommandTrpcClient } from '../shared/adapters/commands.js';
11
+ import { type FilteringConfig } from '../shared/adapters/filtering.js';
12
+
13
+ import { processDiscordMessage } from './processMessage.js';
15
14
 
16
15
  export async function main() {
17
16
  const args = process.argv.slice(2);
@@ -46,238 +45,107 @@ export async function main() {
46
45
  const state = await readDiscordState();
47
46
  const filteringConfig: FilteringConfig = { filters: state.filters };
48
47
 
49
- client.once(Events.ClientReady, (readyClient) => {
48
+ client.once(Events.ClientReady, async (readyClient) => {
50
49
  console.log(`Ready! Logged in as ${readyClient.user.tag}`);
51
50
 
51
+ // Workaround: pre-cache the authorized user's DM channel so inbound
52
+ // DMs aren't dropped before they reach the MessageCreate handler.
53
+ // discord.js >= 14.26 can't construct a partial DMChannel from a
54
+ // MESSAGE_CREATE payload (no `type`, no `recipients`), and the
55
+ // ChannelManager silently drops the dispatch when the channel isn't
56
+ // already in cache. Opening the DM here populates the cache so later
57
+ // events short-circuit to the existing entry.
58
+ // See: https://github.com/discordjs/discord.js/issues/11486
59
+ try {
60
+ const user = await readyClient.users.fetch(config.authorizedUserId);
61
+ await user.createDM();
62
+ } catch (err) {
63
+ console.error(
64
+ `Failed to pre-cache DM channel for authorized user ${config.authorizedUserId}:`,
65
+ err
66
+ );
67
+ }
68
+
69
+ try {
70
+ const rest = new REST({ version: '10' }).setToken(config.botToken);
71
+ console.log('Started refreshing application (/) commands.');
72
+ await rest.put(Routes.applicationCommands(readyClient.user.id), {
73
+ body: slashCommands.map((cmd) => cmd.toJSON()),
74
+ });
75
+ console.log('Successfully reloaded application (/) commands.');
76
+ } catch (error) {
77
+ console.error('Error registering slash commands:', error);
78
+ }
79
+
52
80
  // Start forwarding from daemon to Discord
53
81
  startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, {
54
82
  chatId: config.chatId,
55
83
  config: filteringConfig,
84
+ discordConfig: config,
56
85
  }).catch((error) => {
57
86
  console.error('Error in daemon-to-discord forwarder:', error);
58
87
  });
59
88
  });
60
89
 
61
90
  client.on(Events.MessageCreate, async (message) => {
62
- // Ignore messages from the bot itself
63
- if (message.author.id === client.user?.id) return;
64
- if (message.author.bot) return;
65
-
66
- const externalContextId = message.channelId;
67
- const currentState = await readDiscordState();
68
- const mappedChatId = currentState.channelChatMap?.[externalContextId]?.chatId;
69
- const isRoutingCommand =
70
- message.content.startsWith('/chat') || message.content.startsWith('/agent');
71
-
72
- // Enforce requireMention config for guild messages
73
- if (message.guild) {
74
- const channelConfig = currentState.channelChatMap?.[externalContextId];
75
- const requiresMention =
76
- channelConfig?.requireMention !== undefined
77
- ? channelConfig.requireMention
78
- : config.requireMention;
79
-
80
- if (requiresMention) {
81
- const isMentioned = message.mentions.has(client.user!.id);
82
- let isReplyToBot = false;
91
+ let isReplyToBot = false;
92
+ let referenceContent: string | undefined;
93
+ let referenceAuthor: string | undefined;
83
94
 
84
- if (message.reference && message.reference.messageId) {
85
- try {
86
- const referencedMessage = await message.channel.messages.fetch(
87
- message.reference.messageId
88
- );
89
- isReplyToBot = referencedMessage.author.id === client.user!.id;
90
- } catch (err) {
91
- console.error('Failed to fetch referenced message for mention check:', err);
95
+ if (message.reference && message.reference.messageId) {
96
+ try {
97
+ const referencedMessage = await message.fetchReference();
98
+ isReplyToBot = referencedMessage?.author.id === client.user!.id;
99
+ referenceContent = referencedMessage?.content;
100
+ if (referencedMessage) {
101
+ if (referencedMessage.author.bot) {
102
+ referenceAuthor = 'Assistant';
103
+ } else if (referencedMessage.author.id !== config.authorizedUserId) {
104
+ referenceAuthor = referencedMessage.author.username;
92
105
  }
93
106
  }
94
-
95
- if (!isMentioned && !isReplyToBot) {
96
- return;
97
- }
98
- }
99
- }
100
-
101
- // Check if the user is authorized
102
- if (!isAuthorized(message.author.id, config.authorizedUserId)) {
103
- console.log(
104
- `Unauthorized message from ${message.author.tag} (${message.author.id}) ignored.`
105
- );
106
- return;
107
- }
108
-
109
- console.log(`Received message from ${message.author.tag}: ${message.content}`);
110
-
111
- if (isRoutingCommand) {
112
- const stringChatMap = Object.fromEntries(
113
- Object.entries(currentState.channelChatMap || {}).map(([k, v]) => [k, v.chatId || ''])
114
- );
115
- const routingResult = await handleRoutingCommand(
116
- message.content,
117
- externalContextId,
118
- stringChatMap,
119
- 'discord',
120
- trpc as unknown as RoutingTrpcClient
121
- );
122
-
123
- if (routingResult) {
124
- if (routingResult.type === 'mapped') {
125
- await updateDiscordState((latestState) => ({
126
- channelChatMap: {
127
- ...(latestState.channelChatMap || {}),
128
- [externalContextId]: {
129
- ...(latestState.channelChatMap?.[externalContextId] || {}),
130
- chatId: routingResult.newChatId,
131
- },
132
- },
133
- }));
134
- }
135
- await message.reply(routingResult.text);
136
- return;
137
- }
138
- }
139
-
140
- let targetChatId = mappedChatId;
141
-
142
- if (!targetChatId && !isRoutingCommand) {
143
- const isFirstEverMessage =
144
- !currentState.channelChatMap ||
145
- Object.values(currentState.channelChatMap).every((entry) => !entry.chatId);
146
-
147
- if (isFirstEverMessage) {
148
- targetChatId = config.chatId || 'default';
149
- console.log(
150
- `First contact detected. Automatically mapping channel ${externalContextId} to chat ${targetChatId}.`
151
- );
152
- await updateDiscordState((latestState) => ({
153
- channelChatMap: {
154
- ...(latestState.channelChatMap || {}),
155
- [externalContextId]: {
156
- ...(latestState.channelChatMap?.[externalContextId] || {}),
157
- chatId: targetChatId as string,
158
- },
159
- },
160
- }));
161
- } else {
162
- const isDirectMessage = !message.guild;
163
- const isMentioned = message.mentions.has(client.user!.id);
164
- const isSlashCommand = message.content.startsWith('/');
165
- if (isDirectMessage || isMentioned || isSlashCommand) {
166
- console.log(`Unmapped channel ${externalContextId}, sending first contact warning.`);
167
- await message.reply(
168
- 'This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it.'
169
- );
170
- } else {
171
- console.log(
172
- `Unmapped channel ${externalContextId}, silently ignoring background message.`
173
- );
174
- }
175
- return;
107
+ } catch (err) {
108
+ console.error('Failed to fetch referenced message for mention check:', err);
176
109
  }
177
110
  }
178
111
 
179
- // Fallback typing safeguard
180
- if (!targetChatId) targetChatId = config.chatId || 'default';
112
+ const attachments = message.attachments
113
+ ? Array.from(message.attachments.values()).map((att) => ({
114
+ name: att.name,
115
+ size: att.size,
116
+ url: att.url,
117
+ }))
118
+ : [];
181
119
 
182
- const commandResult = await handleAdapterCommand(
120
+ await processDiscordMessage(
183
121
  message.content,
122
+ message.author,
123
+ message.channelId,
124
+ message.guild,
125
+ async (text) => {
126
+ await message.reply({ content: text, allowedMentions: { parse: [] } });
127
+ },
128
+ config,
129
+ trpc,
184
130
  filteringConfig,
185
- trpc as unknown as CommandTrpcClient,
186
- targetChatId
187
- );
188
-
189
- if (commandResult) {
190
- if (commandResult.type === 'text') {
191
- if (commandResult.newConfig) {
192
- filteringConfig.filters = commandResult.newConfig.filters;
193
- await updateDiscordState({ filters: filteringConfig.filters });
194
- }
195
- await message.reply(commandResult.text);
196
- } else if (commandResult.type === 'debug') {
197
- const formatted =
198
- commandResult.messages.length === 0
199
- ? 'No ignored background messages found.'
200
- : `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` +
201
- commandResult.messages.map((msg) => formatMessage(msg)).join('\n\n---\n\n');
202
- await message.reply(formatted);
131
+ {
132
+ mentionsBot: !!message.mentions?.has(client.user!.id),
133
+ isReplyToBot,
134
+ attachments,
135
+ messageId: message.id,
136
+ ...(referenceContent ? { referenceContent } : {}),
137
+ ...(referenceAuthor ? { referenceAuthor } : {}),
203
138
  }
204
- return;
205
- }
206
-
207
- const downloadedFiles: string[] = [];
208
- if (message.attachments.size > 0) {
209
- const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'discord');
210
- await fs.mkdir(tmpDir, { recursive: true });
211
- const maxSizeMB = config.maxAttachmentSizeMB ?? 25;
212
- const maxSizeBytes = maxSizeMB * 1024 * 1024;
213
-
214
- for (const attachment of message.attachments.values()) {
215
- if (attachment.size > maxSizeBytes) {
216
- console.warn(
217
- `Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`
218
- );
219
- await message.reply(
220
- `Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`
221
- );
222
- continue;
223
- }
224
-
225
- try {
226
- const res = await fetch(attachment.url);
227
- if (!res.ok) {
228
- console.error(`Failed to download attachment ${attachment.name}`);
229
- continue;
230
- }
231
-
232
- const uniqueName = `${Date.now()}-${attachment.name}`;
233
- const filePath = path.join(tmpDir, uniqueName);
234
- const arrayBuffer = await res.arrayBuffer();
235
- await fs.writeFile(filePath, Buffer.from(arrayBuffer));
236
- downloadedFiles.push(filePath);
237
- } catch (err) {
238
- console.error(`Error downloading attachment ${attachment.name}:`, err);
239
- }
240
- }
241
- }
242
-
243
- let finalContent = message.content;
244
-
245
- if (message.reference && message.reference.messageId) {
246
- try {
247
- const referencedMessage = await message.fetchReference();
248
- if (referencedMessage && referencedMessage.content) {
249
- const quotedContent = referencedMessage.content
250
- .split('\n')
251
- .map((line) => `> ${line}`)
252
- .join('\n');
253
- finalContent = `${quotedContent}\n${finalContent}`;
254
- }
255
- } catch (err) {
256
- console.error('Failed to fetch referenced message:', err);
257
- }
258
- }
259
-
260
- console.log(`Forwarding message to daemon: ${finalContent}`);
261
- try {
262
- await trpc.sendMessage.mutate({
263
- type: 'send-message',
264
- client: 'cli',
265
- data: {
266
- message: finalContent,
267
- chatId: targetChatId,
268
- files: downloadedFiles.length > 0 ? downloadedFiles : undefined,
269
- adapter: 'discord',
270
- noWait: true,
271
- },
272
- });
273
- console.log('Message forwarded to daemon successfully.');
274
- } catch (error) {
275
- console.error('Failed to forward message to daemon:', error);
276
- }
139
+ );
277
140
  });
278
141
 
279
142
  client.on(Events.InteractionCreate, async (interaction) => {
280
- await handleDiscordInteraction(interaction, config, trpc as unknown as CommandTrpcClient);
143
+ await handleDiscordInteraction(
144
+ interaction,
145
+ config,
146
+ trpc as unknown as CommandTrpcClient,
147
+ filteringConfig
148
+ );
281
149
  });
282
150
 
283
151
  try {
@@ -22,11 +22,16 @@ describe('handleDiscordInteraction', () => {
22
22
  mockInteraction = {
23
23
  isButton: vi.fn().mockReturnValue(true),
24
24
  isModalSubmit: vi.fn().mockReturnValue(false),
25
+ isChatInputCommand: vi.fn().mockReturnValue(false),
26
+ isRepliable: vi.fn().mockReturnValue(true),
25
27
  user: { id: 'user-1' },
26
28
  customId: '',
27
29
  channelId: 'channel-1',
28
30
  update: vi.fn().mockResolvedValue({}),
29
31
  followUp: vi.fn().mockResolvedValue({}),
32
+ reply: vi.fn().mockResolvedValue({}),
33
+ deferReply: vi.fn().mockResolvedValue({}),
34
+ deleteReply: vi.fn().mockResolvedValue({}),
30
35
  showModal: vi.fn().mockResolvedValue({}),
31
36
  };
32
37
  vi.mocked(readDiscordState).mockResolvedValue({});
@@ -43,7 +48,7 @@ describe('handleDiscordInteraction', () => {
43
48
 
44
49
  it('routes approve to explicit chat if provided', async () => {
45
50
  mockInteraction.customId = 'approve|policy-1|explicit-chat';
46
- await handleDiscordInteraction(mockInteraction, config, mockTrpc);
51
+ await handleDiscordInteraction(mockInteraction, config, mockTrpc, { filters: {} });
47
52
 
48
53
  expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
49
54
  expect.objectContaining({
@@ -59,7 +64,7 @@ describe('handleDiscordInteraction', () => {
59
64
  vi.mocked(readDiscordState).mockResolvedValue({
60
65
  channelChatMap: { 'channel-1': { chatId: 'mapped-chat' } },
61
66
  });
62
- await handleDiscordInteraction(mockInteraction, config, mockTrpc);
67
+ await handleDiscordInteraction(mockInteraction, config, mockTrpc, { filters: {} });
63
68
 
64
69
  expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
65
70
  expect.objectContaining({
@@ -83,7 +88,7 @@ describe('handleDiscordInteraction', () => {
83
88
  channelChatMap: { 'channel-1': { chatId: 'mapped-chat' } },
84
89
  });
85
90
 
86
- await handleDiscordInteraction(mockInteraction, config, mockTrpc);
91
+ await handleDiscordInteraction(mockInteraction, config, mockTrpc, { filters: {} });
87
92
 
88
93
  expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
89
94
  expect.objectContaining({
@@ -93,4 +98,50 @@ describe('handleDiscordInteraction', () => {
93
98
  })
94
99
  );
95
100
  });
101
+
102
+ describe('chat input commands', () => {
103
+ beforeEach(() => {
104
+ mockInteraction.isButton.mockReturnValue(false);
105
+ mockInteraction.isModalSubmit.mockReturnValue(false);
106
+ mockInteraction.isChatInputCommand.mockReturnValue(true);
107
+ mockInteraction.options = {
108
+ getString: vi.fn(),
109
+ };
110
+ });
111
+
112
+ it('routes basic commands like /pending', async () => {
113
+ mockInteraction.commandName = 'pending';
114
+
115
+ await handleDiscordInteraction(mockInteraction, config, mockTrpc, { filters: {} });
116
+
117
+ expect(mockInteraction.deferReply).toHaveBeenCalledWith({ ephemeral: true });
118
+ expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
119
+ expect.objectContaining({
120
+ data: expect.objectContaining({
121
+ message: '/pending',
122
+ }),
123
+ })
124
+ );
125
+ });
126
+
127
+ it('routes commands with arguments like /reject', async () => {
128
+ mockInteraction.commandName = 'reject';
129
+ mockInteraction.options.getString.mockImplementation((name: string) => {
130
+ if (name === 'policy_id') return 'req-123';
131
+ if (name === 'rationale') return 'too risky';
132
+ return null;
133
+ });
134
+
135
+ await handleDiscordInteraction(mockInteraction, config, mockTrpc, { filters: {} });
136
+
137
+ expect(mockInteraction.deferReply).toHaveBeenCalledWith({ ephemeral: true });
138
+ expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
139
+ expect.objectContaining({
140
+ data: expect.objectContaining({
141
+ message: '/reject req-123 too risky',
142
+ }),
143
+ })
144
+ );
145
+ });
146
+ });
96
147
  });