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.
- package/README.md +19 -0
- package/dist/adapter-discord/index.d.mts.map +1 -1
- package/dist/adapter-discord/index.mjs +398 -193
- package/dist/adapter-discord/index.mjs.map +1 -1
- package/dist/adapter-google-chat/index.d.mts +5 -0
- package/dist/adapter-google-chat/index.d.mts.map +1 -0
- package/dist/adapter-google-chat/index.mjs +1077 -0
- package/dist/adapter-google-chat/index.mjs.map +1 -0
- package/dist/cli/index.mjs +107 -14
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lite.mjs +175 -16
- package/dist/cli/lite.mjs.map +1 -1
- package/dist/cli/propose-policy.d.mts +1 -0
- package/dist/cli/propose-policy.mjs +7159 -0
- package/dist/cli/propose-policy.mjs.map +1 -0
- package/dist/daemon/index.d.mts.map +1 -1
- package/dist/daemon/index.mjs +1427 -513
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{lite-oSYSvaOr.mjs → lite-CBxOT1y5.mjs} +101 -24
- package/dist/lite-CBxOT1y5.mjs.map +1 -0
- package/dist/routing-D8rTxtaV.mjs +245 -0
- package/dist/routing-D8rTxtaV.mjs.map +1 -0
- package/dist/web/_app/immutable/assets/0.C-4eziNy.css +1 -0
- package/dist/web/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
- package/dist/web/_app/immutable/chunks/B6YN0Nuq.js +1 -0
- package/dist/web/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
- package/{web/.svelte-kit/output/client/_app/immutable/chunks/8YNcRyEk.js → dist/web/_app/immutable/chunks/C20lZMGz.js} +1 -1
- package/dist/web/_app/immutable/chunks/C9lbZ-kT.js +1 -0
- package/dist/web/_app/immutable/chunks/CK9JZLaG.js +2 -0
- package/dist/web/_app/immutable/chunks/CME08kGM.js +1 -0
- package/dist/web/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
- package/dist/web/_app/immutable/chunks/Ck3rYNON.js +1 -0
- package/dist/web/_app/immutable/chunks/DMtIqaiV.js +2 -0
- package/dist/web/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
- package/dist/web/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
- package/dist/web/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
- package/dist/web/_app/immutable/chunks/DsIToJCP.js +1 -0
- package/dist/web/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
- package/{web/.svelte-kit/output/client/_app/immutable/entry/app.DO5eYwVz.js → dist/web/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
- package/dist/web/_app/immutable/entry/start.DuxJo6av.js +1 -0
- package/dist/web/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
- package/dist/web/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
- package/dist/web/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
- package/dist/web/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
- package/dist/web/_app/immutable/nodes/4.DLarELN4.js +60 -0
- package/dist/web/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +12 -12
- package/dist/{workspace-DjoNjhW0.mjs → workspace-BJmJBfKi.mjs} +103 -11
- package/dist/workspace-BJmJBfKi.mjs.map +1 -0
- package/docs/14_google_chat_adapter/development_log.md +40 -0
- package/docs/14_google_chat_adapter/notes.md +28 -0
- package/docs/14_google_chat_adapter/prd.md +35 -0
- package/docs/14_google_chat_adapter/questions.md +9 -0
- package/docs/14_google_chat_adapter/tickets.md +117 -0
- package/docs/15_sandbox_policies/tickets.md +33 -0
- package/docs/16_session_timeout/development_log.md +20 -0
- package/docs/16_session_timeout/notes.md +44 -0
- package/docs/16_session_timeout/prd.md +106 -0
- package/docs/16_session_timeout/questions.md +10 -0
- package/docs/16_session_timeout/tickets.md +64 -0
- package/docs/17_auto_approve_policy/development_log.md +29 -0
- package/docs/17_auto_approve_policy/notes.md +25 -0
- package/docs/17_auto_approve_policy/prd.md +34 -0
- package/docs/17_auto_approve_policy/questions.md +10 -0
- package/docs/17_auto_approve_policy/tickets.md +11 -0
- package/docs/18_clawmini_skills/development_log.md +36 -0
- package/docs/18_clawmini_skills/notes.md +8 -0
- package/docs/18_clawmini_skills/prd.md +45 -0
- package/docs/18_clawmini_skills/questions.md +10 -0
- package/docs/18_clawmini_skills/tickets.md +55 -0
- package/docs/19_subagents/development_log.md +69 -0
- package/docs/19_subagents/notes.md +18 -0
- package/docs/19_subagents/prd.md +156 -0
- package/docs/19_subagents/questions.md +13 -0
- package/docs/19_subagents/tickets.md +113 -0
- package/docs/20_chat_logs_cleanup/development_log.md +50 -0
- package/docs/20_chat_logs_cleanup/notes.md +43 -0
- package/docs/20_chat_logs_cleanup/prd.md +232 -0
- package/docs/20_chat_logs_cleanup/questions.md +2 -0
- package/docs/20_chat_logs_cleanup/tickets.md +98 -0
- package/docs/20_webui_markdown/development_log.md +36 -0
- package/docs/20_webui_markdown/notes.md +23 -0
- package/docs/20_webui_markdown/prd.md +49 -0
- package/docs/20_webui_markdown/questions.md +10 -0
- package/docs/20_webui_markdown/tickets.md +55 -0
- package/docs/21_adapter_filtering/development_log.md +29 -0
- package/docs/21_adapter_filtering/notes.md +25 -0
- package/docs/21_adapter_filtering/prd.md +44 -0
- package/docs/21_adapter_filtering/questions.md +12 -0
- package/docs/21_adapter_filtering/tickets.md +38 -0
- package/docs/21_built_in_routers/development_log.md +17 -0
- package/docs/21_built_in_routers/notes.md +27 -0
- package/docs/21_built_in_routers/prd.md +34 -0
- package/docs/21_built_in_routers/questions.md +4 -0
- package/docs/21_built_in_routers/tickets.md +25 -0
- package/docs/21_fancy_policies/development_log.md +38 -0
- package/docs/21_fancy_policies/notes.md +27 -0
- package/docs/21_fancy_policies/prd.md +58 -0
- package/docs/21_fancy_policies/questions.md +6 -0
- package/docs/21_fancy_policies/tickets.md +48 -0
- package/docs/22_adapter_multi_chat/development_log.md +76 -0
- package/docs/22_adapter_multi_chat/notes.md +42 -0
- package/docs/22_adapter_multi_chat/prd.md +76 -0
- package/docs/22_adapter_multi_chat/questions.md +16 -0
- package/docs/22_adapter_multi_chat/tickets.md +164 -0
- package/docs/23_custom_token_env/development_log.md +31 -0
- package/docs/23_custom_token_env/notes.md +16 -0
- package/docs/23_custom_token_env/prd.md +42 -0
- package/docs/23_custom_token_env/questions.md +8 -0
- package/docs/23_custom_token_env/tickets.md +54 -0
- package/docs/guides/discord_adapter_setup.md +15 -2
- package/docs/guides/google_chat_adapter_setup.md +145 -0
- package/napkin.md +5 -0
- package/package.json +7 -2
- package/src/adapter-discord/config.test.ts +27 -8
- package/src/adapter-discord/config.ts +6 -8
- package/src/adapter-discord/forwarder.test.ts +307 -114
- package/src/adapter-discord/forwarder.ts +260 -75
- package/src/adapter-discord/index.test.ts +278 -0
- package/src/adapter-discord/index.ts +160 -30
- package/src/adapter-discord/interactions.test.ts +96 -0
- package/src/adapter-discord/interactions.ts +156 -0
- package/src/adapter-discord/state.test.ts +9 -8
- package/src/adapter-discord/state.ts +51 -8
- package/src/adapter-google-chat/auth.test.ts +87 -0
- package/src/adapter-google-chat/auth.ts +132 -0
- package/src/adapter-google-chat/cards.ts +71 -0
- package/src/adapter-google-chat/client.test.ts +561 -0
- package/src/adapter-google-chat/client.ts +430 -0
- package/src/adapter-google-chat/config.test.ts +187 -0
- package/src/adapter-google-chat/config.ts +82 -0
- package/src/adapter-google-chat/cron.test.ts +143 -0
- package/src/adapter-google-chat/cron.ts +81 -0
- package/src/adapter-google-chat/forwarder.test.ts +537 -0
- package/src/adapter-google-chat/forwarder.ts +349 -0
- package/src/adapter-google-chat/index.test.ts +62 -0
- package/src/adapter-google-chat/index.ts +61 -0
- package/src/adapter-google-chat/state.test.ts +96 -0
- package/src/adapter-google-chat/state.ts +85 -0
- package/src/adapter-google-chat/subscriptions.ts +124 -0
- package/src/adapter-google-chat/upload.ts +88 -0
- package/src/adapter-google-chat/utils.test.ts +111 -0
- package/src/adapter-google-chat/utils.ts +133 -0
- package/src/cli/commands/init.ts +0 -7
- package/src/cli/commands/messages.ts +18 -3
- package/src/cli/commands/policies.ts +70 -0
- package/src/cli/commands/skills.ts +71 -0
- package/src/cli/commands/web-api/chats.ts +5 -1
- package/src/cli/e2e/basic.test.ts +1 -1
- package/src/cli/e2e/cron.test.ts +1 -1
- package/src/cli/e2e/daemon.test.ts +132 -4
- package/src/cli/e2e/export-lite-func.test.ts +54 -31
- package/src/cli/e2e/fallbacks.test.ts +8 -6
- package/src/cli/e2e/init.test.ts +7 -0
- package/src/cli/e2e/messages.test.ts +90 -55
- package/src/cli/e2e/propose-policy.test.ts +203 -0
- package/src/cli/e2e/requests.test.ts +15 -0
- package/src/cli/e2e/session-timeout.test.ts +192 -0
- package/src/cli/e2e/skills.test.ts +55 -0
- package/src/cli/e2e/slash-new.test.ts +93 -0
- package/src/cli/e2e/subagents.test.ts +106 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/lite.ts +51 -11
- package/src/cli/propose-policy.ts +91 -0
- package/src/cli/subagent-commands.ts +215 -0
- package/src/daemon/agent/agent-context.ts +89 -0
- package/src/daemon/agent/agent-extractors.ts +68 -0
- package/src/daemon/agent/agent-runner.ts +153 -0
- package/src/daemon/agent/agent-session.ts +261 -0
- package/src/daemon/agent/chat-logger.test.ts +158 -0
- package/src/daemon/agent/chat-logger.ts +188 -0
- package/src/daemon/agent/task-scheduler.test.ts +202 -0
- package/src/daemon/agent/task-scheduler.ts +276 -0
- package/src/daemon/agent/types.ts +84 -0
- package/src/daemon/agent/utils.ts +7 -0
- package/src/daemon/api/agent-router.ts +166 -18
- package/src/daemon/api/index.test.ts +50 -18
- package/src/daemon/api/policy-request.test.ts +39 -2
- package/src/daemon/api/subagent-router.test.ts +108 -0
- package/src/daemon/api/subagent-router.ts +296 -0
- package/src/daemon/api/subagent-utils.test.ts +56 -0
- package/src/daemon/api/subagent-utils.ts +130 -0
- package/src/daemon/api/user-router.ts +30 -13
- package/src/daemon/auth.ts +1 -0
- package/src/daemon/chats.ts +6 -0
- package/src/daemon/cron.test.ts +66 -1
- package/src/daemon/cron.ts +35 -8
- package/src/daemon/index.ts +23 -0
- package/src/daemon/message-agent.test.ts +11 -25
- package/src/daemon/message-extraction.test.ts +10 -27
- package/src/daemon/message-fallbacks.test.ts +13 -35
- package/src/daemon/message-interruption.test.ts +70 -53
- package/src/daemon/message-jobs.test.ts +138 -0
- package/src/daemon/message-queue.test.ts +30 -43
- package/src/daemon/message-router.test.ts +12 -11
- package/src/daemon/message-session.test.ts +41 -28
- package/src/daemon/message-typing.test.ts +19 -6
- package/src/daemon/message.ts +103 -515
- package/src/daemon/policy-request-service.ts +8 -3
- package/src/daemon/policy-utils.ts +19 -1
- package/src/daemon/queue.ts +16 -0
- package/src/daemon/request-store.test.ts +4 -0
- package/src/daemon/routers/session-timeout.test.ts +122 -0
- package/src/daemon/routers/session-timeout.ts +71 -0
- package/src/daemon/routers/slash-new.ts +3 -1
- package/src/daemon/routers/slash-policies.test.ts +26 -13
- package/src/daemon/routers/slash-policies.ts +39 -29
- package/src/daemon/routers/types.ts +8 -0
- package/src/daemon/routers.ts +64 -2
- package/src/daemon/utils/spawn.ts +6 -8
- package/src/shared/adapters/commands.test.ts +155 -0
- package/src/shared/adapters/commands.ts +125 -0
- package/src/shared/adapters/filtering.test.ts +111 -0
- package/src/shared/adapters/filtering.ts +57 -0
- package/src/shared/adapters/routing.test.ts +144 -0
- package/src/shared/adapters/routing.ts +109 -0
- package/src/shared/agent-utils.ts +10 -0
- package/src/shared/chats.test.ts +145 -3
- package/src/shared/chats.ts +215 -18
- package/src/shared/config.ts +67 -15
- package/src/shared/lite.ts +22 -18
- package/src/shared/policies.ts +7 -0
- package/src/shared/workspace.test.ts +45 -1
- package/src/shared/workspace.ts +119 -6
- package/templates/debug/settings.json +5 -2
- package/templates/environments/cladding/env.json +2 -2
- package/templates/gemini/.gemini/hooks/check-subagents.mjs +23 -0
- package/templates/gemini/.gemini/hooks/clawmini-logging.sh +17 -0
- package/templates/gemini/.gemini/hooks/insert-pending.sh +9 -0
- package/templates/gemini/.gemini/settings.json +50 -0
- package/templates/gemini/settings.json +22 -8
- package/templates/gemini-claw/.gemini/base-system.md +100 -0
- package/templates/gemini-claw/.gemini/hooks/check-subagents.mjs +23 -0
- package/templates/gemini-claw/.gemini/hooks/clawmini-logging.sh +1 -1
- package/templates/gemini-claw/.gemini/settings.json +13 -0
- package/templates/gemini-claw/.gemini/subagent-system.md +7 -0
- package/templates/gemini-claw/.gemini/system.md +3 -99
- package/templates/gemini-claw/settings.json +27 -22
- package/templates/skills/clawmini-requests/SKILL.md +92 -0
- package/templates/skills/clawmini-subagents/SKILL.md +79 -0
- package/templates/skills/skill-creator/SKILL.md +60 -0
- package/tsdown.config.ts +10 -1
- package/web/.svelte-kit/generated/server/internal.js +2 -1
- package/web/.svelte-kit/non-ambient.d.ts +2 -0
- package/web/.svelte-kit/output/client/.vite/manifest.json +141 -138
- package/web/.svelte-kit/output/client/_app/immutable/assets/0.C-4eziNy.css +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/assets/4.Cc_xwLNl.css +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B6YN0Nuq.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{Dc-UOHw9.js → BmRlVmv6.js} +1 -1
- package/{dist/web/_app/immutable/chunks/8YNcRyEk.js → web/.svelte-kit/output/client/_app/immutable/chunks/C20lZMGz.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/C9lbZ-kT.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CK9JZLaG.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CME08kGM.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{BPy8HLo7.js → Ck-be5J2.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Ck3rYNON.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DMtIqaiV.js +2 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{B8yYFADm.js → DhD271EB.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{DcrmIfTj.js → DpuLqk8d.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{ZkLyk0mE.js → Drm9vgeP.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DsIToJCP.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{CyNaE55B.js → Zeh-C-mx.js} +1 -1
- package/{dist/web/_app/immutable/entry/app.DO5eYwVz.js → web/.svelte-kit/output/client/_app/immutable/entry/app.BgB5VkRU.js} +2 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.DuxJo6av.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/0.C9oFZP9h.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.BON2Wk6k.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.CK3CLC0f.js → 2.BnwnD1Ki.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.ncP0xLO6.js → 3.CIs4tjjw.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.DLarELN4.js +60 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.BpJUN6QH.js → 5.CE_QKy_3.js} +1 -1
- package/web/.svelte-kit/output/client/_app/version.json +1 -1
- package/web/.svelte-kit/output/server/.vite/manifest.json +12 -3
- package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.C-4eziNy.css +1 -0
- package/web/.svelte-kit/output/server/_app/immutable/assets/_page.Cc_xwLNl.css +1 -0
- package/web/.svelte-kit/output/server/chunks/app-state.svelte.js +5 -0
- package/web/.svelte-kit/output/server/chunks/bot.js +4 -4
- package/web/.svelte-kit/output/server/chunks/client.js +2 -1
- package/web/.svelte-kit/output/server/chunks/exports.js +0 -1
- package/web/.svelte-kit/output/server/chunks/internal.js +2 -1
- package/web/.svelte-kit/output/server/chunks/root.js +482 -392
- package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +57 -7
- package/web/.svelte-kit/output/server/entries/pages/chats/_id_/_page.svelte.js +234 -9
- package/web/.svelte-kit/output/server/index.js +82 -10
- package/web/.svelte-kit/output/server/manifest-full.js +1 -1
- package/web/.svelte-kit/output/server/manifest.js +1 -1
- package/web/.svelte-kit/output/server/nodes/0.js +2 -2
- package/web/.svelte-kit/output/server/nodes/1.js +1 -1
- package/web/.svelte-kit/output/server/nodes/2.js +1 -1
- package/web/.svelte-kit/output/server/nodes/3.js +1 -1
- package/web/.svelte-kit/output/server/nodes/4.js +2 -2
- package/web/.svelte-kit/output/server/nodes/5.js +1 -1
- package/web/.svelte-kit/types/src/routes/$types.d.ts +1 -2
- package/web/.svelte-kit/types/src/routes/agents/$types.d.ts +1 -2
- package/web/.svelte-kit/types/src/routes/chats/[id]/$types.d.ts +1 -2
- package/web/.svelte-kit/types/src/routes/chats/[id]/settings/$types.d.ts +1 -2
- package/web/package.json +8 -0
- package/web/src/lib/app-state.svelte.ts +5 -1
- package/web/src/lib/components/app/markdown-renderer.svelte +56 -0
- package/web/src/lib/components/app/markdown-renderer.svelte.spec.ts +44 -0
- package/web/src/lib/components/app/message-content.svelte +16 -0
- package/web/src/lib/types.ts +67 -3
- package/web/src/routes/+layout.svelte +31 -1
- package/web/src/routes/chats/[id]/+page.svelte +167 -18
- package/web/src/routes/chats/[id]/page.svelte.spec.ts +58 -7
- package/dist/lite-oSYSvaOr.mjs.map +0 -1
- package/dist/web/_app/immutable/assets/0.GI4C4dpV.css +0 -1
- package/dist/web/_app/immutable/chunks/B5abRDXp.js +0 -1
- package/dist/web/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
- package/dist/web/_app/immutable/chunks/BmUXQ3wy.js +0 -2
- package/dist/web/_app/immutable/chunks/C3k55nDF.js +0 -1
- package/dist/web/_app/immutable/chunks/CpaGRn9L.js +0 -1
- package/dist/web/_app/immutable/chunks/DG5RZBw-.js +0 -2
- package/dist/web/_app/immutable/chunks/DQoygso7.js +0 -1
- package/dist/web/_app/immutable/entry/start.D48mVn1m.js +0 -1
- package/dist/web/_app/immutable/nodes/0.B-0CcADM.js +0 -1
- package/dist/web/_app/immutable/nodes/1.FixKgvRO.js +0 -1
- package/dist/web/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
- package/dist/workspace-DjoNjhW0.mjs.map +0 -1
- package/src/daemon/message-verbosity.test.ts +0 -127
- package/web/.svelte-kit/output/client/_app/immutable/assets/0.GI4C4dpV.css +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/B5abRDXp.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/Bi0jeV7Q.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/BmUXQ3wy.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/C3k55nDF.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/CpaGRn9L.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DG5RZBw-.js +0 -2
- package/web/.svelte-kit/output/client/_app/immutable/chunks/DQoygso7.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/0.B-0CcADM.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/1.FixKgvRO.js +0 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/4.CQYJEgv8.js +0 -1
- package/web/.svelte-kit/output/server/_app/immutable/assets/_layout.GI4C4dpV.css +0 -1
- /package/templates/{gemini-claw/.gemini/skills → skills}/clawmini-jobs/SKILL.md +0 -0
|
@@ -12,6 +12,13 @@ const { mockClientInstance } = vi.hoisted(() => ({
|
|
|
12
12
|
},
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
+
vi.mock('./state.js', () => ({
|
|
16
|
+
readDiscordState: vi
|
|
17
|
+
.fn()
|
|
18
|
+
.mockResolvedValue({ channelChatMap: { 'channel-123': { chatId: 'default' } } }),
|
|
19
|
+
updateDiscordState: vi.fn().mockResolvedValue(undefined),
|
|
20
|
+
}));
|
|
21
|
+
|
|
15
22
|
vi.mock('discord.js', () => {
|
|
16
23
|
return {
|
|
17
24
|
Client: class {
|
|
@@ -22,6 +29,24 @@ vi.mock('discord.js', () => {
|
|
|
22
29
|
Events: {
|
|
23
30
|
ClientReady: 'ready',
|
|
24
31
|
MessageCreate: 'messageCreate',
|
|
32
|
+
InteractionCreate: 'interactionCreate',
|
|
33
|
+
},
|
|
34
|
+
ActionRowBuilder: class {
|
|
35
|
+
addComponents = vi.fn().mockReturnThis();
|
|
36
|
+
},
|
|
37
|
+
ModalBuilder: class {
|
|
38
|
+
setCustomId = vi.fn().mockReturnThis();
|
|
39
|
+
setTitle = vi.fn().mockReturnThis();
|
|
40
|
+
addComponents = vi.fn().mockReturnThis();
|
|
41
|
+
},
|
|
42
|
+
TextInputBuilder: class {
|
|
43
|
+
setCustomId = vi.fn().mockReturnThis();
|
|
44
|
+
setLabel = vi.fn().mockReturnThis();
|
|
45
|
+
setStyle = vi.fn().mockReturnThis();
|
|
46
|
+
setRequired = vi.fn().mockReturnThis();
|
|
47
|
+
},
|
|
48
|
+
TextInputStyle: {
|
|
49
|
+
Paragraph: 2,
|
|
25
50
|
},
|
|
26
51
|
GatewayIntentBits: {
|
|
27
52
|
Guilds: 1,
|
|
@@ -38,6 +63,7 @@ vi.mock('./config.js', () => ({
|
|
|
38
63
|
readDiscordConfig: vi.fn(),
|
|
39
64
|
initDiscordConfig: vi.fn(),
|
|
40
65
|
isAuthorized: vi.fn(),
|
|
66
|
+
getDiscordConfigPath: vi.fn(),
|
|
41
67
|
}));
|
|
42
68
|
|
|
43
69
|
vi.mock('./client.js', () => ({
|
|
@@ -59,6 +85,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
59
85
|
botToken: 'test-token',
|
|
60
86
|
authorizedUserId: 'user-123',
|
|
61
87
|
chatId: 'default',
|
|
88
|
+
maxAttachmentSizeMB: 25,
|
|
89
|
+
requireMention: false,
|
|
62
90
|
});
|
|
63
91
|
|
|
64
92
|
// Reset the mock implementation to return the instance
|
|
@@ -101,6 +129,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
101
129
|
author: { id: 'user-123', tag: 'user#1234' },
|
|
102
130
|
content: 'Hello daemon!',
|
|
103
131
|
guild: null,
|
|
132
|
+
channelId: 'channel-123',
|
|
133
|
+
reply: vi.fn(),
|
|
104
134
|
attachments: new Map(),
|
|
105
135
|
};
|
|
106
136
|
|
|
@@ -153,6 +183,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
153
183
|
author: { id: 'user-123', tag: 'user#1234' } as unknown as import('discord.js').User,
|
|
154
184
|
content: 'message 1',
|
|
155
185
|
guild: null,
|
|
186
|
+
channelId: 'channel-123',
|
|
187
|
+
reply: vi.fn(),
|
|
156
188
|
attachments: new Map(),
|
|
157
189
|
} as unknown as import('discord.js').Message);
|
|
158
190
|
await messageHandler({
|
|
@@ -160,6 +192,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
160
192
|
author: { id: 'user-123', tag: 'user#1234' } as unknown as import('discord.js').User,
|
|
161
193
|
content: 'message 2',
|
|
162
194
|
guild: null,
|
|
195
|
+
channelId: 'channel-123',
|
|
196
|
+
reply: vi.fn(),
|
|
163
197
|
attachments: new Map(),
|
|
164
198
|
} as unknown as import('discord.js').Message);
|
|
165
199
|
}
|
|
@@ -209,6 +243,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
209
243
|
author: { id: 'user-evil', tag: 'evil#666' },
|
|
210
244
|
content: 'Hack the daemon!',
|
|
211
245
|
guild: null,
|
|
246
|
+
channelId: 'channel-123',
|
|
247
|
+
reply: vi.fn(),
|
|
212
248
|
};
|
|
213
249
|
|
|
214
250
|
const { isAuthorized } = await import('./config.js');
|
|
@@ -221,6 +257,123 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
221
257
|
expect(mockTrpc.sendMessage.mutate).not.toHaveBeenCalled();
|
|
222
258
|
});
|
|
223
259
|
|
|
260
|
+
it('should process non-DM (guild) messages if requireMention is false', async () => {
|
|
261
|
+
let messageHandler: ((message: import('discord.js').Message) => Promise<void>) | undefined;
|
|
262
|
+
vi.mocked(mockClientInstance.on).mockImplementation(
|
|
263
|
+
(event: string, cb: (...args: unknown[]) => void) => {
|
|
264
|
+
if (event === 'messageCreate') {
|
|
265
|
+
messageHandler = cb as unknown as (
|
|
266
|
+
message: import('discord.js').Message
|
|
267
|
+
) => Promise<void>;
|
|
268
|
+
}
|
|
269
|
+
return mockClientInstance as unknown as import('discord.js').Client;
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const { main } = await import('./index.js');
|
|
274
|
+
await main();
|
|
275
|
+
|
|
276
|
+
const mockMessage = {
|
|
277
|
+
author: { id: 'user-123', tag: 'user#1234' },
|
|
278
|
+
content: 'Hack the daemon!',
|
|
279
|
+
guild: { id: 'guild-123' },
|
|
280
|
+
channelId: 'channel-123',
|
|
281
|
+
reply: vi.fn(),
|
|
282
|
+
attachments: new Map(),
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const { isAuthorized } = await import('./config.js');
|
|
286
|
+
vi.mocked(isAuthorized).mockReturnValue(true);
|
|
287
|
+
|
|
288
|
+
if (messageHandler) {
|
|
289
|
+
await messageHandler(mockMessage as unknown as import('discord.js').Message);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalled();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should NOT process non-DM (guild) messages if channel requireMention is true and not mentioned', async () => {
|
|
296
|
+
let messageHandler: ((message: import('discord.js').Message) => Promise<void>) | undefined;
|
|
297
|
+
vi.mocked(mockClientInstance.on).mockImplementation(
|
|
298
|
+
(event: string, cb: (...args: unknown[]) => void) => {
|
|
299
|
+
if (event === 'messageCreate') {
|
|
300
|
+
messageHandler = cb as unknown as (
|
|
301
|
+
message: import('discord.js').Message
|
|
302
|
+
) => Promise<void>;
|
|
303
|
+
}
|
|
304
|
+
return mockClientInstance as unknown as import('discord.js').Client;
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const { main } = await import('./index.js');
|
|
309
|
+
await main();
|
|
310
|
+
|
|
311
|
+
const mockMessage = {
|
|
312
|
+
author: { id: 'user-123', tag: 'user#1234' },
|
|
313
|
+
content: 'Hack the daemon!',
|
|
314
|
+
guild: { id: 'guild-123' },
|
|
315
|
+
channelId: 'channel-123',
|
|
316
|
+
reply: vi.fn(),
|
|
317
|
+
mentions: { has: vi.fn().mockReturnValue(false) },
|
|
318
|
+
attachments: new Map(),
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const { isAuthorized } = await import('./config.js');
|
|
322
|
+
vi.mocked(isAuthorized).mockReturnValue(true);
|
|
323
|
+
|
|
324
|
+
const { readDiscordState } = await import('./state.js');
|
|
325
|
+
vi.mocked(readDiscordState).mockResolvedValue({
|
|
326
|
+
channelChatMap: { 'channel-123': { chatId: 'default', requireMention: true } },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (messageHandler) {
|
|
330
|
+
await messageHandler(mockMessage as unknown as import('discord.js').Message);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
expect(mockTrpc.sendMessage.mutate).not.toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should process non-DM (guild) messages if channel requireMention is true and IS mentioned', async () => {
|
|
337
|
+
let messageHandler: ((message: import('discord.js').Message) => Promise<void>) | undefined;
|
|
338
|
+
vi.mocked(mockClientInstance.on).mockImplementation(
|
|
339
|
+
(event: string, cb: (...args: unknown[]) => void) => {
|
|
340
|
+
if (event === 'messageCreate') {
|
|
341
|
+
messageHandler = cb as unknown as (
|
|
342
|
+
message: import('discord.js').Message
|
|
343
|
+
) => Promise<void>;
|
|
344
|
+
}
|
|
345
|
+
return mockClientInstance as unknown as import('discord.js').Client;
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const { main } = await import('./index.js');
|
|
350
|
+
await main();
|
|
351
|
+
|
|
352
|
+
const mockMessage = {
|
|
353
|
+
author: { id: 'user-123', tag: 'user#1234' },
|
|
354
|
+
content: 'Hack the daemon!',
|
|
355
|
+
guild: { id: 'guild-123' },
|
|
356
|
+
channelId: 'channel-123',
|
|
357
|
+
reply: vi.fn(),
|
|
358
|
+
mentions: { has: vi.fn().mockReturnValue(true) },
|
|
359
|
+
attachments: new Map(),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const { isAuthorized } = await import('./config.js');
|
|
363
|
+
vi.mocked(isAuthorized).mockReturnValue(true);
|
|
364
|
+
|
|
365
|
+
const { readDiscordState } = await import('./state.js');
|
|
366
|
+
vi.mocked(readDiscordState).mockResolvedValue({
|
|
367
|
+
channelChatMap: { 'channel-123': { chatId: 'default', requireMention: true } },
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (messageHandler) {
|
|
371
|
+
await messageHandler(mockMessage as unknown as import('discord.js').Message);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalled();
|
|
375
|
+
});
|
|
376
|
+
|
|
224
377
|
it('should download attachments and forward their paths', async () => {
|
|
225
378
|
vi.useFakeTimers();
|
|
226
379
|
let messageHandler: ((message: import('discord.js').Message) => Promise<void>) | undefined;
|
|
@@ -259,6 +412,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
259
412
|
author: { id: 'user-123', tag: 'user#1234' } as unknown as import('discord.js').User,
|
|
260
413
|
content: 'Check out this file',
|
|
261
414
|
guild: null,
|
|
415
|
+
channelId: 'channel-123',
|
|
416
|
+
reply: vi.fn(),
|
|
262
417
|
attachments,
|
|
263
418
|
} as unknown as import('discord.js').Message);
|
|
264
419
|
}
|
|
@@ -319,6 +474,7 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
319
474
|
author: { id: 'user-123', tag: 'user#1234' } as unknown as import('discord.js').User,
|
|
320
475
|
content: 'Check out this huge file',
|
|
321
476
|
guild: null,
|
|
477
|
+
channelId: 'channel-123',
|
|
322
478
|
attachments,
|
|
323
479
|
reply: replyMock,
|
|
324
480
|
} as unknown as import('discord.js').Message);
|
|
@@ -375,6 +531,8 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
375
531
|
content: "Yes, I'm in!",
|
|
376
532
|
guild: null,
|
|
377
533
|
attachments: new Map(),
|
|
534
|
+
channelId: 'channel-123',
|
|
535
|
+
reply: vi.fn(),
|
|
378
536
|
reference: { messageId: '12345' },
|
|
379
537
|
fetchReference: vi.fn().mockResolvedValue(mockReferencedMessage),
|
|
380
538
|
} as unknown as import('discord.js').Message);
|
|
@@ -396,4 +554,124 @@ describe('Discord Adapter Entry Point', () => {
|
|
|
396
554
|
});
|
|
397
555
|
vi.useRealTimers();
|
|
398
556
|
});
|
|
557
|
+
|
|
558
|
+
describe('Interaction Handling', () => {
|
|
559
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
560
|
+
let interactionHandler: ((interaction: any) => Promise<void>) | undefined;
|
|
561
|
+
|
|
562
|
+
beforeEach(async () => {
|
|
563
|
+
vi.mocked(mockClientInstance.on).mockImplementation(
|
|
564
|
+
(event: string, cb: (...args: unknown[]) => void) => {
|
|
565
|
+
if (event === 'interactionCreate') {
|
|
566
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
567
|
+
interactionHandler = cb as any;
|
|
568
|
+
}
|
|
569
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
570
|
+
return mockClientInstance as any;
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
const { main } = await import('./index.js');
|
|
574
|
+
await main();
|
|
575
|
+
const { isAuthorized } = await import('./config.js');
|
|
576
|
+
vi.mocked(isAuthorized).mockReturnValue(true);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should ignore non-button and non-modal interactions', async () => {
|
|
580
|
+
const mockInteraction = {
|
|
581
|
+
isButton: () => false,
|
|
582
|
+
isModalSubmit: () => false,
|
|
583
|
+
};
|
|
584
|
+
if (interactionHandler) await interactionHandler(mockInteraction);
|
|
585
|
+
expect(mockTrpc.sendMessage.mutate).not.toHaveBeenCalled();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('should ignore unauthorized interactions', async () => {
|
|
589
|
+
const { isAuthorized } = await import('./config.js');
|
|
590
|
+
vi.mocked(isAuthorized).mockReturnValue(false);
|
|
591
|
+
const mockInteraction = {
|
|
592
|
+
isButton: () => true,
|
|
593
|
+
isModalSubmit: () => false,
|
|
594
|
+
isRepliable: () => true,
|
|
595
|
+
user: { id: 'unauth' },
|
|
596
|
+
reply: vi.fn(),
|
|
597
|
+
};
|
|
598
|
+
if (interactionHandler) await interactionHandler(mockInteraction);
|
|
599
|
+
expect(mockInteraction.reply).toHaveBeenCalledWith({
|
|
600
|
+
content: 'You are not authorized to perform this action.',
|
|
601
|
+
ephemeral: true,
|
|
602
|
+
});
|
|
603
|
+
expect(mockTrpc.sendMessage.mutate).not.toHaveBeenCalled();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('should handle approve button interaction', async () => {
|
|
607
|
+
const mockInteraction = {
|
|
608
|
+
isButton: () => true,
|
|
609
|
+
isModalSubmit: () => false,
|
|
610
|
+
user: { id: 'user-123' },
|
|
611
|
+
customId: 'approve_123',
|
|
612
|
+
update: vi.fn(),
|
|
613
|
+
followUp: vi.fn(),
|
|
614
|
+
};
|
|
615
|
+
if (interactionHandler) await interactionHandler(mockInteraction);
|
|
616
|
+
expect(mockInteraction.update).toHaveBeenCalledWith({ components: [] });
|
|
617
|
+
expect(mockInteraction.followUp).toHaveBeenCalledWith({
|
|
618
|
+
content: 'Approving policy 123...',
|
|
619
|
+
ephemeral: true,
|
|
620
|
+
});
|
|
621
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith({
|
|
622
|
+
type: 'send-message',
|
|
623
|
+
client: 'cli',
|
|
624
|
+
data: {
|
|
625
|
+
message: '/approve 123',
|
|
626
|
+
chatId: 'default',
|
|
627
|
+
adapter: 'discord',
|
|
628
|
+
noWait: true,
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should handle reject button interaction by showing a modal', async () => {
|
|
634
|
+
const mockInteraction = {
|
|
635
|
+
isButton: () => true,
|
|
636
|
+
isModalSubmit: () => false,
|
|
637
|
+
user: { id: 'user-123' },
|
|
638
|
+
customId: 'reject_123',
|
|
639
|
+
showModal: vi.fn(),
|
|
640
|
+
};
|
|
641
|
+
if (interactionHandler) await interactionHandler(mockInteraction);
|
|
642
|
+
expect(mockInteraction.showModal).toHaveBeenCalled();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('should handle reject modal submit interaction', async () => {
|
|
646
|
+
const mockInteraction = {
|
|
647
|
+
isButton: () => false,
|
|
648
|
+
isModalSubmit: () => true,
|
|
649
|
+
isFromMessage: () => true,
|
|
650
|
+
user: { id: 'user-123' },
|
|
651
|
+
customId: 'modal_reject_123',
|
|
652
|
+
fields: {
|
|
653
|
+
getTextInputValue: vi.fn().mockReturnValue('bad policy'),
|
|
654
|
+
},
|
|
655
|
+
reply: vi.fn(),
|
|
656
|
+
update: vi.fn(),
|
|
657
|
+
followUp: vi.fn(),
|
|
658
|
+
};
|
|
659
|
+
if (interactionHandler) await interactionHandler(mockInteraction);
|
|
660
|
+
expect(mockInteraction.update).toHaveBeenCalledWith({ components: [] });
|
|
661
|
+
expect(mockInteraction.followUp).toHaveBeenCalledWith({
|
|
662
|
+
content: 'Rejecting policy 123...',
|
|
663
|
+
ephemeral: true,
|
|
664
|
+
});
|
|
665
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith({
|
|
666
|
+
type: 'send-message',
|
|
667
|
+
client: 'cli',
|
|
668
|
+
data: {
|
|
669
|
+
message: '/reject 123 bad policy',
|
|
670
|
+
chatId: 'default',
|
|
671
|
+
adapter: 'discord',
|
|
672
|
+
noWait: true,
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
});
|
|
399
677
|
});
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { Client, Events, GatewayIntentBits, Partials } from 'discord.js';
|
|
4
4
|
import { readDiscordConfig, isAuthorized, initDiscordConfig } from './config.js';
|
|
5
|
+
import { readDiscordState, updateDiscordState } from './state.js';
|
|
6
|
+
import { handleDiscordInteraction } from './interactions.js';
|
|
5
7
|
import { getTRPCClient } from './client.js';
|
|
6
8
|
import { startDaemonToDiscordForwarder } from './forwarder.js';
|
|
7
9
|
import { getClawminiDir } from '../shared/workspace.js';
|
|
10
|
+
import { handleAdapterCommand, type CommandTrpcClient } from '../shared/adapters/commands.js';
|
|
11
|
+
import { formatMessage, type FilteringConfig } from '../shared/adapters/filtering.js';
|
|
8
12
|
import fs from 'node:fs/promises';
|
|
9
13
|
import path from 'node:path';
|
|
14
|
+
import { handleRoutingCommand, type RoutingTrpcClient } from '../shared/adapters/routing.js';
|
|
10
15
|
|
|
11
16
|
export async function main() {
|
|
12
17
|
const args = process.argv.slice(2);
|
|
@@ -29,27 +34,69 @@ export async function main() {
|
|
|
29
34
|
const trpc = getTRPCClient();
|
|
30
35
|
|
|
31
36
|
const client = new Client({
|
|
32
|
-
intents: [
|
|
37
|
+
intents: [
|
|
38
|
+
GatewayIntentBits.DirectMessages,
|
|
39
|
+
GatewayIntentBits.MessageContent,
|
|
40
|
+
GatewayIntentBits.Guilds,
|
|
41
|
+
GatewayIntentBits.GuildMessages,
|
|
42
|
+
],
|
|
33
43
|
partials: [Partials.Channel],
|
|
34
44
|
});
|
|
35
45
|
|
|
46
|
+
const state = await readDiscordState();
|
|
47
|
+
const filteringConfig: FilteringConfig = { filters: state.filters };
|
|
48
|
+
|
|
36
49
|
client.once(Events.ClientReady, (readyClient) => {
|
|
37
50
|
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
|
38
51
|
|
|
39
52
|
// Start forwarding from daemon to Discord
|
|
40
|
-
startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
startDaemonToDiscordForwarder(readyClient, trpc, config.authorizedUserId, {
|
|
54
|
+
chatId: config.chatId,
|
|
55
|
+
config: filteringConfig,
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
console.error('Error in daemon-to-discord forwarder:', error);
|
|
58
|
+
});
|
|
45
59
|
});
|
|
46
60
|
|
|
47
61
|
client.on(Events.MessageCreate, async (message) => {
|
|
48
62
|
// Ignore messages from the bot itself
|
|
49
63
|
if (message.author.id === client.user?.id) return;
|
|
64
|
+
if (message.author.bot) return;
|
|
65
|
+
|
|
66
|
+
const externalContextId = message.channelId;
|
|
67
|
+
const currentState = await readDiscordState();
|
|
68
|
+
const mappedChatId = currentState.channelChatMap?.[externalContextId]?.chatId;
|
|
69
|
+
const isRoutingCommand =
|
|
70
|
+
message.content.startsWith('/chat') || message.content.startsWith('/agent');
|
|
71
|
+
|
|
72
|
+
// Enforce requireMention config for guild messages
|
|
73
|
+
if (message.guild) {
|
|
74
|
+
const channelConfig = currentState.channelChatMap?.[externalContextId];
|
|
75
|
+
const requiresMention =
|
|
76
|
+
channelConfig?.requireMention !== undefined
|
|
77
|
+
? channelConfig.requireMention
|
|
78
|
+
: config.requireMention;
|
|
79
|
+
|
|
80
|
+
if (requiresMention) {
|
|
81
|
+
const isMentioned = message.mentions.has(client.user!.id);
|
|
82
|
+
let isReplyToBot = false;
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
if (message.reference && message.reference.messageId) {
|
|
85
|
+
try {
|
|
86
|
+
const referencedMessage = await message.channel.messages.fetch(
|
|
87
|
+
message.reference.messageId
|
|
88
|
+
);
|
|
89
|
+
isReplyToBot = referencedMessage.author.id === client.user!.id;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('Failed to fetch referenced message for mention check:', err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!isMentioned && !isReplyToBot) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
53
100
|
|
|
54
101
|
// Check if the user is authorized
|
|
55
102
|
if (!isAuthorized(message.author.id, config.authorizedUserId)) {
|
|
@@ -61,6 +108,102 @@ export async function main() {
|
|
|
61
108
|
|
|
62
109
|
console.log(`Received message from ${message.author.tag}: ${message.content}`);
|
|
63
110
|
|
|
111
|
+
if (isRoutingCommand) {
|
|
112
|
+
const stringChatMap = Object.fromEntries(
|
|
113
|
+
Object.entries(currentState.channelChatMap || {}).map(([k, v]) => [k, v.chatId || ''])
|
|
114
|
+
);
|
|
115
|
+
const routingResult = await handleRoutingCommand(
|
|
116
|
+
message.content,
|
|
117
|
+
externalContextId,
|
|
118
|
+
stringChatMap,
|
|
119
|
+
'discord',
|
|
120
|
+
trpc as unknown as RoutingTrpcClient
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (routingResult) {
|
|
124
|
+
if (routingResult.type === 'mapped') {
|
|
125
|
+
await updateDiscordState((latestState) => ({
|
|
126
|
+
channelChatMap: {
|
|
127
|
+
...(latestState.channelChatMap || {}),
|
|
128
|
+
[externalContextId]: {
|
|
129
|
+
...(latestState.channelChatMap?.[externalContextId] || {}),
|
|
130
|
+
chatId: routingResult.newChatId,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
await message.reply(routingResult.text);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let targetChatId = mappedChatId;
|
|
141
|
+
|
|
142
|
+
if (!targetChatId && !isRoutingCommand) {
|
|
143
|
+
const isFirstEverMessage =
|
|
144
|
+
!currentState.channelChatMap ||
|
|
145
|
+
Object.values(currentState.channelChatMap).every((entry) => !entry.chatId);
|
|
146
|
+
|
|
147
|
+
if (isFirstEverMessage) {
|
|
148
|
+
targetChatId = config.chatId || 'default';
|
|
149
|
+
console.log(
|
|
150
|
+
`First contact detected. Automatically mapping channel ${externalContextId} to chat ${targetChatId}.`
|
|
151
|
+
);
|
|
152
|
+
await updateDiscordState((latestState) => ({
|
|
153
|
+
channelChatMap: {
|
|
154
|
+
...(latestState.channelChatMap || {}),
|
|
155
|
+
[externalContextId]: {
|
|
156
|
+
...(latestState.channelChatMap?.[externalContextId] || {}),
|
|
157
|
+
chatId: targetChatId as string,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
} else {
|
|
162
|
+
const isDirectMessage = !message.guild;
|
|
163
|
+
const isMentioned = message.mentions.has(client.user!.id);
|
|
164
|
+
const isSlashCommand = message.content.startsWith('/');
|
|
165
|
+
if (isDirectMessage || isMentioned || isSlashCommand) {
|
|
166
|
+
console.log(`Unmapped channel ${externalContextId}, sending first contact warning.`);
|
|
167
|
+
await message.reply(
|
|
168
|
+
'This channel/space is not currently mapped to a daemon chat. Please use `/chat [chat-id]` or `/agent [agent-id]` to map it.'
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(
|
|
172
|
+
`Unmapped channel ${externalContextId}, silently ignoring background message.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Fallback typing safeguard
|
|
180
|
+
if (!targetChatId) targetChatId = config.chatId || 'default';
|
|
181
|
+
|
|
182
|
+
const commandResult = await handleAdapterCommand(
|
|
183
|
+
message.content,
|
|
184
|
+
filteringConfig,
|
|
185
|
+
trpc as unknown as CommandTrpcClient,
|
|
186
|
+
targetChatId
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (commandResult) {
|
|
190
|
+
if (commandResult.type === 'text') {
|
|
191
|
+
if (commandResult.newConfig) {
|
|
192
|
+
filteringConfig.filters = commandResult.newConfig.filters;
|
|
193
|
+
await updateDiscordState({ filters: filteringConfig.filters });
|
|
194
|
+
}
|
|
195
|
+
await message.reply(commandResult.text);
|
|
196
|
+
} else if (commandResult.type === 'debug') {
|
|
197
|
+
const formatted =
|
|
198
|
+
commandResult.messages.length === 0
|
|
199
|
+
? 'No ignored background messages found.'
|
|
200
|
+
: `**Debug Output (${commandResult.messages.length} ignored messages):**\n\n` +
|
|
201
|
+
commandResult.messages.map((msg) => formatMessage(msg)).join('\n\n---\n\n');
|
|
202
|
+
await message.reply(formatted);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
64
207
|
const downloadedFiles: string[] = [];
|
|
65
208
|
if (message.attachments.size > 0) {
|
|
66
209
|
const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'discord');
|
|
@@ -121,7 +264,7 @@ export async function main() {
|
|
|
121
264
|
client: 'cli',
|
|
122
265
|
data: {
|
|
123
266
|
message: finalContent,
|
|
124
|
-
chatId:
|
|
267
|
+
chatId: targetChatId,
|
|
125
268
|
files: downloadedFiles.length > 0 ? downloadedFiles : undefined,
|
|
126
269
|
adapter: 'discord',
|
|
127
270
|
noWait: true,
|
|
@@ -133,6 +276,10 @@ export async function main() {
|
|
|
133
276
|
}
|
|
134
277
|
});
|
|
135
278
|
|
|
279
|
+
client.on(Events.InteractionCreate, async (interaction) => {
|
|
280
|
+
await handleDiscordInteraction(interaction, config, trpc as unknown as CommandTrpcClient);
|
|
281
|
+
});
|
|
282
|
+
|
|
136
283
|
try {
|
|
137
284
|
await client.login(config.botToken);
|
|
138
285
|
} catch (error) {
|
|
@@ -141,24 +288,7 @@ export async function main() {
|
|
|
141
288
|
}
|
|
142
289
|
}
|
|
143
290
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (typeof process === 'undefined' || !process.argv || process.argv.length < 2) return false;
|
|
149
|
-
const argv1 = process.argv[1];
|
|
150
|
-
if (!argv1) return false;
|
|
151
|
-
const p1 = path.resolve(argv1);
|
|
152
|
-
const p2 = path.resolve(fileURLToPath(import.meta.url));
|
|
153
|
-
return p1 === p2;
|
|
154
|
-
} catch {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
})();
|
|
158
|
-
|
|
159
|
-
if (isMainModule) {
|
|
160
|
-
main().catch((error) => {
|
|
161
|
-
console.error('Unhandled error in Discord Adapter:', error);
|
|
162
|
-
process.exit(1);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
291
|
+
main().catch((error) => {
|
|
292
|
+
console.error('Unhandled error in Discord Adapter:', error);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { handleDiscordInteraction } from './interactions.js';
|
|
3
|
+
import { readDiscordState } from './state.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('./state.js', () => ({
|
|
6
|
+
readDiscordState: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('handleDiscordInteraction', () => {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
let mockTrpc: any;
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
let mockInteraction: any;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
mockTrpc = {
|
|
18
|
+
sendMessage: {
|
|
19
|
+
mutate: vi.fn().mockResolvedValue({}),
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
mockInteraction = {
|
|
23
|
+
isButton: vi.fn().mockReturnValue(true),
|
|
24
|
+
isModalSubmit: vi.fn().mockReturnValue(false),
|
|
25
|
+
user: { id: 'user-1' },
|
|
26
|
+
customId: '',
|
|
27
|
+
channelId: 'channel-1',
|
|
28
|
+
update: vi.fn().mockResolvedValue({}),
|
|
29
|
+
followUp: vi.fn().mockResolvedValue({}),
|
|
30
|
+
showModal: vi.fn().mockResolvedValue({}),
|
|
31
|
+
};
|
|
32
|
+
vi.mocked(readDiscordState).mockResolvedValue({});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const config = {
|
|
36
|
+
authorizedUserId: 'user-1',
|
|
37
|
+
botToken: 'token',
|
|
38
|
+
clientId: 'client',
|
|
39
|
+
chatId: 'default',
|
|
40
|
+
maxAttachmentSizeMB: 10,
|
|
41
|
+
requireMention: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
it('routes approve to explicit chat if provided', async () => {
|
|
45
|
+
mockInteraction.customId = 'approve|policy-1|explicit-chat';
|
|
46
|
+
await handleDiscordInteraction(mockInteraction, config, mockTrpc);
|
|
47
|
+
|
|
48
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
|
|
49
|
+
expect.objectContaining({
|
|
50
|
+
data: expect.objectContaining({
|
|
51
|
+
chatId: 'explicit-chat',
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('routes approve to channel mapped chat if explicit not provided', async () => {
|
|
58
|
+
mockInteraction.customId = 'approve_policy-1';
|
|
59
|
+
vi.mocked(readDiscordState).mockResolvedValue({
|
|
60
|
+
channelChatMap: { 'channel-1': { chatId: 'mapped-chat' } },
|
|
61
|
+
});
|
|
62
|
+
await handleDiscordInteraction(mockInteraction, config, mockTrpc);
|
|
63
|
+
|
|
64
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
|
|
65
|
+
expect.objectContaining({
|
|
66
|
+
data: expect.objectContaining({
|
|
67
|
+
chatId: 'mapped-chat',
|
|
68
|
+
}),
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('routes modal_reject to mapped chat if explicit chat ID is empty string', async () => {
|
|
74
|
+
mockInteraction.isButton.mockReturnValue(false);
|
|
75
|
+
mockInteraction.isModalSubmit.mockReturnValue(true);
|
|
76
|
+
mockInteraction.isFromMessage = vi.fn().mockReturnValue(true);
|
|
77
|
+
mockInteraction.fields = {
|
|
78
|
+
getTextInputValue: vi.fn().mockReturnValue('nope'),
|
|
79
|
+
};
|
|
80
|
+
mockInteraction.customId = 'modal_reject|policy-1|';
|
|
81
|
+
|
|
82
|
+
vi.mocked(readDiscordState).mockResolvedValue({
|
|
83
|
+
channelChatMap: { 'channel-1': { chatId: 'mapped-chat' } },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await handleDiscordInteraction(mockInteraction, config, mockTrpc);
|
|
87
|
+
|
|
88
|
+
expect(mockTrpc.sendMessage.mutate).toHaveBeenCalledWith(
|
|
89
|
+
expect.objectContaining({
|
|
90
|
+
data: expect.objectContaining({
|
|
91
|
+
chatId: 'mapped-chat',
|
|
92
|
+
}),
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|