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