clawmini 0.0.3 → 0.0.5

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 (333) hide show
  1. package/README.md +19 -0
  2. package/dist/adapter-discord/index.d.mts.map +1 -1
  3. package/dist/adapter-discord/index.mjs +398 -193
  4. package/dist/adapter-discord/index.mjs.map +1 -1
  5. package/dist/adapter-google-chat/index.d.mts +5 -0
  6. package/dist/adapter-google-chat/index.d.mts.map +1 -0
  7. package/dist/adapter-google-chat/index.mjs +1077 -0
  8. package/dist/adapter-google-chat/index.mjs.map +1 -0
  9. package/dist/cli/index.mjs +107 -14
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/lite.mjs +175 -16
  12. package/dist/cli/lite.mjs.map +1 -1
  13. package/dist/cli/propose-policy.d.mts +1 -0
  14. package/dist/cli/propose-policy.mjs +7159 -0
  15. package/dist/cli/propose-policy.mjs.map +1 -0
  16. package/dist/daemon/index.d.mts.map +1 -1
  17. package/dist/daemon/index.mjs +1427 -513
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/{lite-oSYSvaOr.mjs → lite-CBxOT1y5.mjs} +101 -24
  20. package/dist/lite-CBxOT1y5.mjs.map +1 -0
  21. package/dist/routing-D8rTxtaV.mjs +245 -0
  22. package/dist/routing-D8rTxtaV.mjs.map +1 -0
  23. package/dist/web/_app/immutable/assets/0.C-4eziNy.css +1 -0
  24. package/dist/web/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
  25. package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +1 -0
  26. package/dist/web/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
  27. package/{web/.svelte-kit/output/client/_app/immutable/chunks/8YNcRyEk.js → dist/web/_app/immutable/chunks/C20lZMGz.js} +1 -1
  28. package/dist/web/_app/immutable/chunks/C9lbZ-kT.js +1 -0
  29. package/dist/web/_app/immutable/chunks/CK9JZLaG.js +2 -0
  30. package/dist/web/_app/immutable/chunks/CME08kGM.js +1 -0
  31. package/dist/web/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
  32. package/dist/web/_app/immutable/chunks/Ck3rYNON.js +1 -0
  33. package/dist/web/_app/immutable/chunks/DMtIqaiV.js +2 -0
  34. package/dist/web/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
  35. package/dist/web/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
  36. package/dist/web/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
  37. package/dist/web/_app/immutable/chunks/DsIToJCP.js +1 -0
  38. package/dist/web/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
  39. package/{web/.svelte-kit/output/client/_app/immutable/entry/app.DO5eYwVz.js → dist/web/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
  40. package/dist/web/_app/immutable/entry/start.DuxJo6av.js +1 -0
  41. package/dist/web/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
  42. package/dist/web/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
  43. package/dist/web/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
  44. package/dist/web/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
  45. package/dist/web/_app/immutable/nodes/4.DLarELN4.js +60 -0
  46. package/dist/web/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
  47. package/dist/web/_app/version.json +1 -1
  48. package/dist/web/index.html +12 -12
  49. package/dist/{workspace-DjoNjhW0.mjs → workspace-BJmJBfKi.mjs} +103 -11
  50. package/dist/workspace-BJmJBfKi.mjs.map +1 -0
  51. package/docs/14_google_chat_adapter/development_log.md +40 -0
  52. package/docs/14_google_chat_adapter/notes.md +28 -0
  53. package/docs/14_google_chat_adapter/prd.md +35 -0
  54. package/docs/14_google_chat_adapter/questions.md +9 -0
  55. package/docs/14_google_chat_adapter/tickets.md +117 -0
  56. package/docs/15_sandbox_policies/tickets.md +33 -0
  57. package/docs/16_session_timeout/development_log.md +20 -0
  58. package/docs/16_session_timeout/notes.md +44 -0
  59. package/docs/16_session_timeout/prd.md +106 -0
  60. package/docs/16_session_timeout/questions.md +10 -0
  61. package/docs/16_session_timeout/tickets.md +64 -0
  62. package/docs/17_auto_approve_policy/development_log.md +29 -0
  63. package/docs/17_auto_approve_policy/notes.md +25 -0
  64. package/docs/17_auto_approve_policy/prd.md +34 -0
  65. package/docs/17_auto_approve_policy/questions.md +10 -0
  66. package/docs/17_auto_approve_policy/tickets.md +11 -0
  67. package/docs/18_clawmini_skills/development_log.md +36 -0
  68. package/docs/18_clawmini_skills/notes.md +8 -0
  69. package/docs/18_clawmini_skills/prd.md +45 -0
  70. package/docs/18_clawmini_skills/questions.md +10 -0
  71. package/docs/18_clawmini_skills/tickets.md +55 -0
  72. package/docs/19_subagents/development_log.md +69 -0
  73. package/docs/19_subagents/notes.md +18 -0
  74. package/docs/19_subagents/prd.md +156 -0
  75. package/docs/19_subagents/questions.md +13 -0
  76. package/docs/19_subagents/tickets.md +113 -0
  77. package/docs/20_chat_logs_cleanup/development_log.md +50 -0
  78. package/docs/20_chat_logs_cleanup/notes.md +43 -0
  79. package/docs/20_chat_logs_cleanup/prd.md +232 -0
  80. package/docs/20_chat_logs_cleanup/questions.md +2 -0
  81. package/docs/20_chat_logs_cleanup/tickets.md +98 -0
  82. package/docs/20_webui_markdown/development_log.md +36 -0
  83. package/docs/20_webui_markdown/notes.md +23 -0
  84. package/docs/20_webui_markdown/prd.md +49 -0
  85. package/docs/20_webui_markdown/questions.md +10 -0
  86. package/docs/20_webui_markdown/tickets.md +55 -0
  87. package/docs/21_adapter_filtering/development_log.md +29 -0
  88. package/docs/21_adapter_filtering/notes.md +25 -0
  89. package/docs/21_adapter_filtering/prd.md +44 -0
  90. package/docs/21_adapter_filtering/questions.md +12 -0
  91. package/docs/21_adapter_filtering/tickets.md +38 -0
  92. package/docs/21_built_in_routers/development_log.md +17 -0
  93. package/docs/21_built_in_routers/notes.md +27 -0
  94. package/docs/21_built_in_routers/prd.md +34 -0
  95. package/docs/21_built_in_routers/questions.md +4 -0
  96. package/docs/21_built_in_routers/tickets.md +25 -0
  97. package/docs/21_fancy_policies/development_log.md +38 -0
  98. package/docs/21_fancy_policies/notes.md +27 -0
  99. package/docs/21_fancy_policies/prd.md +58 -0
  100. package/docs/21_fancy_policies/questions.md +6 -0
  101. package/docs/21_fancy_policies/tickets.md +48 -0
  102. package/docs/22_adapter_multi_chat/development_log.md +76 -0
  103. package/docs/22_adapter_multi_chat/notes.md +42 -0
  104. package/docs/22_adapter_multi_chat/prd.md +76 -0
  105. package/docs/22_adapter_multi_chat/questions.md +16 -0
  106. package/docs/22_adapter_multi_chat/tickets.md +164 -0
  107. package/docs/23_custom_token_env/development_log.md +31 -0
  108. package/docs/23_custom_token_env/notes.md +16 -0
  109. package/docs/23_custom_token_env/prd.md +42 -0
  110. package/docs/23_custom_token_env/questions.md +8 -0
  111. package/docs/23_custom_token_env/tickets.md +54 -0
  112. package/docs/guides/discord_adapter_setup.md +15 -2
  113. package/docs/guides/google_chat_adapter_setup.md +145 -0
  114. package/napkin.md +5 -0
  115. package/package.json +7 -2
  116. package/src/adapter-discord/config.test.ts +27 -8
  117. package/src/adapter-discord/config.ts +6 -8
  118. package/src/adapter-discord/forwarder.test.ts +307 -114
  119. package/src/adapter-discord/forwarder.ts +260 -75
  120. package/src/adapter-discord/index.test.ts +278 -0
  121. package/src/adapter-discord/index.ts +160 -30
  122. package/src/adapter-discord/interactions.test.ts +96 -0
  123. package/src/adapter-discord/interactions.ts +156 -0
  124. package/src/adapter-discord/state.test.ts +9 -8
  125. package/src/adapter-discord/state.ts +51 -8
  126. package/src/adapter-google-chat/auth.test.ts +87 -0
  127. package/src/adapter-google-chat/auth.ts +132 -0
  128. package/src/adapter-google-chat/cards.ts +71 -0
  129. package/src/adapter-google-chat/client.test.ts +561 -0
  130. package/src/adapter-google-chat/client.ts +430 -0
  131. package/src/adapter-google-chat/config.test.ts +187 -0
  132. package/src/adapter-google-chat/config.ts +82 -0
  133. package/src/adapter-google-chat/cron.test.ts +143 -0
  134. package/src/adapter-google-chat/cron.ts +81 -0
  135. package/src/adapter-google-chat/forwarder.test.ts +537 -0
  136. package/src/adapter-google-chat/forwarder.ts +349 -0
  137. package/src/adapter-google-chat/index.test.ts +62 -0
  138. package/src/adapter-google-chat/index.ts +61 -0
  139. package/src/adapter-google-chat/state.test.ts +96 -0
  140. package/src/adapter-google-chat/state.ts +85 -0
  141. package/src/adapter-google-chat/subscriptions.ts +124 -0
  142. package/src/adapter-google-chat/upload.ts +88 -0
  143. package/src/adapter-google-chat/utils.test.ts +111 -0
  144. package/src/adapter-google-chat/utils.ts +133 -0
  145. package/src/cli/commands/init.ts +0 -7
  146. package/src/cli/commands/messages.ts +18 -3
  147. package/src/cli/commands/policies.ts +70 -0
  148. package/src/cli/commands/skills.ts +71 -0
  149. package/src/cli/commands/web-api/chats.ts +5 -1
  150. package/src/cli/e2e/basic.test.ts +1 -1
  151. package/src/cli/e2e/cron.test.ts +1 -1
  152. package/src/cli/e2e/daemon.test.ts +132 -4
  153. package/src/cli/e2e/export-lite-func.test.ts +54 -31
  154. package/src/cli/e2e/fallbacks.test.ts +8 -6
  155. package/src/cli/e2e/init.test.ts +7 -0
  156. package/src/cli/e2e/messages.test.ts +90 -55
  157. package/src/cli/e2e/propose-policy.test.ts +203 -0
  158. package/src/cli/e2e/requests.test.ts +15 -0
  159. package/src/cli/e2e/session-timeout.test.ts +192 -0
  160. package/src/cli/e2e/skills.test.ts +55 -0
  161. package/src/cli/e2e/slash-new.test.ts +93 -0
  162. package/src/cli/e2e/subagents.test.ts +106 -0
  163. package/src/cli/index.ts +4 -0
  164. package/src/cli/lite.ts +51 -11
  165. package/src/cli/propose-policy.ts +91 -0
  166. package/src/cli/subagent-commands.ts +215 -0
  167. package/src/daemon/agent/agent-context.ts +89 -0
  168. package/src/daemon/agent/agent-extractors.ts +68 -0
  169. package/src/daemon/agent/agent-runner.ts +153 -0
  170. package/src/daemon/agent/agent-session.ts +261 -0
  171. package/src/daemon/agent/chat-logger.test.ts +158 -0
  172. package/src/daemon/agent/chat-logger.ts +188 -0
  173. package/src/daemon/agent/task-scheduler.test.ts +202 -0
  174. package/src/daemon/agent/task-scheduler.ts +276 -0
  175. package/src/daemon/agent/types.ts +84 -0
  176. package/src/daemon/agent/utils.ts +7 -0
  177. package/src/daemon/api/agent-router.ts +166 -18
  178. package/src/daemon/api/index.test.ts +50 -18
  179. package/src/daemon/api/policy-request.test.ts +39 -2
  180. package/src/daemon/api/subagent-router.test.ts +108 -0
  181. package/src/daemon/api/subagent-router.ts +296 -0
  182. package/src/daemon/api/subagent-utils.test.ts +56 -0
  183. package/src/daemon/api/subagent-utils.ts +130 -0
  184. package/src/daemon/api/user-router.ts +30 -13
  185. package/src/daemon/auth.ts +1 -0
  186. package/src/daemon/chats.ts +6 -0
  187. package/src/daemon/cron.test.ts +66 -1
  188. package/src/daemon/cron.ts +35 -8
  189. package/src/daemon/index.ts +23 -0
  190. package/src/daemon/message-agent.test.ts +11 -25
  191. package/src/daemon/message-extraction.test.ts +10 -27
  192. package/src/daemon/message-fallbacks.test.ts +13 -35
  193. package/src/daemon/message-interruption.test.ts +70 -53
  194. package/src/daemon/message-jobs.test.ts +138 -0
  195. package/src/daemon/message-queue.test.ts +30 -43
  196. package/src/daemon/message-router.test.ts +12 -11
  197. package/src/daemon/message-session.test.ts +41 -28
  198. package/src/daemon/message-typing.test.ts +19 -6
  199. package/src/daemon/message.ts +103 -515
  200. package/src/daemon/policy-request-service.ts +8 -3
  201. package/src/daemon/policy-utils.ts +19 -1
  202. package/src/daemon/queue.ts +16 -0
  203. package/src/daemon/request-store.test.ts +4 -0
  204. package/src/daemon/routers/session-timeout.test.ts +122 -0
  205. package/src/daemon/routers/session-timeout.ts +71 -0
  206. package/src/daemon/routers/slash-new.ts +3 -1
  207. package/src/daemon/routers/slash-policies.test.ts +26 -13
  208. package/src/daemon/routers/slash-policies.ts +39 -29
  209. package/src/daemon/routers/types.ts +8 -0
  210. package/src/daemon/routers.ts +64 -2
  211. package/src/daemon/utils/spawn.ts +6 -8
  212. package/src/shared/adapters/commands.test.ts +155 -0
  213. package/src/shared/adapters/commands.ts +125 -0
  214. package/src/shared/adapters/filtering.test.ts +111 -0
  215. package/src/shared/adapters/filtering.ts +57 -0
  216. package/src/shared/adapters/routing.test.ts +144 -0
  217. package/src/shared/adapters/routing.ts +109 -0
  218. package/src/shared/agent-utils.ts +10 -0
  219. package/src/shared/chats.test.ts +145 -3
  220. package/src/shared/chats.ts +215 -18
  221. package/src/shared/config.ts +67 -15
  222. package/src/shared/lite.ts +22 -18
  223. package/src/shared/policies.ts +7 -0
  224. package/src/shared/workspace.test.ts +45 -1
  225. package/src/shared/workspace.ts +119 -6
  226. package/templates/debug/settings.json +5 -2
  227. package/templates/environments/cladding/env.json +2 -2
  228. package/templates/gemini/.gemini/hooks/check-subagents.mjs +23 -0
  229. package/templates/gemini/.gemini/hooks/clawmini-logging.sh +17 -0
  230. package/templates/gemini/.gemini/hooks/insert-pending.sh +9 -0
  231. package/templates/gemini/.gemini/settings.json +50 -0
  232. package/templates/gemini/settings.json +22 -8
  233. package/templates/gemini-claw/.gemini/base-system.md +100 -0
  234. package/templates/gemini-claw/.gemini/hooks/check-subagents.mjs +23 -0
  235. package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +1 -1
  236. package/templates/gemini-claw/.gemini/settings.json +13 -0
  237. package/templates/gemini-claw/.gemini/subagent-system.md +7 -0
  238. package/templates/gemini-claw/.gemini/system.md +3 -99
  239. package/templates/gemini-claw/settings.json +27 -22
  240. package/templates/skills/clawmini-requests/SKILL.md +92 -0
  241. package/templates/skills/clawmini-subagents/SKILL.md +79 -0
  242. package/templates/skills/skill-creator/SKILL.md +60 -0
  243. package/tsdown.config.ts +10 -1
  244. package/web/.svelte-kit/generated/server/internal.js +2 -1
  245. package/web/.svelte-kit/non-ambient.d.ts +2 -0
  246. package/web/.svelte-kit/output/client/.vite/manifest.json +141 -138
  247. package/web/.svelte-kit/output/client/_app/immutable/assets/0.C-4eziNy.css +1 -0
  248. package/web/.svelte-kit/output/client/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
  249. package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +1 -0
  250. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
  251. package/{dist/web/_app/immutable/chunks/8YNcRyEk.js → web/.svelte-kit/output/client/_app/immutable/chunks/C20lZMGz.js} +1 -1
  252. package/web/.svelte-kit/output/client/_app/immutable/chunks/C9lbZ-kT.js +1 -0
  253. package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +2 -0
  254. package/web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js +1 -0
  255. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
  256. package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +1 -0
  257. package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +2 -0
  258. package/web/.svelte-kit/output/client/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
  259. package/web/.svelte-kit/output/client/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
  260. package/web/.svelte-kit/output/client/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
  261. package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +1 -0
  262. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
  263. package/{dist/web/_app/immutable/entry/app.DO5eYwVz.js → web/.svelte-kit/output/client/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
  264. package/web/.svelte-kit/output/client/_app/immutable/entry/start.DuxJo6av.js +1 -0
  265. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
  266. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
  267. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
  268. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
  269. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.DLarELN4.js +60 -0
  270. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
  271. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  272. package/web/.svelte-kit/output/server/.vite/manifest.json +12 -3
  273. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.C-4eziNy.css +1 -0
  274. package/web/.svelte-kit/output/server/_app/immutable/assets/_page.Cc_xwLNl.css +1 -0
  275. package/web/.svelte-kit/output/server/chunks/app-state.svelte.js +5 -0
  276. package/web/.svelte-kit/output/server/chunks/bot.js +4 -4
  277. package/web/.svelte-kit/output/server/chunks/client.js +2 -1
  278. package/web/.svelte-kit/output/server/chunks/exports.js +0 -1
  279. package/web/.svelte-kit/output/server/chunks/internal.js +2 -1
  280. package/web/.svelte-kit/output/server/chunks/root.js +482 -392
  281. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +57 -7
  282. package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +234 -9
  283. package/web/.svelte-kit/output/server/index.js +82 -10
  284. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  285. package/web/.svelte-kit/output/server/manifest.js +1 -1
  286. package/web/.svelte-kit/output/server/nodes/0.js +2 -2
  287. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  288. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  289. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  290. package/web/.svelte-kit/output/server/nodes/4.js +2 -2
  291. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  292. package/web/.svelte-kit/types/src/routes/$types.d.ts +1 -2
  293. package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +1 -2
  294. package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +1 -2
  295. package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +1 -2
  296. package/web/package.json +8 -0
  297. package/web/src/lib/app-state.svelte.ts +5 -1
  298. package/web/src/lib/components/app/markdown-renderer.svelte +56 -0
  299. package/web/src/lib/components/app/markdown-renderer.svelte.spec.ts +44 -0
  300. package/web/src/lib/components/app/message-content.svelte +16 -0
  301. package/web/src/lib/types.ts +67 -3
  302. package/web/src/routes/+layout.svelte +31 -1
  303. package/web/src/routes/chats/[id]/+page.svelte +167 -18
  304. package/web/src/routes/chats/[id]/page.svelte.spec.ts +58 -7
  305. package/dist/lite-oSYSvaOr.mjs.map +0 -1
  306. package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +0 -1
  307. package/dist/web/_app/immutable/chunks/B5abRDXp.js +0 -1
  308. package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
  309. package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +0 -2
  310. package/dist/web/_app/immutable/chunks/C3k55nDF.js +0 -1
  311. package/dist/web/_app/immutable/chunks/CpaGRn9L.js +0 -1
  312. package/dist/web/_app/immutable/chunks/DG5RZBw-.js +0 -2
  313. package/dist/web/_app/immutable/chunks/DQoygso7.js +0 -1
  314. package/dist/web/_app/immutable/entry/start.D48mVn1m.js +0 -1
  315. package/dist/web/_app/immutable/nodes/0.B-0CcADM.js +0 -1
  316. package/dist/web/_app/immutable/nodes/1.FixKgvRO.js +0 -1
  317. package/dist/web/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
  318. package/dist/workspace-DjoNjhW0.mjs.map +0 -1
  319. package/src/daemon/message-verbosity.test.ts +0 -127
  320. package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +0 -1
  321. package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +0 -1
  322. package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
  323. package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +0 -2
  324. package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +0 -1
  325. package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +0 -1
  326. package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +0 -2
  327. package/web/.svelte-kit/output/client/_app/immutable/chunks/DQoygso7.js +0 -1
  328. package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +0 -1
  329. package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B-0CcADM.js +0 -1
  330. package/web/.svelte-kit/output/client/_app/immutable/nodes/1.FixKgvRO.js +0 -1
  331. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
  332. package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +0 -1
  333. /package/templates/{gemini-claw/.gemini/skills → skills}/clawmini-jobs/SKILL.md +0 -0
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import { l as getSocketPath, o as getClawminiDir, u as getWorkspaceRoot } from "../workspace-DjoNjhW0.mjs";
2
+ import { c as getClawminiDir, d as getSocketPath, f as getWorkspaceRoot } from "../workspace-BJmJBfKi.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
5
  import fs from "node:fs";
5
6
  import path from "node:path";
6
7
  import fs$1 from "node:fs/promises";
7
- import { fileURLToPath } from "node:url";
8
8
  import { z } from "zod";
9
9
  import { createTRPCClient, httpLink, httpSubscriptionLink, splitLink } from "@trpc/client";
10
- import http from "node:http";
11
- import { Client, Events, GatewayIntentBits, Partials } from "discord.js";
10
+ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, Colors, EmbedBuilder, Events, GatewayIntentBits, ModalBuilder, Partials, TextInputBuilder, TextInputStyle } from "discord.js";
12
11
 
13
12
  //#region src/adapter-discord/config.ts
14
13
  const DiscordConfigSchema = z.looseObject({
15
14
  botToken: z.string().min(1, "Discord Bot Token is required."),
16
15
  authorizedUserId: z.string().min(1, "Authorized Discord User ID is required."),
17
16
  chatId: z.string().default("default"),
18
- maxAttachmentSizeMB: z.number().default(25).optional()
17
+ maxAttachmentSizeMB: z.number().default(25),
18
+ requireMention: z.boolean().default(false)
19
19
  });
20
20
  function getDiscordConfigPath(startDir = process.cwd()) {
21
21
  return path.join(getClawminiDir(startDir), "adapters", "discord", "config.json");
@@ -25,14 +25,10 @@ async function readDiscordConfig(startDir = process.cwd()) {
25
25
  try {
26
26
  const data = await fs$1.readFile(configPath, "utf-8");
27
27
  const parsed = JSON.parse(data);
28
- const result = DiscordConfigSchema.safeParse(parsed);
29
- if (!result.success) {
30
- console.error("Invalid Discord configuration:", result.error.format());
31
- return null;
32
- }
33
- return result.data;
34
- } catch {
35
- return null;
28
+ return DiscordConfigSchema.parse(parsed);
29
+ } catch (err) {
30
+ if (err.code === "ENOENT") return null;
31
+ throw err;
36
32
  }
37
33
  }
38
34
  async function initDiscordConfig(startDir = process.cwd()) {
@@ -51,98 +47,174 @@ async function initDiscordConfig(startDir = process.cwd()) {
51
47
  console.log(`Created template configuration file at ${configPath}`);
52
48
  console.log("Please update it with your actual Discord Bot Token and User ID.");
53
49
  }
54
- function isAuthorized(userId, authorizedUserId) {
50
+ function isAuthorized$1(userId, authorizedUserId) {
55
51
  return userId === authorizedUserId;
56
52
  }
57
53
 
58
54
  //#endregion
59
- //#region src/shared/event-source.ts
60
- function createUnixSocketEventSource(socketPath) {
61
- return class UnixSocketEventSource {
62
- readyState = 0;
63
- CONNECTING = 0;
64
- OPEN = 1;
65
- CLOSED = 2;
66
- req = null;
67
- listeners = {};
68
- constructor(url, init) {
69
- const parsedUrl = new URL(url);
70
- const options = {
71
- socketPath,
72
- path: parsedUrl.pathname + parsedUrl.search,
73
- method: "GET",
74
- headers: {
75
- Accept: "text/event-stream",
76
- "Cache-Control": "no-cache",
77
- ...init?.headers
78
- }
79
- };
80
- this.req = http.request(options, (res) => {
81
- if (res.statusCode === 200) {
82
- this.readyState = this.OPEN;
83
- this.dispatchEvent({ type: "open" });
84
- } else {
85
- this.readyState = this.CLOSED;
86
- this.dispatchEvent({
87
- type: "error",
88
- message: `Unexpected status code: ${res.statusCode}`
89
- });
90
- return;
91
- }
92
- let buffer = "";
93
- res.on("data", (chunk) => {
94
- buffer += chunk.toString("utf-8");
95
- const lines = buffer.split(/\r?\n\r?\n/);
96
- buffer = lines.pop() || "";
97
- for (const block of lines) this.parseBlock(block);
98
- });
99
- res.on("end", () => {
100
- if (buffer) this.parseBlock(buffer);
101
- this.readyState = this.CLOSED;
102
- this.dispatchEvent({ type: "close" });
103
- });
55
+ //#region src/adapter-discord/state.ts
56
+ const DiscordStateSchema = z.object({
57
+ lastSyncedMessageIds: z.record(z.string(), z.string()).optional(),
58
+ channelChatMap: z.record(z.string(), z.object({
59
+ chatId: z.string().nullable().optional(),
60
+ requireMention: z.boolean().optional()
61
+ })).optional(),
62
+ filters: z.record(z.string(), z.boolean()).optional()
63
+ });
64
+ function getDiscordStatePath(startDir = process.cwd()) {
65
+ return path.join(getClawminiDir(startDir), "adapters", "discord", "state.json");
66
+ }
67
+ async function readDiscordState(startDir = process.cwd()) {
68
+ const statePath = getDiscordStatePath(startDir);
69
+ try {
70
+ const data = await fs$1.readFile(statePath, "utf-8");
71
+ const parsed = JSON.parse(data);
72
+ if (parsed.lastSyncedMessageId && !parsed.lastSyncedMessageIds) parsed.lastSyncedMessageIds = { default: parsed.lastSyncedMessageId };
73
+ if (parsed.channelChatMap) {
74
+ for (const [key, value] of Object.entries(parsed.channelChatMap)) if (typeof value === "string") parsed.channelChatMap[key] = { chatId: value };
75
+ }
76
+ return DiscordStateSchema.parse(parsed);
77
+ } catch (err) {
78
+ if (err.code === "ENOENT") return {};
79
+ throw err;
80
+ }
81
+ }
82
+ async function writeDiscordState(state, startDir = process.cwd()) {
83
+ const statePath = getDiscordStatePath(startDir);
84
+ const dir = path.dirname(statePath);
85
+ try {
86
+ await fs$1.mkdir(dir, { recursive: true });
87
+ await fs$1.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
88
+ } catch (err) {
89
+ console.error(`Failed to write Discord state to ${statePath}:`, err);
90
+ }
91
+ }
92
+ let stateUpdatePromise = Promise.resolve();
93
+ function updateDiscordState(updates, startDir = process.cwd()) {
94
+ return new Promise((resolve, reject) => {
95
+ stateUpdatePromise = stateUpdatePromise.then(async () => {
96
+ try {
97
+ const currentState = await readDiscordState(startDir);
98
+ const resolvedUpdates = typeof updates === "function" ? updates(currentState) : updates;
99
+ const newState = {
100
+ ...currentState,
101
+ ...resolvedUpdates
102
+ };
103
+ await writeDiscordState(newState, startDir);
104
+ resolve(newState);
105
+ } catch (err) {
106
+ console.error(`Failed to write Discord state:`, err);
107
+ reject(err);
108
+ }
109
+ });
110
+ });
111
+ }
112
+
113
+ //#endregion
114
+ //#region src/adapter-discord/interactions.ts
115
+ function isAuthorized(userId, authorizedUserId) {
116
+ return userId === authorizedUserId;
117
+ }
118
+ async function handleDiscordInteraction(interaction, config, trpc) {
119
+ if (!interaction.isButton() && !interaction.isModalSubmit()) return;
120
+ if (!isAuthorized(interaction.user.id, config.authorizedUserId)) {
121
+ if (interaction.isRepliable()) await interaction.reply({
122
+ content: "You are not authorized to perform this action.",
123
+ ephemeral: true
124
+ });
125
+ return;
126
+ }
127
+ if (interaction.isButton()) {
128
+ if (interaction.customId.startsWith("approve_") || interaction.customId.startsWith("approve|")) {
129
+ let policyId, explicitChatId;
130
+ if (interaction.customId.startsWith("approve|")) {
131
+ const parts = interaction.customId.split("|");
132
+ policyId = parts[1];
133
+ explicitChatId = parts[2] || void 0;
134
+ } else policyId = interaction.customId.replace("approve_", "");
135
+ await interaction.update({ components: [] });
136
+ await interaction.followUp({
137
+ content: `Approving policy ${policyId}...`,
138
+ ephemeral: true
104
139
  });
105
- this.req.on("error", (err) => {
106
- this.readyState = this.CLOSED;
107
- this.dispatchEvent({
108
- type: "error",
109
- error: err
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
+ }
110
152
  });
111
- });
112
- this.req.end();
153
+ } catch (error) {
154
+ console.error("Failed to send approve command to daemon:", error);
155
+ await interaction.followUp({
156
+ content: `Failed to approve policy ${policyId}.`,
157
+ ephemeral: true
158
+ });
159
+ }
160
+ } else if (interaction.customId.startsWith("reject_") || interaction.customId.startsWith("reject|")) {
161
+ let policyId, explicitChatId;
162
+ if (interaction.customId.startsWith("reject|")) {
163
+ const parts = interaction.customId.split("|");
164
+ policyId = parts[1];
165
+ explicitChatId = parts[2] || "";
166
+ } else {
167
+ policyId = interaction.customId.replace("reject_", "");
168
+ explicitChatId = "";
169
+ }
170
+ const modal = new ModalBuilder().setCustomId(`modal_reject|${policyId}|${explicitChatId}`).setTitle("Reject Policy");
171
+ const rationaleInput = new TextInputBuilder().setCustomId("rationale").setLabel("Rationale (optional)").setStyle(TextInputStyle.Paragraph).setRequired(false);
172
+ const actionRow = new ActionRowBuilder().addComponents(rationaleInput);
173
+ modal.addComponents(actionRow);
174
+ await interaction.showModal(modal);
113
175
  }
114
- parseBlock(block) {
115
- if (!block.trim()) return;
116
- const lines = block.split(/\r?\n/);
117
- let eventType = "message";
118
- let data = "";
119
- let id = "";
120
- for (const line of lines) if (line.startsWith("event: ")) eventType = line.slice(7).trim();
121
- else if (line.startsWith("data: ")) data += (data ? "\n" : "") + line.slice(6);
122
- else if (line.startsWith("id: ")) id = line.slice(4).trim();
123
- if (data) this.dispatchEvent({
124
- type: eventType,
125
- data,
126
- lastEventId: id
176
+ } else if (interaction.isModalSubmit()) {
177
+ if (interaction.customId.startsWith("modal_reject_") || interaction.customId.startsWith("modal_reject|")) {
178
+ let policyId, explicitChatId;
179
+ if (interaction.customId.startsWith("modal_reject|")) {
180
+ const parts = interaction.customId.split("|");
181
+ policyId = parts[1];
182
+ explicitChatId = parts[2] || void 0;
183
+ } else policyId = interaction.customId.replace("modal_reject_", "");
184
+ const rationale = interaction.fields.getTextInputValue("rationale");
185
+ const command = rationale ? `/reject ${policyId} ${rationale}` : `/reject ${policyId}`;
186
+ if (interaction.isFromMessage()) {
187
+ await interaction.update({ components: [] });
188
+ await interaction.followUp({
189
+ content: `Rejecting policy ${policyId}...`,
190
+ ephemeral: true
191
+ });
192
+ } else await interaction.reply({
193
+ content: `Rejecting policy ${policyId}...`,
194
+ ephemeral: true
127
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);
211
+ await interaction.followUp({
212
+ content: `Failed to reject policy ${policyId}.`,
213
+ ephemeral: true
214
+ });
215
+ }
128
216
  }
129
- addEventListener(type, listener) {
130
- if (!this.listeners[type]) this.listeners[type] = [];
131
- this.listeners[type].push(listener);
132
- }
133
- removeEventListener(type, listener) {
134
- if (!this.listeners[type]) return;
135
- this.listeners[type] = this.listeners[type].filter((l) => l !== listener);
136
- }
137
- dispatchEvent(event) {
138
- const type = event.type;
139
- if (this.listeners[type]) for (const listener of this.listeners[type]) listener(event);
140
- }
141
- close() {
142
- this.readyState = this.CLOSED;
143
- if (this.req) this.req.destroy();
144
- }
145
- };
217
+ }
146
218
  }
147
219
 
148
220
  //#endregion
@@ -173,65 +245,66 @@ function getTRPCClient(options = {}) {
173
245
  }
174
246
 
175
247
  //#endregion
176
- //#region src/adapter-discord/state.ts
177
- const DiscordStateSchema = z.object({ lastSyncedMessageId: z.string().optional() });
178
- function getDiscordStatePath(startDir = process.cwd()) {
179
- return path.join(getClawminiDir(startDir), "adapters", "discord", "state.json");
180
- }
181
- async function readDiscordState(startDir = process.cwd()) {
182
- const statePath = getDiscordStatePath(startDir);
183
- try {
184
- const data = await fs$1.readFile(statePath, "utf-8");
185
- const parsed = JSON.parse(data);
186
- const result = DiscordStateSchema.safeParse(parsed);
187
- if (!result.success) return { lastSyncedMessageId: void 0 };
188
- return result.data;
189
- } catch {
190
- return { lastSyncedMessageId: void 0 };
248
+ //#region src/adapter-discord/forwarder.ts
249
+ async function resolveDiscordDestination(client, discordUserId, chatId) {
250
+ const channelChatMap = (await readDiscordState()).channelChatMap || {};
251
+ let targetDiscordChannelId;
252
+ for (const [channelId, mappedChatId] of Object.entries(channelChatMap)) if (mappedChatId?.chatId === chatId) {
253
+ targetDiscordChannelId = channelId;
254
+ break;
191
255
  }
192
- }
193
- async function writeDiscordState(state, startDir = process.cwd()) {
194
- const statePath = getDiscordStatePath(startDir);
195
- const dir = path.dirname(statePath);
196
- try {
197
- await fs$1.mkdir(dir, { recursive: true });
198
- await fs$1.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
199
- } catch (err) {
200
- console.error(`Failed to write Discord state to ${statePath}:`, err);
256
+ if (targetDiscordChannelId) try {
257
+ const channel = await client.channels.fetch(targetDiscordChannelId);
258
+ if (channel && channel.isTextBased() && !channel.isDMBased()) return channel;
259
+ } catch (error) {
260
+ console.warn(`Failed to fetch mapped channel ${targetDiscordChannelId} for chat ${chatId}, falling back to DM.`, error);
201
261
  }
262
+ return (await client.users.fetch(discordUserId)).createDM();
202
263
  }
203
-
204
- //#endregion
205
- //#region src/adapter-discord/forwarder.ts
206
- async function startDaemonToDiscordForwarder(client, trpc, discordUserId, chatId = "default", signal) {
207
- let lastMessageId = (await readDiscordState()).lastSyncedMessageId;
208
- if (!lastMessageId) try {
209
- const messages = await trpc.getMessages.query({
210
- chatId,
211
- limit: 1
212
- });
213
- if (Array.isArray(messages) && messages.length > 0) {
214
- const lastMsg = messages[messages.length - 1];
215
- if (lastMsg) {
216
- lastMessageId = lastMsg.id;
217
- await writeDiscordState({ lastSyncedMessageId: lastMessageId });
264
+ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, options = {}) {
265
+ const defaultChatId = options.chatId ?? "default";
266
+ const signal = options.signal;
267
+ const config = options.config ?? {};
268
+ const activeSubscriptions = /* @__PURE__ */ new Map();
269
+ const activeTypingSubscriptions = /* @__PURE__ */ new Map();
270
+ let currentLastSyncedMessageIds = (await readDiscordState()).lastSyncedMessageIds || {};
271
+ const saveLastMessageId = async (chatId, id) => {
272
+ currentLastSyncedMessageIds = {
273
+ ...currentLastSyncedMessageIds,
274
+ [chatId]: id
275
+ };
276
+ return updateDiscordState((state) => ({ lastSyncedMessageIds: {
277
+ ...state.lastSyncedMessageIds,
278
+ ...currentLastSyncedMessageIds
279
+ } }));
280
+ };
281
+ const startSubscriptionForChat = async (chatId) => {
282
+ if (activeSubscriptions.has(chatId)) return;
283
+ if (signal?.aborted) return;
284
+ let lastMessageId = currentLastSyncedMessageIds[chatId];
285
+ if (!lastMessageId) try {
286
+ const messages = await trpc.getMessages.query({
287
+ chatId,
288
+ limit: 1
289
+ });
290
+ if (Array.isArray(messages) && messages.length > 0) {
291
+ const lastMsg = messages[messages.length - 1];
292
+ if (lastMsg) {
293
+ await saveLastMessageId(chatId, lastMsg.id);
294
+ lastMessageId = lastMsg.id;
295
+ }
218
296
  }
297
+ } catch (error) {
298
+ if (signal?.aborted) return;
299
+ console.error(`Failed to fetch initial messages from daemon for ${chatId}:`, error);
219
300
  }
220
- } catch (error) {
221
- if (signal?.aborted) return;
222
- console.error("Failed to fetch initial messages from daemon:", error);
223
- }
224
- console.log(`Starting daemon-to-discord forwarder for chat ${chatId}, lastMessageId: ${lastMessageId}`);
225
- let retryDelay = 1e3;
226
- const maxRetryDelay = 3e4;
227
- return new Promise((resolve) => {
301
+ console.log(`Starting daemon-to-discord forwarder for chat ${chatId}, lastMessageId: ${lastMessageId}`);
302
+ let retryDelay = 1e3;
303
+ const maxRetryDelay = 3e4;
228
304
  let subscription = null;
229
305
  let messageQueue = Promise.resolve();
230
306
  const connect = () => {
231
- if (signal?.aborted) {
232
- resolve();
233
- return;
234
- }
307
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
235
308
  subscription = trpc.waitForMessages.subscribe({
236
309
  chatId,
237
310
  lastMessageId
@@ -241,61 +314,83 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, chatId
241
314
  if (!Array.isArray(messages) || messages.length === 0) return;
242
315
  messageQueue = messageQueue.then(async () => {
243
316
  for (const rawMessage of messages) {
244
- if (signal?.aborted) break;
317
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
245
318
  const message = rawMessage;
246
- if (message.role === "log") {
319
+ if (shouldDisplayMessage(message, config)) {
247
320
  const logMessage = message;
248
- if (logMessage.level === "verbose") {
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);
249
346
  lastMessageId = logMessage.id;
250
- await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(console.error);
251
347
  continue;
252
348
  }
253
349
  const hasContent = !!logMessage.content?.trim();
254
- const hasFiles = Array.isArray(logMessage.files) && logMessage.files.length > 0;
350
+ const files = "files" in logMessage ? logMessage.files : void 0;
351
+ const hasFiles = Array.isArray(files) && files.length > 0;
255
352
  let absoluteFiles = [];
256
- if (hasFiles) {
353
+ if (hasFiles && files) {
257
354
  const workspaceRoot = getWorkspaceRoot(process.cwd());
258
- absoluteFiles = logMessage.files.map((f) => path.resolve(workspaceRoot, f));
355
+ absoluteFiles = files.map((f) => path.resolve(workspaceRoot, f));
259
356
  }
260
357
  if (!hasContent && !hasFiles) {
358
+ await saveLastMessageId(chatId, logMessage.id).catch(console.error);
261
359
  lastMessageId = logMessage.id;
262
- await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(console.error);
263
360
  continue;
264
361
  }
265
362
  try {
266
- const dm = await (await client.users.fetch(discordUserId)).createDM();
267
- if (hasContent && logMessage.content.length > 2e3) {
268
- const chunks = chunkString(logMessage.content, 2e3);
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);
269
367
  for (let i = 0; i < chunks.length; i++) {
270
- if (signal?.aborted) break;
368
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) break;
271
369
  const chunkOptions = { content: chunks[i] };
272
370
  if (i === chunks.length - 1 && hasFiles) chunkOptions.files = absoluteFiles;
273
371
  await dm.send(chunkOptions);
274
372
  }
275
373
  } else {
276
- const options = {};
277
- if (hasContent) options.content = logMessage.content;
278
- if (hasFiles) options.files = absoluteFiles;
279
- await dm.send(options);
374
+ const optionsMsg = {};
375
+ if (formattedContent) optionsMsg.content = formattedContent;
376
+ if (hasFiles) optionsMsg.files = absoluteFiles;
377
+ await dm.send(optionsMsg);
280
378
  }
281
379
  } catch (error) {
282
380
  console.error(`Failed to send message to Discord user ${discordUserId}:`, error);
283
381
  break;
284
382
  }
285
383
  }
384
+ await saveLastMessageId(chatId, message.id).catch(console.error);
286
385
  lastMessageId = message.id;
287
- await writeDiscordState({ lastSyncedMessageId: lastMessageId }).catch(console.error);
288
386
  }
289
387
  });
290
388
  },
291
389
  onError: (error) => {
292
- console.error(`Error in daemon-to-discord forwarder subscription. Retrying in ${retryDelay}ms.`, error);
390
+ console.error(`Error in daemon-to-discord forwarder subscription for ${chatId}. Retrying in ${retryDelay}ms.`, error);
293
391
  subscription?.unsubscribe();
294
392
  subscription = null;
295
- if (signal?.aborted) {
296
- resolve();
297
- return;
298
- }
393
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
299
394
  setTimeout(() => {
300
395
  retryDelay = Math.min(retryDelay * 2, maxRetryDelay);
301
396
  connect();
@@ -303,30 +398,30 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, chatId
303
398
  },
304
399
  onComplete: () => {
305
400
  subscription = null;
306
- if (!signal?.aborted) setTimeout(() => connect(), retryDelay);
307
- else resolve();
401
+ if (!signal?.aborted && activeSubscriptions.has(chatId)) setTimeout(() => connect(), retryDelay);
308
402
  }
309
403
  });
310
404
  };
311
405
  let typingSubscription = null;
312
406
  let typingRetryDelay = 1e3;
313
407
  const connectTyping = () => {
314
- if (signal?.aborted) return;
408
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
315
409
  typingSubscription = trpc.waitForTyping.subscribe({ chatId }, {
316
410
  onData: async (event) => {
317
411
  typingRetryDelay = 1e3;
318
412
  if (!event) return;
319
413
  try {
320
- await (await (await client.users.fetch(discordUserId)).createDM()).sendTyping();
414
+ const dm = await resolveDiscordDestination(client, discordUserId, chatId);
415
+ if (dm.sendTyping) await dm.sendTyping();
321
416
  } catch (error) {
322
417
  console.error(`Failed to send typing indicator to Discord user ${discordUserId}:`, error);
323
418
  }
324
419
  },
325
420
  onError: (error) => {
326
- console.error(`Error in daemon-to-discord typing forwarder subscription. Retrying in ${typingRetryDelay}ms.`, error);
421
+ console.error(`Error in daemon-to-discord typing forwarder subscription for ${chatId}. Retrying in ${typingRetryDelay}ms.`, error);
327
422
  typingSubscription?.unsubscribe();
328
423
  typingSubscription = null;
329
- if (signal?.aborted) return;
424
+ if (signal?.aborted || !activeSubscriptions.has(chatId)) return;
330
425
  setTimeout(() => {
331
426
  typingRetryDelay = Math.min(typingRetryDelay * 2, maxRetryDelay);
332
427
  connectTyping();
@@ -334,15 +429,54 @@ async function startDaemonToDiscordForwarder(client, trpc, discordUserId, chatId
334
429
  },
335
430
  onComplete: () => {
336
431
  typingSubscription = null;
337
- if (!signal?.aborted) setTimeout(() => connectTyping(), typingRetryDelay);
432
+ if (!signal?.aborted && activeSubscriptions.has(chatId)) setTimeout(() => connectTyping(), typingRetryDelay);
338
433
  }
339
434
  });
340
435
  };
436
+ activeSubscriptions.set(chatId, { unsubscribe: () => subscription?.unsubscribe() });
437
+ activeTypingSubscriptions.set(chatId, { unsubscribe: () => typingSubscription?.unsubscribe() });
341
438
  connect();
342
439
  connectTyping();
440
+ };
441
+ const syncSubscriptions = async () => {
442
+ if (signal?.aborted) return;
443
+ const state = await readDiscordState();
444
+ if (state.lastSyncedMessageIds) currentLastSyncedMessageIds = {
445
+ ...currentLastSyncedMessageIds,
446
+ ...state.lastSyncedMessageIds
447
+ };
448
+ const targetChatIds = /* @__PURE__ */ new Set();
449
+ targetChatIds.add(defaultChatId);
450
+ if (state.channelChatMap) {
451
+ for (const mappedEntry of Object.values(state.channelChatMap)) if (mappedEntry.chatId) targetChatIds.add(mappedEntry.chatId);
452
+ }
453
+ for (const targetChatId of targetChatIds) if (!activeSubscriptions.has(targetChatId)) startSubscriptionForChat(targetChatId);
454
+ for (const [activeChatId, sub] of activeSubscriptions.entries()) if (!targetChatIds.has(activeChatId)) {
455
+ sub.unsubscribe();
456
+ activeSubscriptions.delete(activeChatId);
457
+ activeTypingSubscriptions.get(activeChatId)?.unsubscribe();
458
+ activeTypingSubscriptions.delete(activeChatId);
459
+ }
460
+ };
461
+ return new Promise((resolve) => {
462
+ syncSubscriptions().catch(console.error);
463
+ const statePath = getDiscordStatePath();
464
+ const stateDir = path.dirname(statePath);
465
+ if (!fs.existsSync(stateDir)) fs.mkdirSync(stateDir, { recursive: true });
466
+ let debounceTimer = null;
467
+ const watcher = fs.watch(stateDir, (eventType, filename) => {
468
+ if (filename === path.basename(statePath)) {
469
+ if (debounceTimer) clearTimeout(debounceTimer);
470
+ debounceTimer = setTimeout(() => {
471
+ syncSubscriptions().catch(console.error);
472
+ }, 200);
473
+ }
474
+ });
343
475
  signal?.addEventListener("abort", () => {
344
- subscription?.unsubscribe();
345
- typingSubscription?.unsubscribe();
476
+ if (debounceTimer) clearTimeout(debounceTimer);
477
+ watcher.close();
478
+ for (const sub of activeSubscriptions.values()) sub.unsubscribe();
479
+ for (const sub of activeTypingSubscriptions.values()) sub.unsubscribe();
346
480
  resolve();
347
481
  });
348
482
  });
@@ -369,23 +503,100 @@ async function main() {
369
503
  }
370
504
  const trpc = getTRPCClient();
371
505
  const client = new Client({
372
- intents: [GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent],
506
+ intents: [
507
+ GatewayIntentBits.DirectMessages,
508
+ GatewayIntentBits.MessageContent,
509
+ GatewayIntentBits.Guilds,
510
+ GatewayIntentBits.GuildMessages
511
+ ],
373
512
  partials: [Partials.Channel]
374
513
  });
514
+ const filteringConfig = { filters: (await readDiscordState()).filters };
375
515
  client.once(Events.ClientReady, (readyClient) => {
376
516
  console.log(`Ready! Logged in as ${readyClient.user.tag}`);
377
- startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, config.chatId).catch((error) => {
517
+ startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, {
518
+ chatId: config.chatId,
519
+ config: filteringConfig
520
+ }).catch((error) => {
378
521
  console.error("Error in daemon-to-discord forwarder:", error);
379
522
  });
380
523
  });
381
524
  client.on(Events.MessageCreate, async (message) => {
382
525
  if (message.author.id === client.user?.id) return;
383
- if (message.guild) return;
384
- if (!isAuthorized(message.author.id, config.authorizedUserId)) {
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)) {
385
545
  console.log(`Unauthorized message from ${message.author.tag} (${message.author.id}) ignored.`);
386
546
  return;
387
547
  }
388
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
+ }
389
600
  const downloadedFiles = [];
390
601
  if (message.attachments.size > 0) {
391
602
  const tmpDir = path.join(getClawminiDir(process.cwd()), "tmp", "discord");
@@ -428,7 +639,7 @@ async function main() {
428
639
  client: "cli",
429
640
  data: {
430
641
  message: finalContent,
431
- chatId: config.chatId,
642
+ chatId: targetChatId,
432
643
  files: downloadedFiles.length > 0 ? downloadedFiles : void 0,
433
644
  adapter: "discord",
434
645
  noWait: true
@@ -439,6 +650,9 @@ async function main() {
439
650
  console.error("Failed to forward message to daemon:", error);
440
651
  }
441
652
  });
653
+ client.on(Events.InteractionCreate, async (interaction) => {
654
+ await handleDiscordInteraction(interaction, config, trpc);
655
+ });
442
656
  try {
443
657
  await client.login(config.botToken);
444
658
  } catch (error) {
@@ -446,16 +660,7 @@ async function main() {
446
660
  process.exit(1);
447
661
  }
448
662
  }
449
- if ((() => {
450
- try {
451
- if (typeof process === "undefined" || !process.argv || process.argv.length < 2) return false;
452
- const argv1 = process.argv[1];
453
- if (!argv1) return false;
454
- return path.resolve(argv1) === path.resolve(fileURLToPath(import.meta.url));
455
- } catch {
456
- return false;
457
- }
458
- })()) main().catch((error) => {
663
+ main().catch((error) => {
459
664
  console.error("Unhandled error in Discord Adapter:", error);
460
665
  process.exit(1);
461
666
  });