clawmini 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.github/workflows/release.yml +49 -0
  4. package/CHANGELOG.md +36 -0
  5. package/README.md +5 -4
  6. package/dist/adapter-discord/index.d.mts.map +1 -1
  7. package/dist/adapter-discord/index.mjs +465 -282
  8. package/dist/adapter-discord/index.mjs.map +1 -1
  9. package/dist/adapter-google-chat/index.mjs +367 -243
  10. package/dist/adapter-google-chat/index.mjs.map +1 -1
  11. package/dist/cli/index.mjs +684 -24
  12. package/dist/cli/index.mjs.map +1 -1
  13. package/dist/cli/lite.mjs +43 -13
  14. package/dist/cli/lite.mjs.map +1 -1
  15. package/dist/cli/{propose-policy.mjs → manage-policies.mjs} +270 -47
  16. package/dist/cli/manage-policies.mjs.map +1 -0
  17. package/dist/cli/run-host.d.mts +1 -0
  18. package/dist/cli/run-host.mjs +3090 -0
  19. package/dist/cli/run-host.mjs.map +1 -0
  20. package/dist/config-CPFQIGdG.mjs +57 -0
  21. package/dist/config-CPFQIGdG.mjs.map +1 -0
  22. package/dist/config-Dvl-Pov4.mjs +76 -0
  23. package/dist/config-Dvl-Pov4.mjs.map +1 -0
  24. package/dist/daemon/index.d.mts.map +1 -1
  25. package/dist/daemon/index.mjs +970 -332
  26. package/dist/daemon/index.mjs.map +1 -1
  27. package/dist/supervisor-actions-CiW56eLi.mjs +843 -0
  28. package/dist/supervisor-actions-CiW56eLi.mjs.map +1 -0
  29. package/dist/turn-log-buffer-DRgW53gl.mjs +767 -0
  30. package/dist/turn-log-buffer-DRgW53gl.mjs.map +1 -0
  31. package/dist/web/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/BhRSsUCh.js +2 -0
  33. package/dist/web/_app/immutable/chunks/BiLeM2i1.js +1 -0
  34. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js → dist/web/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/BrERcKAH.js +1 -0
  36. package/dist/web/_app/immutable/chunks/Bv9252RM.js +1 -0
  37. package/dist/web/_app/immutable/chunks/CIXNBPKi.js +1 -0
  38. package/dist/web/_app/immutable/chunks/DISKL3GN.js +2 -0
  39. package/dist/web/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  40. package/dist/web/_app/immutable/chunks/DnQ3vS13.js +1 -0
  41. package/dist/web/_app/immutable/chunks/KsloHTKS.js +1 -0
  42. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Ck-be5J2.js → dist/web/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  43. package/dist/web/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  44. package/dist/web/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  45. package/dist/web/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  46. package/dist/web/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  47. package/dist/web/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  48. package/dist/web/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  49. package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.0arZe_Uf.js → dist/web/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  50. package/dist/web/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  51. package/dist/web/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  52. package/dist/web/_app/version.json +1 -1
  53. package/dist/web/index.html +12 -12
  54. package/dist/workspace-oWmVh5mi.mjs +1001 -0
  55. package/dist/workspace-oWmVh5mi.mjs.map +1 -0
  56. package/docs/23_adapter_slash_autocomplete/development_log.md +19 -0
  57. package/docs/23_adapter_slash_autocomplete/notes.md +18 -0
  58. package/docs/23_adapter_slash_autocomplete/prd.md +46 -0
  59. package/docs/23_adapter_slash_autocomplete/questions.md +6 -0
  60. package/docs/23_adapter_slash_autocomplete/tickets.md +21 -0
  61. package/docs/24_subagent_job_policy_fixes/development_log.md +22 -0
  62. package/docs/24_subagent_job_policy_fixes/notes.md +28 -0
  63. package/docs/24_subagent_job_policy_fixes/prd.md +59 -0
  64. package/docs/24_subagent_job_policy_fixes/questions.md +3 -0
  65. package/docs/24_subagent_job_policy_fixes/tickets.md +49 -0
  66. package/docs/25_e2e_test_improvements/development_log.md +30 -0
  67. package/docs/25_e2e_test_improvements/notes.md +29 -0
  68. package/docs/25_e2e_test_improvements/prd.md +43 -0
  69. package/docs/25_e2e_test_improvements/questions.md +12 -0
  70. package/docs/25_e2e_test_improvements/tickets-2.md +22 -0
  71. package/docs/25_e2e_test_improvements/tickets.md +22 -0
  72. package/docs/25_policy_cwd/development_log.md +30 -0
  73. package/docs/25_policy_cwd/notes.md +28 -0
  74. package/docs/25_policy_cwd/prd.md +77 -0
  75. package/docs/25_policy_cwd/questions.md +6 -0
  76. package/docs/25_policy_cwd/tickets.md +77 -0
  77. package/docs/CLI_REFERENCE.md +3 -1
  78. package/docs/PHILOSOPHY.md +35 -0
  79. package/docs/adapter-visibility/SPEC.md +461 -0
  80. package/docs/adapter-visibility/SPEC_v2.md +202 -0
  81. package/docs/auto-update/SPEC.md +344 -0
  82. package/docs/backups/SPEC.md +296 -0
  83. package/docs/backups/clawmini.gitignore +69 -0
  84. package/docs/guides/assets/clawmini-avatar.png +0 -0
  85. package/docs/guides/backups.md +332 -0
  86. package/docs/guides/discord_adapter_setup.md +1 -1
  87. package/docs/guides/google_chat_adapter_setup.md +81 -0
  88. package/docs/unified-startup/SPEC.md +203 -0
  89. package/e2e/_helpers/test-environment.test.ts +49 -0
  90. package/e2e/_helpers/test-environment.ts +548 -0
  91. package/e2e/adapters/_google-chat-fixtures.ts +340 -0
  92. package/{src/cli/e2e → e2e/adapters}/adapter-discord.test.ts +22 -23
  93. package/e2e/adapters/adapter-google-chat-downtime.test.ts +157 -0
  94. package/e2e/adapters/adapter-google-chat-inbound.test.ts +697 -0
  95. package/e2e/adapters/adapter-google-chat-outbound.test.ts +297 -0
  96. package/e2e/adapters/adapter-google-chat-roundtrip.test.ts +56 -0
  97. package/e2e/adapters/adapter-google-chat-threads.test.ts +1078 -0
  98. package/e2e/agents/custom-api-env.test.ts +80 -0
  99. package/e2e/agents/export-lite-func.test.ts +104 -0
  100. package/e2e/agents/fallbacks.test.ts +124 -0
  101. package/e2e/agents/interrupt.test.ts +50 -0
  102. package/e2e/agents/no-reply-necessary.test.ts +57 -0
  103. package/e2e/agents/session-timeout-subagents.test.ts +76 -0
  104. package/e2e/agents/subagent-authorization.test.ts +246 -0
  105. package/e2e/agents/subagent-env.test.ts +49 -0
  106. package/e2e/agents/subagent-lifecycle.test.ts +782 -0
  107. package/e2e/agents/subagents-depth.test.ts +47 -0
  108. package/e2e/cli/agents.test.ts +176 -0
  109. package/e2e/cli/auto-update.test.ts +741 -0
  110. package/e2e/cli/basic.test.ts +44 -0
  111. package/{src/cli/e2e → e2e/cli}/export-lite.test.ts +16 -12
  112. package/e2e/cli/init-gitignore.test.ts +86 -0
  113. package/e2e/cli/init.test.ts +76 -0
  114. package/e2e/cli/messages.test.ts +363 -0
  115. package/e2e/cli/serve.test.ts +76 -0
  116. package/{src/cli/e2e → e2e/cli}/skills.test.ts +11 -10
  117. package/{src/cli/e2e → e2e/daemon}/daemon.test.ts +57 -195
  118. package/e2e/jobs/agent-jobs.test.ts +216 -0
  119. package/e2e/jobs/cron.test.ts +64 -0
  120. package/e2e/jobs/restart.test.ts +108 -0
  121. package/e2e/policies/approval-session.test.ts +69 -0
  122. package/e2e/policies/auto-create-policies-file.test.ts +35 -0
  123. package/e2e/policies/builtin-manage-policies.test.ts +184 -0
  124. package/e2e/policies/builtin-run-host.test.ts +180 -0
  125. package/e2e/policies/environment-policies.test.ts +177 -0
  126. package/e2e/policies/manage-policies.test.ts +566 -0
  127. package/e2e/policies/output-size.test.ts +98 -0
  128. package/e2e/policies/policies-context-cwd.test.ts +160 -0
  129. package/e2e/policies/relative-script-path.test.ts +60 -0
  130. package/e2e/policies/requests-show.test.ts +135 -0
  131. package/e2e/policies/requests.test.ts +208 -0
  132. package/e2e/policies/slash-policies.test.ts +308 -0
  133. package/e2e/policies/startup-cleanup.test.ts +48 -0
  134. package/e2e/routers/session-timeout.test.ts +106 -0
  135. package/e2e/routers/slash-model.test.ts +152 -0
  136. package/e2e/routers/slash-new.test.ts +50 -0
  137. package/e2e/routers/slash-restart-adapter.test.ts +96 -0
  138. package/e2e/routers/slash-restart.test.ts +114 -0
  139. package/e2e/routers/slash-shutdown.test.ts +55 -0
  140. package/e2e/routers/slash-stop.test.ts +232 -0
  141. package/e2e/routers/slash-upgrade.test.ts +88 -0
  142. package/{src/cli/e2e → e2e/sandbox}/environments.test.ts +14 -13
  143. package/eslint.config.js +6 -0
  144. package/napkin.md +1 -1
  145. package/package.json +8 -3
  146. package/src/adapter-discord/commands.test.ts +42 -0
  147. package/src/adapter-discord/commands.ts +33 -0
  148. package/src/adapter-discord/config.ts +12 -0
  149. package/src/adapter-discord/forwarder.test.ts +499 -21
  150. package/src/adapter-discord/forwarder.ts +343 -124
  151. package/src/adapter-discord/inbound-cache.test.ts +47 -0
  152. package/src/adapter-discord/inbound-cache.ts +37 -0
  153. package/src/adapter-discord/index.test.ts +67 -2
  154. package/src/adapter-discord/index.ts +84 -216
  155. package/src/adapter-discord/interactions.test.ts +54 -3
  156. package/src/adapter-discord/interactions.ts +97 -53
  157. package/src/adapter-discord/processMessage.ts +239 -0
  158. package/src/adapter-discord/state.ts +1 -0
  159. package/src/adapter-google-chat/auth.test.ts +9 -5
  160. package/src/adapter-google-chat/auth.ts +29 -23
  161. package/src/adapter-google-chat/cards.ts +7 -2
  162. package/src/adapter-google-chat/client.test.ts +37 -2
  163. package/src/adapter-google-chat/client.ts +138 -38
  164. package/src/adapter-google-chat/config.ts +19 -0
  165. package/src/adapter-google-chat/forwarder.test.ts +81 -56
  166. package/src/adapter-google-chat/forwarder.ts +394 -185
  167. package/src/adapter-google-chat/inbound-cache.test.ts +61 -0
  168. package/src/adapter-google-chat/inbound-cache.ts +36 -0
  169. package/src/adapter-google-chat/state.test.ts +1 -0
  170. package/src/adapter-google-chat/state.ts +9 -1
  171. package/src/adapter-google-chat/subscriptions.ts +8 -6
  172. package/src/cli/builtin-policies.ts +44 -0
  173. package/src/cli/commands/agents.ts +59 -5
  174. package/src/cli/commands/down.ts +54 -2
  175. package/src/cli/commands/environments.ts +8 -2
  176. package/src/cli/commands/init.ts +31 -0
  177. package/src/cli/commands/logs.ts +116 -0
  178. package/src/cli/commands/policies.ts +6 -4
  179. package/src/cli/commands/serve.test.ts +67 -0
  180. package/src/cli/commands/serve.ts +284 -0
  181. package/src/cli/commands/up.ts +122 -2
  182. package/src/cli/commands/web-api/agents.ts +3 -2
  183. package/src/cli/index.ts +4 -0
  184. package/src/cli/install-detection.test.ts +72 -0
  185. package/src/cli/install-detection.ts +48 -0
  186. package/src/cli/lite.ts +54 -22
  187. package/src/cli/manage-policies-utils.ts +104 -0
  188. package/src/cli/manage-policies.ts +291 -0
  189. package/src/cli/run-host.ts +45 -0
  190. package/src/cli/supervisor-actions.ts +267 -0
  191. package/src/cli/supervisor-control.test.ts +129 -0
  192. package/src/cli/supervisor-control.ts +155 -0
  193. package/src/cli/supervisor-pid.ts +68 -0
  194. package/src/cli/supervisor.ts +277 -0
  195. package/src/daemon/agent/agent-context.ts +11 -11
  196. package/src/daemon/agent/agent-session.ts +8 -1
  197. package/src/daemon/agent/chat-logger.test.ts +78 -9
  198. package/src/daemon/agent/chat-logger.ts +25 -5
  199. package/src/daemon/agent/turn-registry.test.ts +89 -0
  200. package/src/daemon/agent/turn-registry.ts +94 -0
  201. package/src/daemon/agent/types.ts +2 -0
  202. package/src/daemon/api/agent-policy-endpoints.ts +263 -0
  203. package/src/daemon/api/agent-router.ts +47 -126
  204. package/src/daemon/api/index.test.ts +1 -0
  205. package/src/daemon/api/policy-request.test.ts +7 -5
  206. package/src/daemon/api/router-utils.ts +6 -5
  207. package/src/daemon/api/subagent-router.ts +110 -74
  208. package/src/daemon/api/subagent-utils.test.ts +60 -0
  209. package/src/daemon/api/subagent-utils.ts +113 -87
  210. package/src/daemon/api/user-router.ts +34 -8
  211. package/src/daemon/auth.ts +1 -0
  212. package/src/daemon/cron.test.ts +62 -4
  213. package/src/daemon/cron.ts +42 -16
  214. package/src/daemon/events.ts +65 -0
  215. package/src/daemon/index.ts +24 -1
  216. package/src/daemon/message-interruption.test.ts +1 -0
  217. package/src/daemon/message-jobs.test.ts +1 -0
  218. package/src/daemon/message.ts +78 -14
  219. package/src/daemon/observation.test.ts +26 -18
  220. package/src/daemon/pending-replies.test.ts +112 -0
  221. package/src/daemon/pending-replies.ts +162 -0
  222. package/src/daemon/policy-request-service.ts +3 -1
  223. package/src/daemon/policy-utils.test.ts +66 -1
  224. package/src/daemon/policy-utils.ts +126 -1
  225. package/src/daemon/request-store.ts +31 -0
  226. package/src/daemon/routers/session-timeout.ts +4 -0
  227. package/src/daemon/routers/slash-model.test.ts +344 -0
  228. package/src/daemon/routers/slash-model.ts +207 -0
  229. package/src/daemon/routers/slash-policies.test.ts +38 -32
  230. package/src/daemon/routers/slash-policies.ts +84 -33
  231. package/src/daemon/routers/slash-restart.test.ts +69 -0
  232. package/src/daemon/routers/slash-restart.ts +36 -0
  233. package/src/daemon/routers/slash-shutdown.test.ts +50 -0
  234. package/src/daemon/routers/slash-shutdown.ts +28 -0
  235. package/src/daemon/routers/slash-upgrade.test.ts +116 -0
  236. package/src/daemon/routers/slash-upgrade.ts +76 -0
  237. package/src/daemon/routers/types.ts +7 -0
  238. package/src/daemon/routers.ts +16 -0
  239. package/src/shared/adapters/blockquote.test.ts +28 -0
  240. package/src/shared/adapters/blockquote.ts +20 -0
  241. package/src/shared/adapters/filtering.test.ts +224 -10
  242. package/src/shared/adapters/filtering.ts +95 -7
  243. package/src/shared/adapters/inbound-cache.test.ts +48 -0
  244. package/src/shared/adapters/inbound-cache.ts +54 -0
  245. package/src/shared/adapters/turn-log-buffer.ts +266 -0
  246. package/src/shared/adapters/turn-log.test.ts +389 -0
  247. package/src/shared/adapters/turn-log.ts +357 -0
  248. package/src/shared/agent-utils.ts +12 -5
  249. package/src/shared/chats.test.ts +4 -0
  250. package/src/shared/chats.ts +9 -0
  251. package/src/shared/config.ts +16 -1
  252. package/src/shared/lite.ts +76 -2
  253. package/src/shared/policies.ts +26 -0
  254. package/src/shared/template-manifest.ts +267 -0
  255. package/src/shared/utils/shell.ts +61 -0
  256. package/src/shared/version.ts +34 -0
  257. package/src/shared/workspace.test.ts +217 -0
  258. package/src/shared/workspace.ts +626 -48
  259. package/templates/environments/cladding/allowlist-domain.mjs +125 -0
  260. package/templates/environments/cladding/env.json +21 -1
  261. package/templates/environments/cladding/run-with-network.mjs +54 -0
  262. package/templates/environments/macos-proxy/allowlist-domain.mjs +95 -0
  263. package/templates/environments/macos-proxy/env.json +8 -1
  264. package/templates/environments/macos-proxy/proxy.mjs +0 -1
  265. package/templates/gemini/template.json +5 -0
  266. package/templates/gemini-claw/template.json +13 -0
  267. package/templates/skills/clawmini-requests/SKILL.md +69 -10
  268. package/templates/skills/run-host/SKILL.md +51 -0
  269. package/templates/skills/skill-creator/SKILL.md +4 -3
  270. package/templates/skills/skill-creator/scripts/validate.sh +52 -0
  271. package/tsdown.config.ts +10 -1
  272. package/vitest.config.ts +2 -2
  273. package/web/.svelte-kit/ambient.d.ts +292 -118
  274. package/web/.svelte-kit/generated/server/internal.js +1 -1
  275. package/web/.svelte-kit/output/client/.vite/manifest.json +126 -136
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Drm9vgeP.js → 3AZlWB6U.js} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/BhRSsUCh.js +2 -0
  278. package/web/.svelte-kit/output/client/_app/immutable/chunks/BiLeM2i1.js +1 -0
  279. package/{dist/web/_app/immutable/chunks/CME08kGM.js → web/.svelte-kit/output/client/_app/immutable/chunks/BmBj85Ll.js} +1 -1
  280. package/web/.svelte-kit/output/client/_app/immutable/chunks/BrERcKAH.js +1 -0
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bv9252RM.js +1 -0
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/CIXNBPKi.js +1 -0
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/DISKL3GN.js +2 -0
  284. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Zeh-C-mx.js → DcpaLzmX.js} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/DnQ3vS13.js +1 -0
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/KsloHTKS.js +1 -0
  287. package/{dist/web/_app/immutable/chunks/Ck-be5J2.js → web/.svelte-kit/output/client/_app/immutable/chunks/RsHsUj-8.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{vDehDcuJ.js → wpfV79dV.js} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/app.CIw1Qj0n.js +2 -0
  290. package/web/.svelte-kit/output/client/_app/immutable/entry/start.Di0-Jhte.js +1 -0
  291. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.CUGC2p-K.js → 0.DYyUA1au.js} +1 -1
  292. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.D-3QEMMZ.js +1 -0
  293. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.BnwnD1Ki.js → 2.4olHnH7U.js} +1 -1
  294. package/{dist/web/_app/immutable/nodes/3.0arZe_Uf.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.4w0bE-m2.js} +3 -3
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CZvjhVHt.js +60 -0
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bq2JzCEj.js → 5.DLbPVJY2.js} +1 -1
  297. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  298. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -10
  299. package/web/.svelte-kit/output/server/chunks/Icon.js +1 -1
  300. package/web/.svelte-kit/output/server/chunks/client.js +1 -1
  301. package/web/.svelte-kit/output/server/chunks/exports.js +1 -1
  302. package/web/.svelte-kit/output/server/chunks/index-server.js +2 -1
  303. package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
  304. package/web/.svelte-kit/output/server/chunks/render-context.js +77 -0
  305. package/web/.svelte-kit/output/server/chunks/root.js +739 -788
  306. package/web/.svelte-kit/output/server/chunks/shared.js +234 -21
  307. package/web/.svelte-kit/output/server/index.js +126 -90
  308. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  309. package/web/.svelte-kit/output/server/manifest.js +1 -1
  310. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  311. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  312. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  313. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  314. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  315. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  316. package/web/.svelte-kit/output/server/remote-entry.js +245 -81
  317. package/web/.svelte-kit/tsconfig.json +4 -1
  318. package/dist/cli/propose-policy.mjs.map +0 -1
  319. package/dist/lite-CBxOT1y5.mjs +0 -241
  320. package/dist/lite-CBxOT1y5.mjs.map +0 -1
  321. package/dist/routing-D8rTxtaV.mjs +0 -245
  322. package/dist/routing-D8rTxtaV.mjs.map +0 -1
  323. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  324. package/dist/web/_app/immutable/chunks/BmRlVmv6.js +0 -1
  325. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +0 -2
  326. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +0 -1
  327. package/dist/web/_app/immutable/chunks/D5iV40bG.js +0 -1
  328. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +0 -2
  329. package/dist/web/_app/immutable/chunks/DhD271EB.js +0 -1
  330. package/dist/web/_app/immutable/chunks/DpuLqk8d.js +0 -1
  331. package/dist/web/_app/immutable/chunks/DsIToJCP.js +0 -1
  332. package/dist/web/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  333. package/dist/web/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  334. package/dist/web/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  335. package/dist/web/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  336. package/dist/workspace-BJmJBfKi.mjs +0 -456
  337. package/dist/workspace-BJmJBfKi.mjs.map +0 -1
  338. package/src/cli/e2e/agents.test.ts +0 -140
  339. package/src/cli/e2e/basic.test.ts +0 -43
  340. package/src/cli/e2e/cron.test.ts +0 -132
  341. package/src/cli/e2e/export-lite-func.test.ts +0 -206
  342. package/src/cli/e2e/fallbacks.test.ts +0 -175
  343. package/src/cli/e2e/init.test.ts +0 -77
  344. package/src/cli/e2e/messages.test.ts +0 -332
  345. package/src/cli/e2e/propose-policy.test.ts +0 -203
  346. package/src/cli/e2e/requests.test.ts +0 -180
  347. package/src/cli/e2e/session-timeout.test.ts +0 -192
  348. package/src/cli/e2e/slash-new.test.ts +0 -93
  349. package/src/cli/e2e/subagents.test.ts +0 -106
  350. package/src/cli/e2e/utils.ts +0 -66
  351. package/src/cli/propose-policy.ts +0 -91
  352. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmRlVmv6.js +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +0 -2
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/chunks/D5iV40bG.js +0 -1
  357. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +0 -2
  358. package/web/.svelte-kit/output/client/_app/immutable/chunks/DhD271EB.js +0 -1
  359. package/web/.svelte-kit/output/client/_app/immutable/chunks/DpuLqk8d.js +0 -1
  360. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +0 -1
  361. package/web/.svelte-kit/output/client/_app/immutable/entry/app.BCSV3nrG.js +0 -2
  362. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D4eLEZUM.js +0 -1
  363. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.CGC_42IQ.js +0 -1
  364. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.ClM1bXLE.js +0 -60
  365. package/web/.svelte-kit/output/server/chunks/false.js +0 -4
  366. /package/dist/cli/{propose-policy.d.mts → manage-policies.d.mts} +0 -0
  367. /package/{src/cli/e2e → e2e/_helpers}/global-setup.ts +0 -0
@@ -1,63 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { c as getClawminiDir, d as getSocketPath, f as getWorkspaceRoot } from "../workspace-BJmJBfKi.mjs";
2
+ import { g as getWorkspaceRoot, h as getSocketPath, u as getClawminiDir } from "../workspace-oWmVh5mi.mjs";
3
3
  import { t as createUnixSocketFetch } from "../fetch-Cn1XNyiO.mjs";
4
- import { a as createUnixSocketEventSource, i as shouldDisplayMessage, n as handleAdapterCommand, r as formatMessage, t as handleRoutingCommand } from "../routing-D8rTxtaV.mjs";
4
+ import { n as initDiscordConfig, r as readDiscordConfig } from "../config-CPFQIGdG.mjs";
5
+ import { a as handleAdapterCommand, c as routeMessage, i as handleRoutingCommand, n as createUnixSocketEventSource, o as createInboundCache, r as prependBlockquote, s as formatMessage, t as createTurnLogBuffer } from "../turn-log-buffer-DRgW53gl.mjs";
5
6
  import fs from "node:fs";
6
7
  import path from "node:path";
7
- import fs$1 from "node:fs/promises";
8
+ import fsPromises from "node:fs/promises";
8
9
  import { z } from "zod";
9
10
  import { createTRPCClient, httpLink, httpSubscriptionLink, splitLink } from "@trpc/client";
10
- import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, Colors, EmbedBuilder, Events, GatewayIntentBits, ModalBuilder, Partials, TextInputBuilder, TextInputStyle } from "discord.js";
11
+ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, Colors, EmbedBuilder, Events, GatewayIntentBits, ModalBuilder, Partials, REST, Routes, SlashCommandBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
11
12
 
12
- //#region src/adapter-discord/config.ts
13
- const DiscordConfigSchema = z.looseObject({
14
- botToken: z.string().min(1, "Discord Bot Token is required."),
15
- authorizedUserId: z.string().min(1, "Authorized Discord User ID is required."),
16
- chatId: z.string().default("default"),
17
- maxAttachmentSizeMB: z.number().default(25),
18
- requireMention: z.boolean().default(false)
19
- });
20
- function getDiscordConfigPath(startDir = process.cwd()) {
21
- return path.join(getClawminiDir(startDir), "adapters", "discord", "config.json");
22
- }
23
- async function readDiscordConfig(startDir = process.cwd()) {
24
- const configPath = getDiscordConfigPath(startDir);
25
- try {
26
- const data = await fs$1.readFile(configPath, "utf-8");
27
- const parsed = JSON.parse(data);
28
- return DiscordConfigSchema.parse(parsed);
29
- } catch (err) {
30
- if (err.code === "ENOENT") return null;
31
- throw err;
32
- }
33
- }
34
- async function initDiscordConfig(startDir = process.cwd()) {
35
- const configPath = getDiscordConfigPath(startDir);
36
- const configDir = path.dirname(configPath);
37
- await fs$1.mkdir(configDir, { recursive: true });
38
- if (fs.existsSync(configPath)) {
39
- console.log(`Config file already exists at ${configPath}`);
40
- return;
41
- }
42
- await fs$1.writeFile(configPath, JSON.stringify({
43
- botToken: "YOUR_DISCORD_BOT_TOKEN",
44
- authorizedUserId: "YOUR_DISCORD_USER_ID",
45
- chatId: "default"
46
- }, null, 2), "utf-8");
47
- console.log(`Created template configuration file at ${configPath}`);
48
- console.log("Please update it with your actual Discord Bot Token and User ID.");
49
- }
50
- function isAuthorized$1(userId, authorizedUserId) {
51
- return userId === authorizedUserId;
52
- }
53
-
54
- //#endregion
55
13
  //#region src/adapter-discord/state.ts
56
14
  const DiscordStateSchema = z.object({
57
15
  lastSyncedMessageIds: z.record(z.string(), z.string()).optional(),
58
16
  channelChatMap: z.record(z.string(), z.object({
59
17
  chatId: z.string().nullable().optional(),
60
- requireMention: z.boolean().optional()
18
+ requireMention: z.boolean().optional(),
19
+ threadsDisabled: z.boolean().optional()
61
20
  })).optional(),
62
21
  filters: z.record(z.string(), z.boolean()).optional()
63
22
  });
@@ -67,7 +26,7 @@ function getDiscordStatePath(startDir = process.cwd()) {
67
26
  async function readDiscordState(startDir = process.cwd()) {
68
27
  const statePath = getDiscordStatePath(startDir);
69
28
  try {
70
- const data = await fs$1.readFile(statePath, "utf-8");
29
+ const data = await fsPromises.readFile(statePath, "utf-8");
71
30
  const parsed = JSON.parse(data);
72
31
  if (parsed.lastSyncedMessageId && !parsed.lastSyncedMessageIds) parsed.lastSyncedMessageIds = { default: parsed.lastSyncedMessageId };
73
32
  if (parsed.channelChatMap) {
@@ -83,8 +42,8 @@ async function writeDiscordState(state, startDir = process.cwd()) {
83
42
  const statePath = getDiscordStatePath(startDir);
84
43
  const dir = path.dirname(statePath);
85
44
  try {
86
- await fs$1.mkdir(dir, { recursive: true });
87
- await fs$1.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
45
+ await fsPromises.mkdir(dir, { recursive: true });
46
+ await fsPromises.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
88
47
  } catch (err) {
89
48
  console.error(`Failed to write Discord state to ${statePath}:`, err);
90
49
  }
@@ -110,13 +69,160 @@ function updateDiscordState(updates, startDir = process.cwd()) {
110
69
  });
111
70
  }
112
71
 
72
+ //#endregion
73
+ //#region src/adapter-discord/inbound-cache.ts
74
+ /**
75
+ * Discord-side wrapper around the shared inbound-message TTL cache.
76
+ *
77
+ * On every inbound user message, the gateway records `{ messageId, channelId
78
+ * }`. The same `messageId` is sent to the daemon as `externalRef` on the
79
+ * `sendMessage` mutation. When the forwarder later sees `turnStarted` with
80
+ * that `externalRef`, it resolves the channel + message id and starts a
81
+ * Discord thread anchored on the user's message.
82
+ */
83
+ const INBOUND_TTL_MS = 600 * 1e3;
84
+ const cache = createInboundCache(INBOUND_TTL_MS);
85
+ function recordInbound(entry) {
86
+ cache.record(entry.messageId, { channelId: entry.channelId });
87
+ }
88
+ function resolveInbound(messageId) {
89
+ const value = cache.resolve(messageId);
90
+ return value ? {
91
+ messageId,
92
+ channelId: value.channelId
93
+ } : null;
94
+ }
95
+
96
+ //#endregion
97
+ //#region src/adapter-discord/processMessage.ts
98
+ async function processDiscordMessage(content, author, channelId, guild, reply, config, trpc, filteringConfig, options = {}) {
99
+ if (author.bot) return;
100
+ const externalContextId = channelId || "default";
101
+ const currentState = await readDiscordState();
102
+ const mappedChatId = options.explicitChatId || (channelId ? currentState.channelChatMap?.[channelId]?.chatId : null);
103
+ const isRoutingCommand = content.startsWith("/chat") || content.startsWith("/agent");
104
+ if (guild && channelId) {
105
+ const channelConfig = currentState.channelChatMap?.[channelId];
106
+ if (channelConfig?.requireMention !== void 0 ? channelConfig.requireMention : config.requireMention) {
107
+ if (!options.mentionsBot && !options.isReplyToBot) return;
108
+ }
109
+ }
110
+ function isAuthorized(userId, authorizedUserId) {
111
+ return userId === authorizedUserId;
112
+ }
113
+ if (!isAuthorized(author.id, config.authorizedUserId)) {
114
+ console.log(`Unauthorized message from ${author.tag} (${author.id}) ignored.`);
115
+ return;
116
+ }
117
+ console.log(`Received message from ${author.tag}: ${content}`);
118
+ if (isRoutingCommand) {
119
+ const routingResult = await handleRoutingCommand(content, externalContextId, Object.fromEntries(Object.entries(currentState.channelChatMap || {}).map(([k, v]) => [k, v.chatId || ""])), "discord", trpc);
120
+ if (routingResult) {
121
+ if (routingResult.type === "mapped") await updateDiscordState((latestState) => ({ channelChatMap: {
122
+ ...latestState.channelChatMap || {},
123
+ [externalContextId]: {
124
+ ...latestState.channelChatMap?.[externalContextId] || {},
125
+ chatId: routingResult.newChatId
126
+ }
127
+ } }));
128
+ await reply(routingResult.text);
129
+ return;
130
+ }
131
+ }
132
+ let targetChatId = mappedChatId;
133
+ if (!targetChatId && !isRoutingCommand) if (!currentState.channelChatMap || Object.values(currentState.channelChatMap).every((entry) => !entry.chatId)) {
134
+ targetChatId = config.chatId || "default";
135
+ console.log(`First contact detected. Automatically mapping channel ${externalContextId} to chat ${targetChatId}.`);
136
+ await updateDiscordState((latestState) => ({ channelChatMap: {
137
+ ...latestState.channelChatMap || {},
138
+ [externalContextId]: {
139
+ ...latestState.channelChatMap?.[externalContextId] || {},
140
+ chatId: targetChatId
141
+ }
142
+ } }));
143
+ } else {
144
+ const isDirectMessage = !guild;
145
+ const isSlashCommand = content.startsWith("/");
146
+ if (isDirectMessage || options.mentionsBot || isSlashCommand) {
147
+ console.log(`Unmapped channel ${externalContextId}, sending first contact warning.`);
148
+ await reply("This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it.");
149
+ } else console.log(`Unmapped channel ${externalContextId}, silently ignoring background message.`);
150
+ return;
151
+ }
152
+ if (!targetChatId) targetChatId = config.chatId || "default";
153
+ const commandResult = await handleAdapterCommand(content, filteringConfig, trpc, targetChatId);
154
+ if (commandResult) {
155
+ if (commandResult.type === "text") {
156
+ if (commandResult.newConfig) {
157
+ filteringConfig.filters = commandResult.newConfig.filters;
158
+ await updateDiscordState({ filters: filteringConfig.filters });
159
+ }
160
+ await reply(commandResult.text);
161
+ } else if (commandResult.type === "debug") await reply(commandResult.messages.length === 0 ? "No ignored background messages found." : `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` + commandResult.messages.map((msg) => formatMessage(msg)).join("\n\n---\n\n"));
162
+ return;
163
+ }
164
+ const downloadedFiles = [];
165
+ if (options.attachments && options.attachments.length > 0) {
166
+ const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp", "discord");
167
+ await fsPromises.mkdir(tmpDir, { recursive: true });
168
+ const maxSizeMB = config.maxAttachmentSizeMB ?? 25;
169
+ const maxSizeBytes = maxSizeMB * 1024 * 1024;
170
+ for (const attachment of options.attachments) {
171
+ if (attachment.size > maxSizeBytes) {
172
+ console.warn(`Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`);
173
+ await reply(`Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`);
174
+ continue;
175
+ }
176
+ try {
177
+ const res = await fetch(attachment.url);
178
+ if (!res.ok) {
179
+ console.error(`Failed to download attachment ${attachment.name}`);
180
+ continue;
181
+ }
182
+ const uniqueName = `${Date.now()}-${attachment.name}`;
183
+ const filePath = path.join(tmpDir, uniqueName);
184
+ const arrayBuffer = await res.arrayBuffer();
185
+ await fsPromises.writeFile(filePath, Buffer.from(arrayBuffer));
186
+ downloadedFiles.push(filePath);
187
+ } catch (err) {
188
+ console.error(`Error downloading attachment ${attachment.name}:`, err);
189
+ }
190
+ }
191
+ }
192
+ let finalContent = content;
193
+ if (options.referenceContent) finalContent = prependBlockquote(options.referenceContent, finalContent, options.referenceAuthor);
194
+ console.log(`Forwarding message to daemon: ${finalContent}`);
195
+ if (options.messageId && channelId) recordInbound({
196
+ messageId: options.messageId,
197
+ channelId
198
+ });
199
+ try {
200
+ await trpc.sendMessage.mutate({
201
+ type: "send-message",
202
+ client: "cli",
203
+ data: {
204
+ message: finalContent,
205
+ chatId: targetChatId,
206
+ files: downloadedFiles.length > 0 ? downloadedFiles : void 0,
207
+ adapter: "discord",
208
+ noWait: true,
209
+ ...options.messageId ? { externalRef: options.messageId } : {}
210
+ }
211
+ });
212
+ console.log("Message forwarded to daemon successfully.");
213
+ } catch (error) {
214
+ console.error("Failed to forward message to daemon:", error);
215
+ await reply("Failed to forward message to the daemon. Please check the logs.");
216
+ }
217
+ }
218
+
113
219
  //#endregion
114
220
  //#region src/adapter-discord/interactions.ts
115
221
  function isAuthorized(userId, authorizedUserId) {
116
222
  return userId === authorizedUserId;
117
223
  }
118
- async function handleDiscordInteraction(interaction, config, trpc) {
119
- if (!interaction.isButton() && !interaction.isModalSubmit()) return;
224
+ async function handleDiscordInteraction(interaction, config, trpc, filteringConfig) {
225
+ if (!interaction.isButton() && !interaction.isModalSubmit() && !interaction.isChatInputCommand()) return;
120
226
  if (!isAuthorized(interaction.user.id, config.authorizedUserId)) {
121
227
  if (interaction.isRepliable()) await interaction.reply({
122
228
  content: "You are not authorized to perform this action.",
@@ -124,6 +230,34 @@ async function handleDiscordInteraction(interaction, config, trpc) {
124
230
  });
125
231
  return;
126
232
  }
233
+ if (interaction.isChatInputCommand()) {
234
+ const { commandName } = interaction;
235
+ let commandStr = `/${commandName}`;
236
+ if (commandName === "approve" || commandName === "reject") {
237
+ const policyId = interaction.options.getString("policy_id");
238
+ if (policyId) commandStr += ` ${policyId}`;
239
+ }
240
+ if (commandName === "reject") {
241
+ const rationale = interaction.options.getString("rationale");
242
+ if (rationale) commandStr += ` ${rationale}`;
243
+ }
244
+ await interaction.deferReply({ ephemeral: true });
245
+ const currentState = await readDiscordState();
246
+ const targetChatId = interaction.channelId ? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId : config.chatId;
247
+ let replied = false;
248
+ await processDiscordMessage(commandStr, interaction.user, interaction.channelId, interaction.guild, async (text) => {
249
+ replied = true;
250
+ await interaction.followUp({
251
+ content: text,
252
+ ephemeral: true
253
+ });
254
+ }, config, trpc, filteringConfig, {
255
+ explicitChatId: targetChatId,
256
+ mentionsBot: true
257
+ });
258
+ if (!replied) await interaction.deleteReply();
259
+ return;
260
+ }
127
261
  if (interaction.isButton()) {
128
262
  if (interaction.customId.startsWith("approve_") || interaction.customId.startsWith("approve|")) {
129
263
  let policyId, explicitChatId;
@@ -137,26 +271,17 @@ async function handleDiscordInteraction(interaction, config, trpc) {
137
271
  content: `Approving policy ${policyId}...`,
138
272
  ephemeral: true
139
273
  });
140
- try {
141
- const currentState = await readDiscordState();
142
- const targetChatId = explicitChatId || (interaction.channelId ? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId : config.chatId);
143
- await trpc.sendMessage.mutate({
144
- type: "send-message",
145
- client: "cli",
146
- data: {
147
- message: `/approve ${policyId}`,
148
- chatId: targetChatId,
149
- adapter: "discord",
150
- noWait: true
151
- }
152
- });
153
- } catch (error) {
154
- console.error("Failed to send approve command to daemon:", error);
274
+ const currentState = await readDiscordState();
275
+ const targetChatId = explicitChatId || (interaction.channelId ? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId : config.chatId);
276
+ await processDiscordMessage(`/approve ${policyId}`, interaction.user, interaction.channelId, interaction.guild, async (text) => {
155
277
  await interaction.followUp({
156
- content: `Failed to approve policy ${policyId}.`,
278
+ content: text,
157
279
  ephemeral: true
158
280
  });
159
- }
281
+ }, config, trpc, filteringConfig, {
282
+ explicitChatId: targetChatId,
283
+ mentionsBot: true
284
+ });
160
285
  } else if (interaction.customId.startsWith("reject_") || interaction.customId.startsWith("reject|")) {
161
286
  let policyId, explicitChatId;
162
287
  if (interaction.customId.startsWith("reject|")) {
@@ -189,30 +314,24 @@ async function handleDiscordInteraction(interaction, config, trpc) {
189
314
  content: `Rejecting policy ${policyId}...`,
190
315
  ephemeral: true
191
316
  });
192
- } else await interaction.reply({
193
- content: `Rejecting policy ${policyId}...`,
194
- ephemeral: true
195
- });
196
- try {
197
- const currentState = await readDiscordState();
198
- const targetChatId = explicitChatId || (interaction.channelId ? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId : config.chatId);
199
- await trpc.sendMessage.mutate({
200
- type: "send-message",
201
- client: "cli",
202
- data: {
203
- message: command,
204
- chatId: targetChatId,
205
- adapter: "discord",
206
- noWait: true
207
- }
208
- });
209
- } catch (error) {
210
- console.error("Failed to send reject command to daemon:", error);
317
+ } else {
318
+ await interaction.deferReply({ ephemeral: true });
211
319
  await interaction.followUp({
212
- content: `Failed to reject policy ${policyId}.`,
320
+ content: `Rejecting policy ${policyId}...`,
213
321
  ephemeral: true
214
322
  });
215
323
  }
324
+ const currentState = await readDiscordState();
325
+ const targetChatId = explicitChatId || (interaction.channelId ? currentState.channelChatMap?.[interaction.channelId]?.chatId || config.chatId : config.chatId);
326
+ await processDiscordMessage(command, interaction.user, interaction.channelId, interaction.guild, async (text) => {
327
+ await interaction.followUp({
328
+ content: text,
329
+ ephemeral: true
330
+ });
331
+ }, config, trpc, filteringConfig, {
332
+ explicitChatId: targetChatId,
333
+ mentionsBot: true
334
+ });
216
335
  }
217
336
  }
218
337
  }
@@ -246,6 +365,20 @@ function getTRPCClient(options = {}) {
246
365
 
247
366
  //#endregion
248
367
  //#region src/adapter-discord/forwarder.ts
368
+ const DEFAULT_THREAD_LOG_OPTS = {
369
+ maxToolPreview: 400,
370
+ maxLogMessageChars: 1800,
371
+ editDebounceMs: 1e3
372
+ };
373
+ function resolveThreadLogOpts(config) {
374
+ const v = config?.visibility?.threadLog;
375
+ return {
376
+ maxToolPreview: v?.maxToolPreview ?? DEFAULT_THREAD_LOG_OPTS.maxToolPreview,
377
+ maxLogMessageChars: v?.maxLogMessageChars ?? DEFAULT_THREAD_LOG_OPTS.maxLogMessageChars,
378
+ editDebounceMs: v?.editDebounceMs ?? DEFAULT_THREAD_LOG_OPTS.editDebounceMs
379
+ };
380
+ }
381
+ const NO_MENTIONS = { allowedMentions: { parse: [] } };
249
382
  async function resolveDiscordDestination(client, discordUserId, chatId) {
250
383
  const channelChatMap = (await readDiscordState()).channelChatMap || {};
251
384
  let targetDiscordChannelId;
@@ -265,6 +398,8 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, option
265
398
  const defaultChatId = options.chatId ?? "default";
266
399
  const signal = options.signal;
267
400
  const config = options.config ?? {};
401
+ const threadLogOpts = resolveThreadLogOpts(options.discordConfig);
402
+ const threadsGloballyEnabled = options.discordConfig?.visibility?.threads !== false;
268
403
  const activeSubscriptions = /* @__PURE__ */ new Map();
269
404
  const activeTypingSubscriptions = /* @__PURE__ */ new Map();
270
405
  let currentLastSyncedMessageIds = (await readDiscordState()).lastSyncedMessageIds || {};
@@ -278,6 +413,170 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, option
278
413
  ...currentLastSyncedMessageIds
279
414
  } }));
280
415
  };
416
+ const postThreaded = async (anchor, text) => {
417
+ return (await anchor.send({
418
+ content: text || "​",
419
+ ...NO_MENTIONS
420
+ })).id;
421
+ };
422
+ const editThreaded = async (anchor, messageId, text) => {
423
+ await (await anchor.messages.fetch(messageId)).edit({
424
+ content: text || "​",
425
+ ...NO_MENTIONS
426
+ });
427
+ };
428
+ const isMissingMessageError = (err) => {
429
+ const code = err?.code ?? 0;
430
+ return code === 404 || code === 10008;
431
+ };
432
+ const turnLog = createTurnLogBuffer({
433
+ postThreaded,
434
+ editThreaded,
435
+ isMissingMessageError,
436
+ options: threadLogOpts,
437
+ threadsEnabled: threadsGloballyEnabled
438
+ });
439
+ const collapseDestination = (dest, turnId) => {
440
+ if (dest.kind !== "thread-log") return dest;
441
+ if (!threadsGloballyEnabled) return { kind: "drop" };
442
+ if (turnId && turnLog.threadsDisabledFor(turnId)) return { kind: "drop" };
443
+ return dest;
444
+ };
445
+ const channelThreadsDisabled = async (chatId) => {
446
+ const state = await readDiscordState();
447
+ for (const [, entry] of Object.entries(state.channelChatMap || {})) if (entry?.chatId === chatId) return entry.threadsDisabled === true;
448
+ return false;
449
+ };
450
+ const openThreadForTurn = async (externalRef) => {
451
+ if (!externalRef) return void 0;
452
+ const inbound = resolveInbound(externalRef);
453
+ if (!inbound) return void 0;
454
+ let channel;
455
+ try {
456
+ channel = await client.channels.fetch(inbound.channelId);
457
+ } catch (err) {
458
+ console.warn(`Failed to fetch channel ${inbound.channelId} for turn anchor:`, err);
459
+ return;
460
+ }
461
+ if (!channel || !channel.isTextBased() || channel.isDMBased() || channel.isThread()) return;
462
+ const guildChannel = channel;
463
+ let userMessage;
464
+ try {
465
+ userMessage = await guildChannel.messages.fetch(inbound.messageId);
466
+ } catch (err) {
467
+ console.warn(`Failed to fetch user message ${inbound.messageId} for turn anchor:`, err);
468
+ return;
469
+ }
470
+ if (userMessage.hasThread && userMessage.thread) return userMessage.thread;
471
+ try {
472
+ return await userMessage.startThread({
473
+ name: "Activity log",
474
+ autoArchiveDuration: 1440
475
+ });
476
+ } catch (err) {
477
+ if (err?.code === 160004) try {
478
+ const refreshed = await guildChannel.messages.fetch(inbound.messageId);
479
+ if (refreshed.hasThread && refreshed.thread) return refreshed.thread;
480
+ } catch (refetchErr) {
481
+ console.warn(`Failed to refetch user message ${inbound.messageId} after thread-exists race:`, refetchErr);
482
+ }
483
+ console.warn(`Failed to start thread on message ${inbound.messageId}:`, err);
484
+ return;
485
+ }
486
+ };
487
+ const handleTurnStarted = async (chatId, turnId, externalRef) => {
488
+ const threadsDisabled = !threadsGloballyEnabled || await channelThreadsDisabled(chatId);
489
+ const anchor = threadsDisabled ? void 0 : await openThreadForTurn(externalRef);
490
+ if (!anchor && !threadsDisabled) return;
491
+ turnLog.start({
492
+ turnId,
493
+ threadsDisabled,
494
+ anchorThread: anchor
495
+ });
496
+ };
497
+ const handleTurnEnded = async (turnId) => {
498
+ await turnLog.end(turnId);
499
+ };
500
+ const sendPolicyCard = async (chatId, message) => {
501
+ if (message.role !== "policy" || message.status !== "pending") return false;
502
+ try {
503
+ const dm = await resolveDiscordDestination(client, discordUserId, chatId);
504
+ const embed = new EmbedBuilder().setTitle("Action Required: Policy Request").setDescription(message.content || "A pending policy request requires your attention.").setColor(Colors.Yellow);
505
+ const policyId = "requestId" in message && message.requestId || message.id;
506
+ const row = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId(`approve|${policyId}|${chatId}`).setLabel("Approve").setStyle(ButtonStyle.Success), new ButtonBuilder().setCustomId(`reject|${policyId}|${chatId}`).setLabel("Reject").setStyle(ButtonStyle.Danger));
507
+ const optionsMsg = {
508
+ embeds: [embed],
509
+ components: [row],
510
+ ...NO_MENTIONS
511
+ };
512
+ try {
513
+ await dm.send(optionsMsg);
514
+ } catch (richError) {
515
+ console.warn(`Failed to send rich message to Discord user ${discordUserId}, falling back to plain text:`, richError);
516
+ await dm.send({
517
+ content: `Action Required: Policy Request\n\n${message.content || "A pending policy request requires your attention."}\n\nApprove: \`/approve ${policyId}\`\nReject: \`/reject ${policyId} <optional_rationale>\``,
518
+ ...NO_MENTIONS
519
+ });
520
+ }
521
+ } catch (error) {
522
+ console.error(`Failed to send message to Discord user ${discordUserId}:`, error);
523
+ }
524
+ return true;
525
+ };
526
+ const sendTopLevel = async (chatId, message) => {
527
+ if ("level" in message && message.level === "verbose") return;
528
+ const hasContent = !!message.content?.trim();
529
+ const files = "files" in message ? message.files ?? [] : [];
530
+ const hasFiles = Array.isArray(files) && files.length > 0;
531
+ let absoluteFiles = [];
532
+ if (hasFiles) {
533
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
534
+ absoluteFiles = files.map((f) => path.resolve(workspaceRoot, f));
535
+ }
536
+ if (!hasContent && !hasFiles) return;
537
+ try {
538
+ const dm = await resolveDiscordDestination(client, discordUserId, chatId);
539
+ const formattedContent = formatMessage(message);
540
+ if (formattedContent && formattedContent.length > 2e3) {
541
+ const chunks = chunkString(formattedContent, 2e3);
542
+ for (let i = 0; i < chunks.length; i++) {
543
+ if (signal?.aborted) break;
544
+ const chunkOptions = {
545
+ content: chunks[i],
546
+ ...NO_MENTIONS
547
+ };
548
+ if (i === chunks.length - 1 && hasFiles) chunkOptions.files = absoluteFiles;
549
+ await dm.send(chunkOptions);
550
+ }
551
+ } else {
552
+ const optionsMsg = { ...NO_MENTIONS };
553
+ if (formattedContent) optionsMsg.content = formattedContent;
554
+ if (hasFiles) optionsMsg.files = absoluteFiles;
555
+ await dm.send(optionsMsg);
556
+ }
557
+ } catch (error) {
558
+ console.error(`Failed to send message to Discord user ${discordUserId}:`, error);
559
+ throw error;
560
+ }
561
+ };
562
+ const handleMessageForChat = async (chatId, message) => {
563
+ const effective = collapseDestination(routeMessage(message, config), message.turnId);
564
+ if (effective.kind === "drop") return;
565
+ if (effective.kind === "thread-log") {
566
+ if (!message.turnId) {
567
+ console.warn(`thread-log event for ${message.role} has no turnId — dropping.`);
568
+ return;
569
+ }
570
+ if (!turnLog.has(message.turnId)) return;
571
+ turnLog.append(message.turnId, message);
572
+ return;
573
+ }
574
+ if (message.role === "policy" && message.status === "pending") {
575
+ await sendPolicyCard(chatId, message);
576
+ return;
577
+ }
578
+ await sendTopLevel(chatId, message);
579
+ };
281
580
  const startSubscriptionForChat = async (chatId) => {
282
581
  if (activeSubscriptions.has(chatId)) return;
283
582
  if (signal?.aborted) return;
@@ -309,82 +608,33 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, option
309
608
  chatId,
310
609
  lastMessageId
311
610
  }, {
312
- onData: (messages) => {
611
+ onData: (items) => {
313
612
  retryDelay = 1e3;
314
- if (!Array.isArray(messages) || messages.length === 0) return;
613
+ if (!Array.isArray(items) || items.length === 0) return;
315
614
  messageQueue = messageQueue.then(async () => {
316
- for (const rawMessage of messages) {
615
+ for (const raw of items) {
317
616
  if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
318
- const message = rawMessage;
319
- if (shouldDisplayMessage(message, config)) {
320
- const logMessage = message;
321
- if (logMessage.role === "policy" && logMessage.status === "pending") {
322
- try {
323
- const dm = await resolveDiscordDestination(client, discordUserId, chatId);
324
- const embed = new EmbedBuilder().setTitle("Action Required: Policy Request").setDescription(logMessage.content || "A pending policy request requires your attention.").setColor(Colors.Yellow);
325
- const policyId = "requestId" in logMessage && logMessage.requestId || logMessage.id;
326
- const row = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId(`approve|${policyId}|${chatId}`).setLabel("Approve").setStyle(ButtonStyle.Success), new ButtonBuilder().setCustomId(`reject|${policyId}|${chatId}`).setLabel("Reject").setStyle(ButtonStyle.Danger));
327
- const optionsMsg = {
328
- embeds: [embed],
329
- components: [row]
330
- };
331
- try {
332
- await dm.send(optionsMsg);
333
- } catch (richError) {
334
- console.warn(`Failed to send rich message to Discord user ${discordUserId}, falling back to plain text:`, richError);
335
- await dm.send({ content: `Action Required: Policy Request\n\n${logMessage.content || "A pending policy request requires your attention."}\n\nApprove: \`/approve ${policyId}\`\nReject: \`/reject ${policyId} <optional_rationale>\`` });
336
- }
337
- } catch (error) {
338
- console.error(`Failed to send message to Discord user ${discordUserId}:`, error);
339
- }
340
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
341
- lastMessageId = logMessage.id;
342
- continue;
343
- }
344
- if ("level" in logMessage && logMessage.level === "verbose") {
345
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
346
- lastMessageId = logMessage.id;
347
- continue;
348
- }
349
- const hasContent = !!logMessage.content?.trim();
350
- const files = "files" in logMessage ? logMessage.files : void 0;
351
- const hasFiles = Array.isArray(files) && files.length > 0;
352
- let absoluteFiles = [];
353
- if (hasFiles && files) {
354
- const workspaceRoot = getWorkspaceRoot(process.cwd());
355
- absoluteFiles = files.map((f) => path.resolve(workspaceRoot, f));
356
- }
357
- if (!hasContent && !hasFiles) {
358
- await saveLastMessageId(chatId, logMessage.id).catch(console.error);
359
- lastMessageId = logMessage.id;
360
- continue;
361
- }
617
+ const item = raw;
618
+ if (item.kind === "turn") {
362
619
  try {
363
- const dm = await resolveDiscordDestination(client, discordUserId, chatId);
364
- const formattedContent = formatMessage(message);
365
- if (formattedContent && formattedContent.length > 2e3) {
366
- const chunks = chunkString(formattedContent, 2e3);
367
- for (let i = 0; i < chunks.length; i++) {
368
- if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
369
- const chunkOptions = { content: chunks[i] };
370
- if (i === chunks.length - 1 && hasFiles) chunkOptions.files = absoluteFiles;
371
- await dm.send(chunkOptions);
372
- }
373
- } else {
374
- const optionsMsg = {};
375
- if (formattedContent) optionsMsg.content = formattedContent;
376
- if (hasFiles) optionsMsg.files = absoluteFiles;
377
- await dm.send(optionsMsg);
378
- }
379
- } catch (error) {
380
- console.error(`Failed to send message to Discord user ${discordUserId}:`, error);
381
- break;
620
+ if (item.event.type === "started") await handleTurnStarted(chatId, item.event.turnId, item.event.externalRef);
621
+ else await handleTurnEnded(item.event.turnId);
622
+ } catch (err) {
623
+ console.error("Failed to handle turn event:", err);
382
624
  }
625
+ continue;
626
+ }
627
+ const message = item.message;
628
+ try {
629
+ await handleMessageForChat(chatId, message);
630
+ } catch (err) {
631
+ console.error("Failed to handle message:", err);
632
+ break;
383
633
  }
384
634
  await saveLastMessageId(chatId, message.id).catch(console.error);
385
635
  lastMessageId = message.id;
386
636
  }
387
- });
637
+ }).catch((err) => console.error("Message queue chain error:", err));
388
638
  },
389
639
  onError: (error) => {
390
640
  console.error(`Error in daemon-to-discord forwarder subscription for ${chatId}. Retrying in ${retryDelay}ms.`, error);
@@ -477,6 +727,7 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, option
477
727
  watcher.close();
478
728
  for (const sub of activeSubscriptions.values()) sub.unsubscribe();
479
729
  for (const sub of activeTypingSubscriptions.values()) sub.unsubscribe();
730
+ turnLog.shutdown();
480
731
  resolve();
481
732
  });
482
733
  });
@@ -488,6 +739,19 @@ function chunkString(str, size) {
488
739
  return chunks;
489
740
  }
490
741
 
742
+ //#endregion
743
+ //#region src/adapter-discord/commands.ts
744
+ const slashCommands = [
745
+ new SlashCommandBuilder().setName("new").setDescription("Start a new chat or operation."),
746
+ new SlashCommandBuilder().setName("stop").setDescription("Stop the current operation."),
747
+ new SlashCommandBuilder().setName("approve").setDescription("Approve a pending policy request.").addStringOption((option) => option.setName("policy_id").setDescription("The ID of the policy to approve").setRequired(true)),
748
+ new SlashCommandBuilder().setName("reject").setDescription("Reject a pending policy request.").addStringOption((option) => option.setName("policy_id").setDescription("The ID of the policy to reject").setRequired(true)).addStringOption((option) => option.setName("rationale").setDescription("Optional rationale for rejecting the policy").setRequired(false)),
749
+ new SlashCommandBuilder().setName("pending").setDescription("List pending policy requests."),
750
+ new SlashCommandBuilder().setName("show").setDescription("Show background messages."),
751
+ new SlashCommandBuilder().setName("hide").setDescription("Hide background messages."),
752
+ new SlashCommandBuilder().setName("debug").setDescription("Output debug information about ignored background messages.")
753
+ ];
754
+
491
755
  //#endregion
492
756
  //#region src/adapter-discord/index.ts
493
757
  async function main() {
@@ -512,146 +776,65 @@ async function main() {
512
776
  partials: [Partials.Channel]
513
777
  });
514
778
  const filteringConfig = { filters: (await readDiscordState()).filters };
515
- client.once(Events.ClientReady, (readyClient) => {
779
+ client.once(Events.ClientReady, async (readyClient) => {
516
780
  console.log(`Ready! Logged in as ${readyClient.user.tag}`);
781
+ try {
782
+ await (await readyClient.users.fetch(config.authorizedUserId)).createDM();
783
+ } catch (err) {
784
+ console.error(`Failed to pre-cache DM channel for authorized user ${config.authorizedUserId}:`, err);
785
+ }
786
+ try {
787
+ const rest = new REST({ version: "10" }).setToken(config.botToken);
788
+ console.log("Started refreshing application (/) commands.");
789
+ await rest.put(Routes.applicationCommands(readyClient.user.id), { body: slashCommands.map((cmd) => cmd.toJSON()) });
790
+ console.log("Successfully reloaded application (/) commands.");
791
+ } catch (error) {
792
+ console.error("Error registering slash commands:", error);
793
+ }
517
794
  startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, {
518
795
  chatId: config.chatId,
519
- config: filteringConfig
796
+ config: filteringConfig,
797
+ discordConfig: config
520
798
  }).catch((error) => {
521
799
  console.error("Error in daemon-to-discord forwarder:", error);
522
800
  });
523
801
  });
524
802
  client.on(Events.MessageCreate, async (message) => {
525
- if (message.author.id === client.user?.id) return;
526
- if (message.author.bot) return;
527
- const externalContextId = message.channelId;
528
- const currentState = await readDiscordState();
529
- const mappedChatId = currentState.channelChatMap?.[externalContextId]?.chatId;
530
- const isRoutingCommand = message.content.startsWith("/chat") || message.content.startsWith("/agent");
531
- if (message.guild) {
532
- const channelConfig = currentState.channelChatMap?.[externalContextId];
533
- if (channelConfig?.requireMention !== void 0 ? channelConfig.requireMention : config.requireMention) {
534
- const isMentioned = message.mentions.has(client.user.id);
535
- let isReplyToBot = false;
536
- if (message.reference && message.reference.messageId) try {
537
- isReplyToBot = (await message.channel.messages.fetch(message.reference.messageId)).author.id === client.user.id;
538
- } catch (err) {
539
- console.error("Failed to fetch referenced message for mention check:", err);
540
- }
541
- if (!isMentioned && !isReplyToBot) return;
542
- }
543
- }
544
- if (!isAuthorized$1(message.author.id, config.authorizedUserId)) {
545
- console.log(`Unauthorized message from ${message.author.tag} (${message.author.id}) ignored.`);
546
- return;
547
- }
548
- console.log(`Received message from ${message.author.tag}: ${message.content}`);
549
- if (isRoutingCommand) {
550
- const stringChatMap = Object.fromEntries(Object.entries(currentState.channelChatMap || {}).map(([k, v]) => [k, v.chatId || ""]));
551
- const routingResult = await handleRoutingCommand(message.content, externalContextId, stringChatMap, "discord", trpc);
552
- if (routingResult) {
553
- if (routingResult.type === "mapped") await updateDiscordState((latestState) => ({ channelChatMap: {
554
- ...latestState.channelChatMap || {},
555
- [externalContextId]: {
556
- ...latestState.channelChatMap?.[externalContextId] || {},
557
- chatId: routingResult.newChatId
558
- }
559
- } }));
560
- await message.reply(routingResult.text);
561
- return;
562
- }
563
- }
564
- let targetChatId = mappedChatId;
565
- if (!targetChatId && !isRoutingCommand) if (!currentState.channelChatMap || Object.values(currentState.channelChatMap).every((entry) => !entry.chatId)) {
566
- targetChatId = config.chatId || "default";
567
- console.log(`First contact detected. Automatically mapping channel ${externalContextId} to chat ${targetChatId}.`);
568
- await updateDiscordState((latestState) => ({ channelChatMap: {
569
- ...latestState.channelChatMap || {},
570
- [externalContextId]: {
571
- ...latestState.channelChatMap?.[externalContextId] || {},
572
- chatId: targetChatId
573
- }
574
- } }));
575
- } else {
576
- const isDirectMessage = !message.guild;
577
- const isMentioned = message.mentions.has(client.user.id);
578
- const isSlashCommand = message.content.startsWith("/");
579
- if (isDirectMessage || isMentioned || isSlashCommand) {
580
- console.log(`Unmapped channel ${externalContextId}, sending first contact warning.`);
581
- await message.reply("This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it.");
582
- } else console.log(`Unmapped channel ${externalContextId}, silently ignoring background message.`);
583
- return;
584
- }
585
- if (!targetChatId) targetChatId = config.chatId || "default";
586
- const commandResult = await handleAdapterCommand(message.content, filteringConfig, trpc, targetChatId);
587
- if (commandResult) {
588
- if (commandResult.type === "text") {
589
- if (commandResult.newConfig) {
590
- filteringConfig.filters = commandResult.newConfig.filters;
591
- await updateDiscordState({ filters: filteringConfig.filters });
592
- }
593
- await message.reply(commandResult.text);
594
- } else if (commandResult.type === "debug") {
595
- const formatted = commandResult.messages.length === 0 ? "No ignored background messages found." : `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` + commandResult.messages.map((msg) => formatMessage(msg)).join("\n\n---\n\n");
596
- await message.reply(formatted);
597
- }
598
- return;
599
- }
600
- const downloadedFiles = [];
601
- if (message.attachments.size > 0) {
602
- const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp", "discord");
603
- await fs$1.mkdir(tmpDir, { recursive: true });
604
- const maxSizeMB = config.maxAttachmentSizeMB ?? 25;
605
- const maxSizeBytes = maxSizeMB * 1024 * 1024;
606
- for (const attachment of message.attachments.values()) {
607
- if (attachment.size > maxSizeBytes) {
608
- console.warn(`Attachment ${attachment.name} exceeds size limit (${maxSizeMB}MB). Ignoring.`);
609
- await message.reply(`Warning: Attachment ${attachment.name} exceeds the size limit of ${maxSizeMB}MB and was ignored.`);
610
- continue;
611
- }
612
- try {
613
- const res = await fetch(attachment.url);
614
- if (!res.ok) {
615
- console.error(`Failed to download attachment ${attachment.name}`);
616
- continue;
617
- }
618
- const uniqueName = `${Date.now()}-${attachment.name}`;
619
- const filePath = path.join(tmpDir, uniqueName);
620
- const arrayBuffer = await res.arrayBuffer();
621
- await fs$1.writeFile(filePath, Buffer.from(arrayBuffer));
622
- downloadedFiles.push(filePath);
623
- } catch (err) {
624
- console.error(`Error downloading attachment ${attachment.name}:`, err);
625
- }
626
- }
627
- }
628
- let finalContent = message.content;
803
+ let isReplyToBot = false;
804
+ let referenceContent;
805
+ let referenceAuthor;
629
806
  if (message.reference && message.reference.messageId) try {
630
807
  const referencedMessage = await message.fetchReference();
631
- if (referencedMessage && referencedMessage.content) finalContent = `${referencedMessage.content.split("\n").map((line) => `> ${line}`).join("\n")}\n${finalContent}`;
808
+ isReplyToBot = referencedMessage?.author.id === client.user.id;
809
+ referenceContent = referencedMessage?.content;
810
+ if (referencedMessage) {
811
+ if (referencedMessage.author.bot) referenceAuthor = "Assistant";
812
+ else if (referencedMessage.author.id !== config.authorizedUserId) referenceAuthor = referencedMessage.author.username;
813
+ }
632
814
  } catch (err) {
633
- console.error("Failed to fetch referenced message:", err);
815
+ console.error("Failed to fetch referenced message for mention check:", err);
634
816
  }
635
- console.log(`Forwarding message to daemon: ${finalContent}`);
636
- try {
637
- await trpc.sendMessage.mutate({
638
- type: "send-message",
639
- client: "cli",
640
- data: {
641
- message: finalContent,
642
- chatId: targetChatId,
643
- files: downloadedFiles.length > 0 ? downloadedFiles : void 0,
644
- adapter: "discord",
645
- noWait: true
646
- }
817
+ const attachments = message.attachments ? Array.from(message.attachments.values()).map((att) => ({
818
+ name: att.name,
819
+ size: att.size,
820
+ url: att.url
821
+ })) : [];
822
+ await processDiscordMessage(message.content, message.author, message.channelId, message.guild, async (text) => {
823
+ await message.reply({
824
+ content: text,
825
+ allowedMentions: { parse: [] }
647
826
  });
648
- console.log("Message forwarded to daemon successfully.");
649
- } catch (error) {
650
- console.error("Failed to forward message to daemon:", error);
651
- }
827
+ }, config, trpc, filteringConfig, {
828
+ mentionsBot: !!message.mentions?.has(client.user.id),
829
+ isReplyToBot,
830
+ attachments,
831
+ messageId: message.id,
832
+ ...referenceContent ? { referenceContent } : {},
833
+ ...referenceAuthor ? { referenceAuthor } : {}
834
+ });
652
835
  });
653
836
  client.on(Events.InteractionCreate, async (interaction) => {
654
- await handleDiscordInteraction(interaction, config, trpc);
837
+ await handleDiscordInteraction(interaction, config, trpc, filteringConfig);
655
838
  });
656
839
  try {
657
840
  await client.login(config.botToken);