@poolzin/pool-bot 2026.2.21 → 2026.2.23
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/CHANGELOG.md +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/device-pair/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/irc/src/accounts.ts +1 -1
- package/extensions/irc/src/onboarding.ts +4 -4
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +10 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +10 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +10 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +10 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +10 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +10 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +10 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
- package/dist/agents/openclaw-tools.js +0 -151
- package/dist/agents/tool-security.js +0 -96
- package/dist/gateway/url-validation.js +0 -94
- package/dist/infra/openclaw-root.js +0 -109
- package/dist/infra/tmp-openclaw-dir.js +0 -81
- package/dist/media/path-sanitization.js +0 -78
|
@@ -1,15 +1,221 @@
|
|
|
1
|
-
import { Button, StringSelectMenu, } from "@buape/carbon";
|
|
1
|
+
import { Button, ChannelSelectMenu, MentionableSelectMenu, Modal, RoleSelectMenu, StringSelectMenu, UserSelectMenu, } from "@buape/carbon";
|
|
2
2
|
import { ButtonStyle, ChannelType } from "discord-api-types/v10";
|
|
3
|
+
import { resolveHumanDelayConfig } from "../../agents/identity.js";
|
|
4
|
+
import { resolveChunkMode, resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
|
5
|
+
import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../../auto-reply/envelope.js";
|
|
6
|
+
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
|
|
7
|
+
import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
|
8
|
+
import { createReplyReferencePlanner } from "../../auto-reply/reply/reply-reference.js";
|
|
9
|
+
import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
|
|
10
|
+
import { recordInboundSession } from "../../channels/session.js";
|
|
11
|
+
import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
|
|
12
|
+
import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
|
|
3
13
|
import { logVerbose } from "../../globals.js";
|
|
4
14
|
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
|
5
15
|
import { logDebug, logError } from "../../logger.js";
|
|
6
16
|
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
|
7
17
|
import { readChannelAllowFromStore, upsertChannelPairingRequest, } from "../../pairing/pairing-store.js";
|
|
8
18
|
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
|
9
|
-
import {
|
|
19
|
+
import { createNonExitingRuntime } from "../../runtime.js";
|
|
20
|
+
import { resolveDiscordComponentEntry, resolveDiscordModalEntry } from "../components-registry.js";
|
|
21
|
+
import { createDiscordFormModal, formatDiscordComponentEventText, parseDiscordComponentCustomId, parseDiscordComponentCustomIdForCarbon, parseDiscordModalCustomId, parseDiscordModalCustomIdForCarbon, } from "../components.js";
|
|
22
|
+
import { normalizeDiscordAllowList, normalizeDiscordSlug, resolveDiscordAllowListMatch, resolveDiscordChannelConfigWithFallback, resolveDiscordGuildEntry, resolveDiscordMemberAccessState, resolveDiscordOwnerAllowFrom, } from "./allow-list.js";
|
|
10
23
|
import { formatDiscordUserTag } from "./format.js";
|
|
24
|
+
import { buildDirectLabel, buildGuildLabel } from "./reply-context.js";
|
|
25
|
+
import { deliverDiscordReply } from "./reply-delivery.js";
|
|
26
|
+
import { sendTyping } from "./typing.js";
|
|
11
27
|
const AGENT_BUTTON_KEY = "agent";
|
|
12
28
|
const AGENT_SELECT_KEY = "agentsel";
|
|
29
|
+
function resolveAgentComponentRoute(params) {
|
|
30
|
+
return resolveAgentRoute({
|
|
31
|
+
cfg: params.ctx.cfg,
|
|
32
|
+
channel: "discord",
|
|
33
|
+
accountId: params.ctx.accountId,
|
|
34
|
+
guildId: params.rawGuildId,
|
|
35
|
+
memberRoleIds: params.memberRoleIds,
|
|
36
|
+
peer: {
|
|
37
|
+
kind: params.isDirectMessage ? "direct" : "channel",
|
|
38
|
+
id: params.isDirectMessage ? params.userId : params.channelId,
|
|
39
|
+
},
|
|
40
|
+
parentPeer: params.parentId ? { kind: "channel", id: params.parentId } : undefined,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function ackComponentInteraction(params) {
|
|
44
|
+
try {
|
|
45
|
+
await params.interaction.reply({
|
|
46
|
+
content: "✓",
|
|
47
|
+
...params.replyOpts,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
logError(`${params.label}: failed to acknowledge interaction: ${String(err)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function resolveDiscordChannelContext(interaction) {
|
|
55
|
+
const channel = interaction.channel;
|
|
56
|
+
const channelName = channel && "name" in channel ? channel.name : undefined;
|
|
57
|
+
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
|
58
|
+
const channelType = channel && "type" in channel ? channel.type : undefined;
|
|
59
|
+
const isThread = isThreadChannelType(channelType);
|
|
60
|
+
let parentId;
|
|
61
|
+
let parentName;
|
|
62
|
+
let parentSlug = "";
|
|
63
|
+
if (isThread && channel && "parentId" in channel) {
|
|
64
|
+
parentId = channel.parentId ?? undefined;
|
|
65
|
+
if ("parent" in channel) {
|
|
66
|
+
const parent = channel.parent;
|
|
67
|
+
if (parent?.name) {
|
|
68
|
+
parentName = parent.name;
|
|
69
|
+
parentSlug = normalizeDiscordSlug(parentName);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { channelName, channelSlug, channelType, isThread, parentId, parentName, parentSlug };
|
|
74
|
+
}
|
|
75
|
+
async function resolveComponentInteractionContext(params) {
|
|
76
|
+
const { interaction, label } = params;
|
|
77
|
+
// Use interaction's actual channel_id (trusted source from Discord)
|
|
78
|
+
// This prevents channel spoofing attacks
|
|
79
|
+
const channelId = interaction.rawData.channel_id;
|
|
80
|
+
if (!channelId) {
|
|
81
|
+
logError(`${label}: missing channel_id in interaction`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const user = interaction.user;
|
|
85
|
+
if (!user) {
|
|
86
|
+
logError(`${label}: missing user in interaction`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const shouldDefer = params.defer !== false && "defer" in interaction;
|
|
90
|
+
let didDefer = false;
|
|
91
|
+
// Defer immediately to satisfy Discord's 3-second interaction ACK requirement.
|
|
92
|
+
// We use an ephemeral deferred reply so subsequent interaction.reply() calls
|
|
93
|
+
// can safely edit the original deferred response.
|
|
94
|
+
if (shouldDefer) {
|
|
95
|
+
try {
|
|
96
|
+
await interaction.defer({ ephemeral: true });
|
|
97
|
+
didDefer = true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logError(`${label}: failed to defer interaction: ${String(err)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const replyOpts = didDefer ? {} : { ephemeral: true };
|
|
104
|
+
const username = formatUsername(user);
|
|
105
|
+
const userId = user.id;
|
|
106
|
+
// P1 FIX: Use rawData.guild_id as source of truth - interaction.guild can be null
|
|
107
|
+
// when guild is not cached even though guild_id is present in rawData
|
|
108
|
+
const rawGuildId = interaction.rawData.guild_id;
|
|
109
|
+
const isDirectMessage = !rawGuildId;
|
|
110
|
+
const memberRoleIds = Array.isArray(interaction.rawData.member?.roles)
|
|
111
|
+
? interaction.rawData.member.roles.map((roleId) => String(roleId))
|
|
112
|
+
: [];
|
|
113
|
+
return {
|
|
114
|
+
channelId,
|
|
115
|
+
user,
|
|
116
|
+
username,
|
|
117
|
+
userId,
|
|
118
|
+
replyOpts,
|
|
119
|
+
rawGuildId,
|
|
120
|
+
isDirectMessage,
|
|
121
|
+
memberRoleIds,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function ensureGuildComponentMemberAllowed(params) {
|
|
125
|
+
const { interaction, guildInfo, channelId, rawGuildId, channelCtx, memberRoleIds, user, replyOpts, componentLabel, unauthorizedReply, } = params;
|
|
126
|
+
if (!rawGuildId) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const channelConfig = resolveDiscordChannelConfigWithFallback({
|
|
130
|
+
guildInfo,
|
|
131
|
+
channelId,
|
|
132
|
+
channelName: channelCtx.channelName,
|
|
133
|
+
channelSlug: channelCtx.channelSlug,
|
|
134
|
+
parentId: channelCtx.parentId,
|
|
135
|
+
parentName: channelCtx.parentName,
|
|
136
|
+
parentSlug: channelCtx.parentSlug,
|
|
137
|
+
scope: channelCtx.isThread ? "thread" : "channel",
|
|
138
|
+
});
|
|
139
|
+
const { memberAllowed } = resolveDiscordMemberAccessState({
|
|
140
|
+
channelConfig,
|
|
141
|
+
guildInfo,
|
|
142
|
+
memberRoleIds,
|
|
143
|
+
sender: {
|
|
144
|
+
id: user.id,
|
|
145
|
+
name: user.username,
|
|
146
|
+
tag: user.discriminator ? `${user.username}#${user.discriminator}` : undefined,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
if (memberAllowed) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
logVerbose(`agent ${componentLabel}: blocked user ${user.id} (not in users/roles allowlist)`);
|
|
153
|
+
try {
|
|
154
|
+
await interaction.reply({
|
|
155
|
+
content: unauthorizedReply,
|
|
156
|
+
...replyOpts,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Interaction may have expired
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
async function ensureComponentUserAllowed(params) {
|
|
165
|
+
const allowList = normalizeDiscordAllowList(params.entry.allowedUsers, [
|
|
166
|
+
"discord:",
|
|
167
|
+
"user:",
|
|
168
|
+
"pk:",
|
|
169
|
+
]);
|
|
170
|
+
if (!allowList) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
const match = resolveDiscordAllowListMatch({
|
|
174
|
+
allowList,
|
|
175
|
+
candidate: {
|
|
176
|
+
id: params.user.id,
|
|
177
|
+
name: params.user.username,
|
|
178
|
+
tag: formatDiscordUserTag(params.user),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
if (match.allowed) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
logVerbose(`discord component ${params.componentLabel}: blocked user ${params.user.id} (not in allowedUsers)`);
|
|
185
|
+
try {
|
|
186
|
+
await params.interaction.reply({
|
|
187
|
+
content: params.unauthorizedReply,
|
|
188
|
+
...params.replyOpts,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Interaction may have expired
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
async function ensureAgentComponentInteractionAllowed(params) {
|
|
197
|
+
const guildInfo = resolveDiscordGuildEntry({
|
|
198
|
+
guild: params.interaction.guild ?? undefined,
|
|
199
|
+
guildEntries: params.ctx.guildEntries,
|
|
200
|
+
});
|
|
201
|
+
const channelCtx = resolveDiscordChannelContext(params.interaction);
|
|
202
|
+
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
|
203
|
+
interaction: params.interaction,
|
|
204
|
+
guildInfo,
|
|
205
|
+
channelId: params.channelId,
|
|
206
|
+
rawGuildId: params.rawGuildId,
|
|
207
|
+
channelCtx,
|
|
208
|
+
memberRoleIds: params.memberRoleIds,
|
|
209
|
+
user: params.user,
|
|
210
|
+
replyOpts: params.replyOpts,
|
|
211
|
+
componentLabel: params.componentLabel,
|
|
212
|
+
unauthorizedReply: params.unauthorizedReply,
|
|
213
|
+
});
|
|
214
|
+
if (!memberAllowed) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return { parentId: channelCtx.parentId };
|
|
218
|
+
}
|
|
13
219
|
/**
|
|
14
220
|
* Build agent button custom ID: agent:componentId=<id>
|
|
15
221
|
* The channelId is NOT embedded in customId - we use interaction.rawData.channel_id instead
|
|
@@ -59,14 +265,14 @@ function isThreadChannelType(channelType) {
|
|
|
59
265
|
channelType === ChannelType.AnnouncementThread);
|
|
60
266
|
}
|
|
61
267
|
async function ensureDmComponentAuthorized(params) {
|
|
62
|
-
const { ctx, interaction, user, componentLabel } = params;
|
|
268
|
+
const { ctx, interaction, user, componentLabel, replyOpts } = params;
|
|
63
269
|
const dmPolicy = ctx.dmPolicy ?? "pairing";
|
|
64
270
|
if (dmPolicy === "disabled") {
|
|
65
271
|
logVerbose(`agent ${componentLabel}: blocked (DM policy disabled)`);
|
|
66
272
|
try {
|
|
67
273
|
await interaction.reply({
|
|
68
274
|
content: "DM interactions are disabled.",
|
|
69
|
-
|
|
275
|
+
...replyOpts,
|
|
70
276
|
});
|
|
71
277
|
}
|
|
72
278
|
catch {
|
|
@@ -111,7 +317,7 @@ async function ensureDmComponentAuthorized(params) {
|
|
|
111
317
|
code,
|
|
112
318
|
})
|
|
113
319
|
: "Pairing already requested. Ask the bot owner to approve your code.",
|
|
114
|
-
|
|
320
|
+
...replyOpts,
|
|
115
321
|
});
|
|
116
322
|
}
|
|
117
323
|
catch {
|
|
@@ -123,7 +329,7 @@ async function ensureDmComponentAuthorized(params) {
|
|
|
123
329
|
try {
|
|
124
330
|
await interaction.reply({
|
|
125
331
|
content: `You are not authorized to use this ${componentLabel}.`,
|
|
126
|
-
|
|
332
|
+
...replyOpts,
|
|
127
333
|
});
|
|
128
334
|
}
|
|
129
335
|
catch {
|
|
@@ -131,6 +337,597 @@ async function ensureDmComponentAuthorized(params) {
|
|
|
131
337
|
}
|
|
132
338
|
return false;
|
|
133
339
|
}
|
|
340
|
+
async function resolveInteractionContextWithDmAuth(params) {
|
|
341
|
+
const interactionCtx = await resolveComponentInteractionContext({
|
|
342
|
+
interaction: params.interaction,
|
|
343
|
+
label: params.label,
|
|
344
|
+
defer: params.defer,
|
|
345
|
+
});
|
|
346
|
+
if (!interactionCtx) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
if (interactionCtx.isDirectMessage) {
|
|
350
|
+
const authorized = await ensureDmComponentAuthorized({
|
|
351
|
+
ctx: params.ctx,
|
|
352
|
+
interaction: params.interaction,
|
|
353
|
+
user: interactionCtx.user,
|
|
354
|
+
componentLabel: params.componentLabel,
|
|
355
|
+
replyOpts: interactionCtx.replyOpts,
|
|
356
|
+
});
|
|
357
|
+
if (!authorized) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return interactionCtx;
|
|
362
|
+
}
|
|
363
|
+
function normalizeComponentId(value) {
|
|
364
|
+
if (typeof value === "string") {
|
|
365
|
+
const trimmed = value.trim();
|
|
366
|
+
return trimmed ? trimmed : undefined;
|
|
367
|
+
}
|
|
368
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
369
|
+
return String(value);
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
function parseDiscordComponentData(data, customId) {
|
|
374
|
+
if (!data || typeof data !== "object") {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
const rawComponentId = "cid" in data
|
|
378
|
+
? data.cid
|
|
379
|
+
: data.componentId;
|
|
380
|
+
const rawModalId = "mid" in data ? data.mid : data.modalId;
|
|
381
|
+
let componentId = normalizeComponentId(rawComponentId);
|
|
382
|
+
let modalId = normalizeComponentId(rawModalId);
|
|
383
|
+
if (!componentId && customId) {
|
|
384
|
+
const parsed = parseDiscordComponentCustomId(customId);
|
|
385
|
+
if (parsed) {
|
|
386
|
+
componentId = parsed.componentId;
|
|
387
|
+
modalId = parsed.modalId;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!componentId) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return { componentId, modalId };
|
|
394
|
+
}
|
|
395
|
+
function parseDiscordModalId(data, customId) {
|
|
396
|
+
if (data && typeof data === "object") {
|
|
397
|
+
const rawModalId = "mid" in data ? data.mid : data.modalId;
|
|
398
|
+
const modalId = normalizeComponentId(rawModalId);
|
|
399
|
+
if (modalId) {
|
|
400
|
+
return modalId;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (customId) {
|
|
404
|
+
return parseDiscordModalCustomId(customId);
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
function resolveInteractionCustomId(interaction) {
|
|
409
|
+
if (!interaction?.rawData || typeof interaction.rawData !== "object") {
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
if (!("data" in interaction.rawData)) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const data = interaction.rawData.data;
|
|
416
|
+
const customId = data?.custom_id;
|
|
417
|
+
if (typeof customId !== "string") {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
const trimmed = customId.trim();
|
|
421
|
+
return trimmed ? trimmed : undefined;
|
|
422
|
+
}
|
|
423
|
+
function mapOptionLabels(options, values) {
|
|
424
|
+
if (!options || options.length === 0) {
|
|
425
|
+
return values;
|
|
426
|
+
}
|
|
427
|
+
const map = new Map(options.map((option) => [option.value, option.label]));
|
|
428
|
+
return values.map((value) => map.get(value) ?? value);
|
|
429
|
+
}
|
|
430
|
+
function mapSelectValues(entry, values) {
|
|
431
|
+
if (entry.selectType === "string") {
|
|
432
|
+
return mapOptionLabels(entry.options, values);
|
|
433
|
+
}
|
|
434
|
+
if (entry.selectType === "user") {
|
|
435
|
+
return values.map((value) => `user:${value}`);
|
|
436
|
+
}
|
|
437
|
+
if (entry.selectType === "role") {
|
|
438
|
+
return values.map((value) => `role:${value}`);
|
|
439
|
+
}
|
|
440
|
+
if (entry.selectType === "mentionable") {
|
|
441
|
+
return values.map((value) => `mentionable:${value}`);
|
|
442
|
+
}
|
|
443
|
+
if (entry.selectType === "channel") {
|
|
444
|
+
return values.map((value) => `channel:${value}`);
|
|
445
|
+
}
|
|
446
|
+
return values;
|
|
447
|
+
}
|
|
448
|
+
function resolveModalFieldValues(field, interaction) {
|
|
449
|
+
const fields = interaction.fields;
|
|
450
|
+
const optionLabels = field.options?.map((option) => ({
|
|
451
|
+
value: option.value,
|
|
452
|
+
label: option.label,
|
|
453
|
+
}));
|
|
454
|
+
const required = field.required === true;
|
|
455
|
+
try {
|
|
456
|
+
switch (field.type) {
|
|
457
|
+
case "text": {
|
|
458
|
+
const value = required ? fields.getText(field.id, true) : fields.getText(field.id);
|
|
459
|
+
return value ? [value] : [];
|
|
460
|
+
}
|
|
461
|
+
case "select":
|
|
462
|
+
case "checkbox":
|
|
463
|
+
case "radio": {
|
|
464
|
+
const values = required
|
|
465
|
+
? fields.getStringSelect(field.id, true)
|
|
466
|
+
: (fields.getStringSelect(field.id) ?? []);
|
|
467
|
+
return mapOptionLabels(optionLabels, values);
|
|
468
|
+
}
|
|
469
|
+
case "role-select": {
|
|
470
|
+
try {
|
|
471
|
+
const roles = required
|
|
472
|
+
? fields.getRoleSelect(field.id, true)
|
|
473
|
+
: (fields.getRoleSelect(field.id) ?? []);
|
|
474
|
+
return roles.map((role) => role.name ?? role.id);
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
const values = required
|
|
478
|
+
? fields.getStringSelect(field.id, true)
|
|
479
|
+
: (fields.getStringSelect(field.id) ?? []);
|
|
480
|
+
return values;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
case "user-select": {
|
|
484
|
+
const users = required
|
|
485
|
+
? fields.getUserSelect(field.id, true)
|
|
486
|
+
: (fields.getUserSelect(field.id) ?? []);
|
|
487
|
+
return users.map((user) => formatDiscordUserTag(user));
|
|
488
|
+
}
|
|
489
|
+
default:
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
logError(`agent modal: failed to read field ${field.id}: ${String(err)}`);
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function formatModalSubmissionText(entry, interaction) {
|
|
499
|
+
const lines = [`Form "${entry.title}" submitted.`];
|
|
500
|
+
for (const field of entry.fields) {
|
|
501
|
+
const values = resolveModalFieldValues(field, interaction);
|
|
502
|
+
if (values.length === 0) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
lines.push(`- ${field.label}: ${values.join(", ")}`);
|
|
506
|
+
}
|
|
507
|
+
if (lines.length === 1) {
|
|
508
|
+
lines.push("- (no values)");
|
|
509
|
+
}
|
|
510
|
+
return lines.join("\n");
|
|
511
|
+
}
|
|
512
|
+
async function dispatchDiscordComponentEvent(params) {
|
|
513
|
+
const { ctx, interaction, interactionCtx, channelCtx, guildInfo, eventText } = params;
|
|
514
|
+
const runtime = ctx.runtime ?? createNonExitingRuntime();
|
|
515
|
+
const route = resolveAgentRoute({
|
|
516
|
+
cfg: ctx.cfg,
|
|
517
|
+
channel: "discord",
|
|
518
|
+
accountId: ctx.accountId,
|
|
519
|
+
guildId: interactionCtx.rawGuildId,
|
|
520
|
+
memberRoleIds: interactionCtx.memberRoleIds,
|
|
521
|
+
peer: {
|
|
522
|
+
kind: interactionCtx.isDirectMessage ? "direct" : "channel",
|
|
523
|
+
id: interactionCtx.isDirectMessage ? interactionCtx.userId : interactionCtx.channelId,
|
|
524
|
+
},
|
|
525
|
+
parentPeer: channelCtx.parentId ? { kind: "channel", id: channelCtx.parentId } : undefined,
|
|
526
|
+
});
|
|
527
|
+
const sessionKey = params.routeOverrides?.sessionKey ?? route.sessionKey;
|
|
528
|
+
const agentId = params.routeOverrides?.agentId ?? route.agentId;
|
|
529
|
+
const accountId = params.routeOverrides?.accountId ?? route.accountId;
|
|
530
|
+
const fromLabel = interactionCtx.isDirectMessage
|
|
531
|
+
? buildDirectLabel(interactionCtx.user)
|
|
532
|
+
: buildGuildLabel({
|
|
533
|
+
guild: interaction.guild ?? undefined,
|
|
534
|
+
channelName: channelCtx.channelName ?? interactionCtx.channelId,
|
|
535
|
+
channelId: interactionCtx.channelId,
|
|
536
|
+
});
|
|
537
|
+
const senderName = interactionCtx.user.globalName ?? interactionCtx.user.username;
|
|
538
|
+
const senderUsername = interactionCtx.user.username;
|
|
539
|
+
const senderTag = formatDiscordUserTag(interactionCtx.user);
|
|
540
|
+
const groupChannel = !interactionCtx.isDirectMessage && channelCtx.channelSlug
|
|
541
|
+
? `#${channelCtx.channelSlug}`
|
|
542
|
+
: undefined;
|
|
543
|
+
const groupSubject = interactionCtx.isDirectMessage ? undefined : groupChannel;
|
|
544
|
+
const channelConfig = resolveDiscordChannelConfigWithFallback({
|
|
545
|
+
guildInfo,
|
|
546
|
+
channelId: interactionCtx.channelId,
|
|
547
|
+
channelName: channelCtx.channelName,
|
|
548
|
+
channelSlug: channelCtx.channelSlug,
|
|
549
|
+
parentId: channelCtx.parentId,
|
|
550
|
+
parentName: channelCtx.parentName,
|
|
551
|
+
parentSlug: channelCtx.parentSlug,
|
|
552
|
+
scope: channelCtx.isThread ? "thread" : "channel",
|
|
553
|
+
});
|
|
554
|
+
const groupSystemPrompt = channelConfig?.systemPrompt?.trim() || undefined;
|
|
555
|
+
const ownerAllowFrom = resolveDiscordOwnerAllowFrom({
|
|
556
|
+
channelConfig,
|
|
557
|
+
guildInfo,
|
|
558
|
+
sender: { id: interactionCtx.user.id, name: interactionCtx.user.username, tag: senderTag },
|
|
559
|
+
});
|
|
560
|
+
const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId });
|
|
561
|
+
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
|
562
|
+
const previousTimestamp = readSessionUpdatedAt({
|
|
563
|
+
storePath,
|
|
564
|
+
sessionKey,
|
|
565
|
+
});
|
|
566
|
+
const timestamp = Date.now();
|
|
567
|
+
const combinedBody = formatInboundEnvelope({
|
|
568
|
+
channel: "Discord",
|
|
569
|
+
from: fromLabel,
|
|
570
|
+
timestamp,
|
|
571
|
+
body: eventText,
|
|
572
|
+
chatType: interactionCtx.isDirectMessage ? "direct" : "channel",
|
|
573
|
+
senderLabel: senderName,
|
|
574
|
+
previousTimestamp,
|
|
575
|
+
envelope: envelopeOptions,
|
|
576
|
+
});
|
|
577
|
+
const ctxPayload = finalizeInboundContext({
|
|
578
|
+
Body: combinedBody,
|
|
579
|
+
BodyForAgent: eventText,
|
|
580
|
+
RawBody: eventText,
|
|
581
|
+
CommandBody: eventText,
|
|
582
|
+
From: interactionCtx.isDirectMessage
|
|
583
|
+
? `discord:${interactionCtx.userId}`
|
|
584
|
+
: `discord:channel:${interactionCtx.channelId}`,
|
|
585
|
+
To: `channel:${interactionCtx.channelId}`,
|
|
586
|
+
SessionKey: sessionKey,
|
|
587
|
+
AccountId: accountId,
|
|
588
|
+
ChatType: interactionCtx.isDirectMessage ? "direct" : "channel",
|
|
589
|
+
ConversationLabel: fromLabel,
|
|
590
|
+
SenderName: senderName,
|
|
591
|
+
SenderId: interactionCtx.userId,
|
|
592
|
+
SenderUsername: senderUsername,
|
|
593
|
+
SenderTag: senderTag,
|
|
594
|
+
GroupSubject: groupSubject,
|
|
595
|
+
GroupChannel: groupChannel,
|
|
596
|
+
GroupSystemPrompt: interactionCtx.isDirectMessage ? undefined : groupSystemPrompt,
|
|
597
|
+
GroupSpace: guildInfo?.id ?? guildInfo?.slug ?? interactionCtx.rawGuildId ?? undefined,
|
|
598
|
+
OwnerAllowFrom: ownerAllowFrom,
|
|
599
|
+
Provider: "discord",
|
|
600
|
+
Surface: "discord",
|
|
601
|
+
WasMentioned: true,
|
|
602
|
+
CommandAuthorized: true,
|
|
603
|
+
CommandSource: "text",
|
|
604
|
+
MessageSid: interaction.rawData.id,
|
|
605
|
+
Timestamp: timestamp,
|
|
606
|
+
OriginatingChannel: "discord",
|
|
607
|
+
OriginatingTo: `channel:${interactionCtx.channelId}`,
|
|
608
|
+
});
|
|
609
|
+
await recordInboundSession({
|
|
610
|
+
storePath,
|
|
611
|
+
sessionKey: ctxPayload.SessionKey ?? sessionKey,
|
|
612
|
+
ctx: ctxPayload,
|
|
613
|
+
updateLastRoute: interactionCtx.isDirectMessage
|
|
614
|
+
? {
|
|
615
|
+
sessionKey: route.mainSessionKey,
|
|
616
|
+
channel: "discord",
|
|
617
|
+
to: `user:${interactionCtx.userId}`,
|
|
618
|
+
accountId,
|
|
619
|
+
}
|
|
620
|
+
: undefined,
|
|
621
|
+
onRecordError: (err) => {
|
|
622
|
+
logVerbose(`discord: failed updating component session meta: ${String(err)}`);
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
const deliverTarget = `channel:${interactionCtx.channelId}`;
|
|
626
|
+
const typingChannelId = interactionCtx.channelId;
|
|
627
|
+
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
|
628
|
+
cfg: ctx.cfg,
|
|
629
|
+
agentId,
|
|
630
|
+
channel: "discord",
|
|
631
|
+
accountId,
|
|
632
|
+
});
|
|
633
|
+
const tableMode = resolveMarkdownTableMode({
|
|
634
|
+
cfg: ctx.cfg,
|
|
635
|
+
channel: "discord",
|
|
636
|
+
accountId,
|
|
637
|
+
});
|
|
638
|
+
const textLimit = resolveTextChunkLimit(ctx.cfg, "discord", accountId, {
|
|
639
|
+
fallbackLimit: 2000,
|
|
640
|
+
});
|
|
641
|
+
const token = ctx.token ?? "";
|
|
642
|
+
const replyToMode = ctx.discordConfig?.replyToMode ?? ctx.cfg.channels?.discord?.replyToMode ?? "off";
|
|
643
|
+
const replyReference = createReplyReferencePlanner({
|
|
644
|
+
replyToMode,
|
|
645
|
+
startId: params.replyToId,
|
|
646
|
+
});
|
|
647
|
+
await dispatchReplyWithBufferedBlockDispatcher({
|
|
648
|
+
ctx: ctxPayload,
|
|
649
|
+
cfg: ctx.cfg,
|
|
650
|
+
replyOptions: { onModelSelected },
|
|
651
|
+
dispatcherOptions: {
|
|
652
|
+
...prefixOptions,
|
|
653
|
+
humanDelay: resolveHumanDelayConfig(ctx.cfg, agentId),
|
|
654
|
+
deliver: async (payload) => {
|
|
655
|
+
const replyToId = replyReference.use();
|
|
656
|
+
await deliverDiscordReply({
|
|
657
|
+
replies: [payload],
|
|
658
|
+
target: deliverTarget,
|
|
659
|
+
token,
|
|
660
|
+
accountId,
|
|
661
|
+
rest: interaction.client.rest,
|
|
662
|
+
runtime,
|
|
663
|
+
replyToId,
|
|
664
|
+
textLimit,
|
|
665
|
+
maxLinesPerMessage: ctx.discordConfig?.maxLinesPerMessage,
|
|
666
|
+
tableMode,
|
|
667
|
+
chunkMode: resolveChunkMode(ctx.cfg, "discord", accountId),
|
|
668
|
+
});
|
|
669
|
+
replyReference.markSent();
|
|
670
|
+
},
|
|
671
|
+
onReplyStart: async () => {
|
|
672
|
+
try {
|
|
673
|
+
await sendTyping({ client: interaction.client, channelId: typingChannelId });
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
logVerbose(`discord: typing failed for component reply: ${String(err)}`);
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
onError: (err) => {
|
|
680
|
+
logError(`discord component dispatch failed: ${String(err)}`);
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async function handleDiscordComponentEvent(params) {
|
|
686
|
+
const parsed = parseDiscordComponentData(params.data, resolveInteractionCustomId(params.interaction));
|
|
687
|
+
if (!parsed) {
|
|
688
|
+
logError(`${params.label}: failed to parse component data`);
|
|
689
|
+
try {
|
|
690
|
+
await params.interaction.reply({
|
|
691
|
+
content: "This component is no longer valid.",
|
|
692
|
+
ephemeral: true,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
// Interaction may have expired
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const entry = resolveDiscordComponentEntry({ id: parsed.componentId, consume: false });
|
|
701
|
+
if (!entry) {
|
|
702
|
+
try {
|
|
703
|
+
await params.interaction.reply({
|
|
704
|
+
content: "This component has expired.",
|
|
705
|
+
ephemeral: true,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
// Interaction may have expired
|
|
710
|
+
}
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const interactionCtx = await resolveInteractionContextWithDmAuth({
|
|
714
|
+
ctx: params.ctx,
|
|
715
|
+
interaction: params.interaction,
|
|
716
|
+
label: params.label,
|
|
717
|
+
componentLabel: params.componentLabel,
|
|
718
|
+
});
|
|
719
|
+
if (!interactionCtx) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const { channelId, user, replyOpts, rawGuildId, memberRoleIds } = interactionCtx;
|
|
723
|
+
const guildInfo = resolveDiscordGuildEntry({
|
|
724
|
+
guild: params.interaction.guild ?? undefined,
|
|
725
|
+
guildEntries: params.ctx.guildEntries,
|
|
726
|
+
});
|
|
727
|
+
const channelCtx = resolveDiscordChannelContext(params.interaction);
|
|
728
|
+
const unauthorizedReply = `You are not authorized to use this ${params.componentLabel}.`;
|
|
729
|
+
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
|
730
|
+
interaction: params.interaction,
|
|
731
|
+
guildInfo,
|
|
732
|
+
channelId,
|
|
733
|
+
rawGuildId,
|
|
734
|
+
channelCtx,
|
|
735
|
+
memberRoleIds,
|
|
736
|
+
user,
|
|
737
|
+
replyOpts,
|
|
738
|
+
componentLabel: params.componentLabel,
|
|
739
|
+
unauthorizedReply,
|
|
740
|
+
});
|
|
741
|
+
if (!memberAllowed) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const componentAllowed = await ensureComponentUserAllowed({
|
|
745
|
+
entry,
|
|
746
|
+
interaction: params.interaction,
|
|
747
|
+
user,
|
|
748
|
+
replyOpts,
|
|
749
|
+
componentLabel: params.componentLabel,
|
|
750
|
+
unauthorizedReply,
|
|
751
|
+
});
|
|
752
|
+
if (!componentAllowed) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const consumed = resolveDiscordComponentEntry({
|
|
756
|
+
id: parsed.componentId,
|
|
757
|
+
consume: !entry.reusable,
|
|
758
|
+
});
|
|
759
|
+
if (!consumed) {
|
|
760
|
+
try {
|
|
761
|
+
await params.interaction.reply({
|
|
762
|
+
content: "This component has expired.",
|
|
763
|
+
ephemeral: true,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
// Interaction may have expired
|
|
768
|
+
}
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (consumed.kind === "modal-trigger") {
|
|
772
|
+
try {
|
|
773
|
+
await params.interaction.reply({
|
|
774
|
+
content: "This form is no longer available.",
|
|
775
|
+
ephemeral: true,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
// Interaction may have expired
|
|
780
|
+
}
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const values = params.values ? mapSelectValues(consumed, params.values) : undefined;
|
|
784
|
+
const eventText = formatDiscordComponentEventText({
|
|
785
|
+
kind: consumed.kind === "select" ? "select" : "button",
|
|
786
|
+
label: consumed.label,
|
|
787
|
+
values,
|
|
788
|
+
});
|
|
789
|
+
try {
|
|
790
|
+
await params.interaction.reply({ content: "✓", ...replyOpts });
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
logError(`${params.label}: failed to acknowledge interaction: ${String(err)}`);
|
|
794
|
+
}
|
|
795
|
+
await dispatchDiscordComponentEvent({
|
|
796
|
+
ctx: params.ctx,
|
|
797
|
+
interaction: params.interaction,
|
|
798
|
+
interactionCtx,
|
|
799
|
+
channelCtx,
|
|
800
|
+
guildInfo,
|
|
801
|
+
eventText,
|
|
802
|
+
replyToId: consumed.messageId ?? params.interaction.message?.id,
|
|
803
|
+
routeOverrides: {
|
|
804
|
+
sessionKey: consumed.sessionKey,
|
|
805
|
+
agentId: consumed.agentId,
|
|
806
|
+
accountId: consumed.accountId,
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
async function handleDiscordModalTrigger(params) {
|
|
811
|
+
const parsed = parseDiscordComponentData(params.data, resolveInteractionCustomId(params.interaction));
|
|
812
|
+
if (!parsed) {
|
|
813
|
+
logError(`${params.label}: failed to parse modal trigger data`);
|
|
814
|
+
try {
|
|
815
|
+
await params.interaction.reply({
|
|
816
|
+
content: "This button is no longer valid.",
|
|
817
|
+
ephemeral: true,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
// Interaction may have expired
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const entry = resolveDiscordComponentEntry({ id: parsed.componentId, consume: false });
|
|
826
|
+
if (!entry || entry.kind !== "modal-trigger") {
|
|
827
|
+
try {
|
|
828
|
+
await params.interaction.reply({
|
|
829
|
+
content: "This button has expired.",
|
|
830
|
+
ephemeral: true,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// Interaction may have expired
|
|
835
|
+
}
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const modalId = entry.modalId ?? parsed.modalId;
|
|
839
|
+
if (!modalId) {
|
|
840
|
+
try {
|
|
841
|
+
await params.interaction.reply({
|
|
842
|
+
content: "This form is no longer available.",
|
|
843
|
+
ephemeral: true,
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
catch {
|
|
847
|
+
// Interaction may have expired
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const interactionCtx = await resolveInteractionContextWithDmAuth({
|
|
852
|
+
ctx: params.ctx,
|
|
853
|
+
interaction: params.interaction,
|
|
854
|
+
label: params.label,
|
|
855
|
+
componentLabel: "form",
|
|
856
|
+
defer: false,
|
|
857
|
+
});
|
|
858
|
+
if (!interactionCtx) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const { channelId, user, replyOpts, rawGuildId, memberRoleIds } = interactionCtx;
|
|
862
|
+
const guildInfo = resolveDiscordGuildEntry({
|
|
863
|
+
guild: params.interaction.guild ?? undefined,
|
|
864
|
+
guildEntries: params.ctx.guildEntries,
|
|
865
|
+
});
|
|
866
|
+
const channelCtx = resolveDiscordChannelContext(params.interaction);
|
|
867
|
+
const unauthorizedReply = "You are not authorized to use this form.";
|
|
868
|
+
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
|
869
|
+
interaction: params.interaction,
|
|
870
|
+
guildInfo,
|
|
871
|
+
channelId,
|
|
872
|
+
rawGuildId,
|
|
873
|
+
channelCtx,
|
|
874
|
+
memberRoleIds,
|
|
875
|
+
user,
|
|
876
|
+
replyOpts,
|
|
877
|
+
componentLabel: "form",
|
|
878
|
+
unauthorizedReply,
|
|
879
|
+
});
|
|
880
|
+
if (!memberAllowed) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const componentAllowed = await ensureComponentUserAllowed({
|
|
884
|
+
entry,
|
|
885
|
+
interaction: params.interaction,
|
|
886
|
+
user,
|
|
887
|
+
replyOpts,
|
|
888
|
+
componentLabel: "form",
|
|
889
|
+
unauthorizedReply,
|
|
890
|
+
});
|
|
891
|
+
if (!componentAllowed) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
const consumed = resolveDiscordComponentEntry({
|
|
895
|
+
id: parsed.componentId,
|
|
896
|
+
consume: !entry.reusable,
|
|
897
|
+
});
|
|
898
|
+
if (!consumed) {
|
|
899
|
+
try {
|
|
900
|
+
await params.interaction.reply({
|
|
901
|
+
content: "This form has expired.",
|
|
902
|
+
ephemeral: true,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
// Interaction may have expired
|
|
907
|
+
}
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const resolvedModalId = consumed.modalId ?? modalId;
|
|
911
|
+
const modalEntry = resolveDiscordModalEntry({ id: resolvedModalId, consume: false });
|
|
912
|
+
if (!modalEntry) {
|
|
913
|
+
try {
|
|
914
|
+
await params.interaction.reply({
|
|
915
|
+
content: "This form has expired.",
|
|
916
|
+
ephemeral: true,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
catch {
|
|
920
|
+
// Interaction may have expired
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
await params.interaction.showModal(createDiscordFormModal(modalEntry));
|
|
926
|
+
}
|
|
927
|
+
catch (err) {
|
|
928
|
+
logError(`${params.label}: failed to show modal: ${String(err)}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
134
931
|
export class AgentComponentButton extends Button {
|
|
135
932
|
label = AGENT_BUTTON_KEY;
|
|
136
933
|
customId = `${AGENT_BUTTON_KEY}:seed=1`;
|
|
@@ -157,112 +954,41 @@ export class AgentComponentButton extends Button {
|
|
|
157
954
|
return;
|
|
158
955
|
}
|
|
159
956
|
const { componentId } = parsed;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
const user = interaction.user;
|
|
169
|
-
if (!user) {
|
|
170
|
-
logError("agent button: missing user in interaction");
|
|
957
|
+
const interactionCtx = await resolveInteractionContextWithDmAuth({
|
|
958
|
+
ctx: this.ctx,
|
|
959
|
+
interaction,
|
|
960
|
+
label: "agent button",
|
|
961
|
+
componentLabel: "button",
|
|
962
|
+
});
|
|
963
|
+
if (!interactionCtx) {
|
|
171
964
|
return;
|
|
172
965
|
}
|
|
173
|
-
const username =
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (!authorized) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// P2 FIX: Check user allowlist before processing component interaction
|
|
191
|
-
// This prevents unauthorized users from injecting system events
|
|
192
|
-
const guild = interaction.guild;
|
|
193
|
-
const guildInfo = resolveDiscordGuildEntry({
|
|
194
|
-
guild: guild ?? undefined,
|
|
195
|
-
guildEntries: this.ctx.guildEntries,
|
|
966
|
+
const { channelId, user, username, userId, replyOpts, rawGuildId, isDirectMessage, memberRoleIds, } = interactionCtx;
|
|
967
|
+
// Check user allowlist before processing component interaction
|
|
968
|
+
// This prevents unauthorized users from injecting system events.
|
|
969
|
+
const allowed = await ensureAgentComponentInteractionAllowed({
|
|
970
|
+
ctx: this.ctx,
|
|
971
|
+
interaction,
|
|
972
|
+
channelId,
|
|
973
|
+
rawGuildId,
|
|
974
|
+
memberRoleIds,
|
|
975
|
+
user,
|
|
976
|
+
replyOpts,
|
|
977
|
+
componentLabel: "button",
|
|
978
|
+
unauthorizedReply: "You are not authorized to use this button.",
|
|
196
979
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const channelName = channel && "name" in channel ? channel.name : undefined;
|
|
200
|
-
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
|
201
|
-
const channelType = channel && "type" in channel ? channel.type : undefined;
|
|
202
|
-
const isThread = isThreadChannelType(channelType);
|
|
203
|
-
// Resolve thread parent for allowlist inheritance
|
|
204
|
-
// Note: We can get parentId from channel but cannot fetch parent name without a client.
|
|
205
|
-
// The parentId alone enables ID-based parent config matching. Name-based matching
|
|
206
|
-
// requires the channel cache to have parent info available.
|
|
207
|
-
let parentId;
|
|
208
|
-
let parentName;
|
|
209
|
-
let parentSlug = "";
|
|
210
|
-
if (isThread && channel && "parentId" in channel) {
|
|
211
|
-
parentId = channel.parentId ?? undefined;
|
|
212
|
-
// Try to get parent name from channel's parent if available
|
|
213
|
-
if ("parent" in channel) {
|
|
214
|
-
const parent = channel.parent;
|
|
215
|
-
if (parent?.name) {
|
|
216
|
-
parentName = parent.name;
|
|
217
|
-
parentSlug = normalizeDiscordSlug(parentName);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Only check guild allowlists if this is a guild interaction
|
|
222
|
-
if (rawGuildId) {
|
|
223
|
-
const channelConfig = resolveDiscordChannelConfigWithFallback({
|
|
224
|
-
guildInfo,
|
|
225
|
-
channelId,
|
|
226
|
-
channelName,
|
|
227
|
-
channelSlug,
|
|
228
|
-
parentId,
|
|
229
|
-
parentName,
|
|
230
|
-
parentSlug,
|
|
231
|
-
scope: isThread ? "thread" : "channel",
|
|
232
|
-
});
|
|
233
|
-
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
|
234
|
-
if (Array.isArray(channelUsers) && channelUsers.length > 0) {
|
|
235
|
-
const userOk = resolveDiscordUserAllowed({
|
|
236
|
-
allowList: channelUsers,
|
|
237
|
-
userId,
|
|
238
|
-
userName: user.username,
|
|
239
|
-
userTag: user.discriminator ? `${user.username}#${user.discriminator}` : undefined,
|
|
240
|
-
});
|
|
241
|
-
if (!userOk) {
|
|
242
|
-
logVerbose(`agent button: blocked user ${userId} (not in allowlist)`);
|
|
243
|
-
try {
|
|
244
|
-
await interaction.reply({
|
|
245
|
-
content: "You are not authorized to use this button.",
|
|
246
|
-
ephemeral: true,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// Interaction may have expired
|
|
251
|
-
}
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
980
|
+
if (!allowed) {
|
|
981
|
+
return;
|
|
255
982
|
}
|
|
256
|
-
|
|
257
|
-
const route =
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
},
|
|
983
|
+
const { parentId } = allowed;
|
|
984
|
+
const route = resolveAgentComponentRoute({
|
|
985
|
+
ctx: this.ctx,
|
|
986
|
+
rawGuildId,
|
|
987
|
+
memberRoleIds,
|
|
988
|
+
isDirectMessage,
|
|
989
|
+
userId,
|
|
990
|
+
channelId,
|
|
991
|
+
parentId,
|
|
266
992
|
});
|
|
267
993
|
const eventText = `[Discord component: ${componentId} clicked by ${username} (${userId})]`;
|
|
268
994
|
logDebug(`agent button: enqueuing event for channel ${channelId}: ${eventText}`);
|
|
@@ -270,16 +996,7 @@ export class AgentComponentButton extends Button {
|
|
|
270
996
|
sessionKey: route.sessionKey,
|
|
271
997
|
contextKey: `discord:agent-button:${channelId}:${componentId}:${userId}`,
|
|
272
998
|
});
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
await interaction.reply({
|
|
276
|
-
content: "✓",
|
|
277
|
-
ephemeral: true,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
catch (err) {
|
|
281
|
-
logError(`agent button: failed to acknowledge interaction: ${String(err)}`);
|
|
282
|
-
}
|
|
999
|
+
await ackComponentInteraction({ interaction, replyOpts, label: "agent button" });
|
|
283
1000
|
}
|
|
284
1001
|
}
|
|
285
1002
|
export class AgentSelectMenu extends StringSelectMenu {
|
|
@@ -307,127 +1024,285 @@ export class AgentSelectMenu extends StringSelectMenu {
|
|
|
307
1024
|
return;
|
|
308
1025
|
}
|
|
309
1026
|
const { componentId } = parsed;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
1027
|
+
const interactionCtx = await resolveInteractionContextWithDmAuth({
|
|
1028
|
+
ctx: this.ctx,
|
|
1029
|
+
interaction,
|
|
1030
|
+
label: "agent select",
|
|
1031
|
+
componentLabel: "select menu",
|
|
1032
|
+
});
|
|
1033
|
+
if (!interactionCtx) {
|
|
315
1034
|
return;
|
|
316
1035
|
}
|
|
317
|
-
const user =
|
|
318
|
-
|
|
319
|
-
|
|
1036
|
+
const { channelId, user, username, userId, replyOpts, rawGuildId, isDirectMessage, memberRoleIds, } = interactionCtx;
|
|
1037
|
+
// Check user allowlist before processing component interaction.
|
|
1038
|
+
const allowed = await ensureAgentComponentInteractionAllowed({
|
|
1039
|
+
ctx: this.ctx,
|
|
1040
|
+
interaction,
|
|
1041
|
+
channelId,
|
|
1042
|
+
rawGuildId,
|
|
1043
|
+
memberRoleIds,
|
|
1044
|
+
user,
|
|
1045
|
+
replyOpts,
|
|
1046
|
+
componentLabel: "select",
|
|
1047
|
+
unauthorizedReply: "You are not authorized to use this select menu.",
|
|
1048
|
+
});
|
|
1049
|
+
if (!allowed) {
|
|
320
1050
|
return;
|
|
321
1051
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
1052
|
+
const { parentId } = allowed;
|
|
1053
|
+
// Extract selected values
|
|
1054
|
+
const values = interaction.values ?? [];
|
|
1055
|
+
const valuesText = values.length > 0 ? ` (selected: ${values.join(", ")})` : "";
|
|
1056
|
+
const route = resolveAgentComponentRoute({
|
|
1057
|
+
ctx: this.ctx,
|
|
1058
|
+
rawGuildId,
|
|
1059
|
+
memberRoleIds,
|
|
1060
|
+
isDirectMessage,
|
|
1061
|
+
userId,
|
|
1062
|
+
channelId,
|
|
1063
|
+
parentId,
|
|
1064
|
+
});
|
|
1065
|
+
const eventText = `[Discord select menu: ${componentId} interacted by ${username} (${userId})${valuesText}]`;
|
|
1066
|
+
logDebug(`agent select: enqueuing event for channel ${channelId}: ${eventText}`);
|
|
1067
|
+
enqueueSystemEvent(eventText, {
|
|
1068
|
+
sessionKey: route.sessionKey,
|
|
1069
|
+
contextKey: `discord:agent-select:${channelId}:${componentId}:${userId}`,
|
|
1070
|
+
});
|
|
1071
|
+
await ackComponentInteraction({ interaction, replyOpts, label: "agent select" });
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
class DiscordComponentButton extends Button {
|
|
1075
|
+
label = "component";
|
|
1076
|
+
customId = "*";
|
|
1077
|
+
style = ButtonStyle.Primary;
|
|
1078
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1079
|
+
ctx;
|
|
1080
|
+
constructor(ctx) {
|
|
1081
|
+
super();
|
|
1082
|
+
this.ctx = ctx;
|
|
1083
|
+
}
|
|
1084
|
+
async run(interaction, data) {
|
|
1085
|
+
const parsed = parseDiscordComponentData(data, resolveInteractionCustomId(interaction));
|
|
1086
|
+
if (parsed?.modalId) {
|
|
1087
|
+
await handleDiscordModalTrigger({
|
|
330
1088
|
ctx: this.ctx,
|
|
331
1089
|
interaction,
|
|
332
|
-
|
|
333
|
-
|
|
1090
|
+
data,
|
|
1091
|
+
label: "discord component modal",
|
|
334
1092
|
});
|
|
335
|
-
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
1093
|
+
return;
|
|
338
1094
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
1095
|
+
await handleDiscordComponentEvent({
|
|
1096
|
+
ctx: this.ctx,
|
|
1097
|
+
interaction,
|
|
1098
|
+
data,
|
|
1099
|
+
componentLabel: "button",
|
|
1100
|
+
label: "discord component button",
|
|
344
1101
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
class DiscordComponentStringSelect extends StringSelectMenu {
|
|
1105
|
+
customId = "*";
|
|
1106
|
+
options = [];
|
|
1107
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1108
|
+
ctx;
|
|
1109
|
+
constructor(ctx) {
|
|
1110
|
+
super();
|
|
1111
|
+
this.ctx = ctx;
|
|
1112
|
+
}
|
|
1113
|
+
async run(interaction, data) {
|
|
1114
|
+
await handleDiscordComponentEvent({
|
|
1115
|
+
ctx: this.ctx,
|
|
1116
|
+
interaction,
|
|
1117
|
+
data,
|
|
1118
|
+
componentLabel: "select menu",
|
|
1119
|
+
label: "discord component select",
|
|
1120
|
+
values: interaction.values ?? [],
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
class DiscordComponentUserSelect extends UserSelectMenu {
|
|
1125
|
+
customId = "*";
|
|
1126
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1127
|
+
ctx;
|
|
1128
|
+
constructor(ctx) {
|
|
1129
|
+
super();
|
|
1130
|
+
this.ctx = ctx;
|
|
1131
|
+
}
|
|
1132
|
+
async run(interaction, data) {
|
|
1133
|
+
await handleDiscordComponentEvent({
|
|
1134
|
+
ctx: this.ctx,
|
|
1135
|
+
interaction,
|
|
1136
|
+
data,
|
|
1137
|
+
componentLabel: "user select",
|
|
1138
|
+
label: "discord component user select",
|
|
1139
|
+
values: interaction.values ?? [],
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
class DiscordComponentRoleSelect extends RoleSelectMenu {
|
|
1144
|
+
customId = "*";
|
|
1145
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1146
|
+
ctx;
|
|
1147
|
+
constructor(ctx) {
|
|
1148
|
+
super();
|
|
1149
|
+
this.ctx = ctx;
|
|
1150
|
+
}
|
|
1151
|
+
async run(interaction, data) {
|
|
1152
|
+
await handleDiscordComponentEvent({
|
|
1153
|
+
ctx: this.ctx,
|
|
1154
|
+
interaction,
|
|
1155
|
+
data,
|
|
1156
|
+
componentLabel: "role select",
|
|
1157
|
+
label: "discord component role select",
|
|
1158
|
+
values: interaction.values ?? [],
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
class DiscordComponentMentionableSelect extends MentionableSelectMenu {
|
|
1163
|
+
customId = "*";
|
|
1164
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1165
|
+
ctx;
|
|
1166
|
+
constructor(ctx) {
|
|
1167
|
+
super();
|
|
1168
|
+
this.ctx = ctx;
|
|
1169
|
+
}
|
|
1170
|
+
async run(interaction, data) {
|
|
1171
|
+
await handleDiscordComponentEvent({
|
|
1172
|
+
ctx: this.ctx,
|
|
1173
|
+
interaction,
|
|
1174
|
+
data,
|
|
1175
|
+
componentLabel: "mentionable select",
|
|
1176
|
+
label: "discord component mentionable select",
|
|
1177
|
+
values: interaction.values ?? [],
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
class DiscordComponentChannelSelect extends ChannelSelectMenu {
|
|
1182
|
+
customId = "*";
|
|
1183
|
+
customIdParser = parseDiscordComponentCustomIdForCarbon;
|
|
1184
|
+
ctx;
|
|
1185
|
+
constructor(ctx) {
|
|
1186
|
+
super();
|
|
1187
|
+
this.ctx = ctx;
|
|
1188
|
+
}
|
|
1189
|
+
async run(interaction, data) {
|
|
1190
|
+
await handleDiscordComponentEvent({
|
|
1191
|
+
ctx: this.ctx,
|
|
1192
|
+
interaction,
|
|
1193
|
+
data,
|
|
1194
|
+
componentLabel: "channel select",
|
|
1195
|
+
label: "discord component channel select",
|
|
1196
|
+
values: interaction.values ?? [],
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
class DiscordComponentModal extends Modal {
|
|
1201
|
+
title = "Pool Bot form";
|
|
1202
|
+
customId = "*";
|
|
1203
|
+
components = [];
|
|
1204
|
+
customIdParser = parseDiscordModalCustomIdForCarbon;
|
|
1205
|
+
ctx;
|
|
1206
|
+
constructor(ctx) {
|
|
1207
|
+
super();
|
|
1208
|
+
this.ctx = ctx;
|
|
1209
|
+
}
|
|
1210
|
+
async run(interaction, data) {
|
|
1211
|
+
const modalId = parseDiscordModalId(data, resolveInteractionCustomId(interaction));
|
|
1212
|
+
if (!modalId) {
|
|
1213
|
+
logError("discord component modal: missing modal id");
|
|
1214
|
+
try {
|
|
1215
|
+
await interaction.reply({
|
|
1216
|
+
content: "This form is no longer valid.",
|
|
1217
|
+
ephemeral: true,
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
catch {
|
|
1221
|
+
// Interaction may have expired
|
|
364
1222
|
}
|
|
1223
|
+
return;
|
|
365
1224
|
}
|
|
366
|
-
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
channelSlug,
|
|
373
|
-
parentId,
|
|
374
|
-
parentName,
|
|
375
|
-
parentSlug,
|
|
376
|
-
scope: isThread ? "thread" : "channel",
|
|
377
|
-
});
|
|
378
|
-
const channelUsers = channelConfig?.users ?? guildInfo?.users;
|
|
379
|
-
if (Array.isArray(channelUsers) && channelUsers.length > 0) {
|
|
380
|
-
const userOk = resolveDiscordUserAllowed({
|
|
381
|
-
allowList: channelUsers,
|
|
382
|
-
userId,
|
|
383
|
-
userName: user.username,
|
|
384
|
-
userTag: user.discriminator ? `${user.username}#${user.discriminator}` : undefined,
|
|
1225
|
+
const modalEntry = resolveDiscordModalEntry({ id: modalId, consume: false });
|
|
1226
|
+
if (!modalEntry) {
|
|
1227
|
+
try {
|
|
1228
|
+
await interaction.reply({
|
|
1229
|
+
content: "This form has expired.",
|
|
1230
|
+
ephemeral: true,
|
|
385
1231
|
});
|
|
386
|
-
if (!userOk) {
|
|
387
|
-
logVerbose(`agent select: blocked user ${userId} (not in allowlist)`);
|
|
388
|
-
try {
|
|
389
|
-
await interaction.reply({
|
|
390
|
-
content: "You are not authorized to use this select menu.",
|
|
391
|
-
ephemeral: true,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
// Interaction may have expired
|
|
396
|
-
}
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
1232
|
}
|
|
1233
|
+
catch {
|
|
1234
|
+
// Interaction may have expired
|
|
1235
|
+
}
|
|
1236
|
+
return;
|
|
400
1237
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
channel: "discord",
|
|
408
|
-
accountId: this.ctx.accountId,
|
|
409
|
-
guildId: rawGuildId,
|
|
410
|
-
peer: {
|
|
411
|
-
kind: isDirectMessage ? "dm" : "channel",
|
|
412
|
-
id: isDirectMessage ? userId : channelId,
|
|
413
|
-
},
|
|
1238
|
+
const interactionCtx = await resolveInteractionContextWithDmAuth({
|
|
1239
|
+
ctx: this.ctx,
|
|
1240
|
+
interaction,
|
|
1241
|
+
label: "discord component modal",
|
|
1242
|
+
componentLabel: "form",
|
|
1243
|
+
defer: false,
|
|
414
1244
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
1245
|
+
if (!interactionCtx) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const { channelId, user, replyOpts, rawGuildId, memberRoleIds } = interactionCtx;
|
|
1249
|
+
const guildInfo = resolveDiscordGuildEntry({
|
|
1250
|
+
guild: interaction.guild ?? undefined,
|
|
1251
|
+
guildEntries: this.ctx.guildEntries,
|
|
1252
|
+
});
|
|
1253
|
+
const channelCtx = resolveDiscordChannelContext(interaction);
|
|
1254
|
+
const memberAllowed = await ensureGuildComponentMemberAllowed({
|
|
1255
|
+
interaction,
|
|
1256
|
+
guildInfo,
|
|
1257
|
+
channelId,
|
|
1258
|
+
rawGuildId,
|
|
1259
|
+
channelCtx,
|
|
1260
|
+
memberRoleIds,
|
|
1261
|
+
user,
|
|
1262
|
+
replyOpts,
|
|
1263
|
+
componentLabel: "form",
|
|
1264
|
+
unauthorizedReply: "You are not authorized to use this form.",
|
|
1265
|
+
});
|
|
1266
|
+
if (!memberAllowed) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const consumed = resolveDiscordModalEntry({
|
|
1270
|
+
id: modalId,
|
|
1271
|
+
consume: !modalEntry.reusable,
|
|
420
1272
|
});
|
|
421
|
-
|
|
1273
|
+
if (!consumed) {
|
|
1274
|
+
try {
|
|
1275
|
+
await interaction.reply({
|
|
1276
|
+
content: "This form has expired.",
|
|
1277
|
+
ephemeral: true,
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
catch {
|
|
1281
|
+
// Interaction may have expired
|
|
1282
|
+
}
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
422
1285
|
try {
|
|
423
|
-
await interaction.
|
|
424
|
-
content: "✓",
|
|
425
|
-
ephemeral: true,
|
|
426
|
-
});
|
|
1286
|
+
await interaction.acknowledge();
|
|
427
1287
|
}
|
|
428
1288
|
catch (err) {
|
|
429
|
-
logError(`
|
|
1289
|
+
logError(`discord component modal: failed to acknowledge: ${String(err)}`);
|
|
430
1290
|
}
|
|
1291
|
+
const eventText = formatModalSubmissionText(consumed, interaction);
|
|
1292
|
+
await dispatchDiscordComponentEvent({
|
|
1293
|
+
ctx: this.ctx,
|
|
1294
|
+
interaction,
|
|
1295
|
+
interactionCtx,
|
|
1296
|
+
channelCtx,
|
|
1297
|
+
guildInfo,
|
|
1298
|
+
eventText,
|
|
1299
|
+
replyToId: consumed.messageId,
|
|
1300
|
+
routeOverrides: {
|
|
1301
|
+
sessionKey: consumed.sessionKey,
|
|
1302
|
+
agentId: consumed.agentId,
|
|
1303
|
+
accountId: consumed.accountId,
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
431
1306
|
}
|
|
432
1307
|
}
|
|
433
1308
|
export function createAgentComponentButton(ctx) {
|
|
@@ -436,3 +1311,24 @@ export function createAgentComponentButton(ctx) {
|
|
|
436
1311
|
export function createAgentSelectMenu(ctx) {
|
|
437
1312
|
return new AgentSelectMenu(ctx);
|
|
438
1313
|
}
|
|
1314
|
+
export function createDiscordComponentButton(ctx) {
|
|
1315
|
+
return new DiscordComponentButton(ctx);
|
|
1316
|
+
}
|
|
1317
|
+
export function createDiscordComponentStringSelect(ctx) {
|
|
1318
|
+
return new DiscordComponentStringSelect(ctx);
|
|
1319
|
+
}
|
|
1320
|
+
export function createDiscordComponentUserSelect(ctx) {
|
|
1321
|
+
return new DiscordComponentUserSelect(ctx);
|
|
1322
|
+
}
|
|
1323
|
+
export function createDiscordComponentRoleSelect(ctx) {
|
|
1324
|
+
return new DiscordComponentRoleSelect(ctx);
|
|
1325
|
+
}
|
|
1326
|
+
export function createDiscordComponentMentionableSelect(ctx) {
|
|
1327
|
+
return new DiscordComponentMentionableSelect(ctx);
|
|
1328
|
+
}
|
|
1329
|
+
export function createDiscordComponentChannelSelect(ctx) {
|
|
1330
|
+
return new DiscordComponentChannelSelect(ctx);
|
|
1331
|
+
}
|
|
1332
|
+
export function createDiscordComponentModal(ctx) {
|
|
1333
|
+
return new DiscordComponentModal(ctx);
|
|
1334
|
+
}
|