@openclaw/discord 2026.3.13 → 2026.5.1-beta.1
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/account-inspect-api.ts +6 -0
- package/action-runtime-api.ts +1 -0
- package/api.ts +132 -0
- package/channel-config-api.ts +1 -0
- package/channel-plugin-api.ts +3 -0
- package/config-api.ts +4 -0
- package/configured-state.ts +6 -0
- package/contract-api.ts +21 -0
- package/directory-contract-api.ts +4 -0
- package/doctor-contract-api.ts +1 -0
- package/index.test.ts +13 -0
- package/index.ts +18 -13
- package/openclaw.plugin.json +3282 -1
- package/package.json +67 -2
- package/runtime-api.actions.ts +15 -0
- package/runtime-api.lookup.ts +22 -0
- package/runtime-api.monitor.ts +50 -0
- package/runtime-api.send.ts +79 -0
- package/runtime-api.threads.ts +30 -0
- package/runtime-api.ts +180 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +4 -0
- package/security-audit-contract-api.ts +1 -0
- package/security-contract-api.ts +4 -0
- package/session-key-api.ts +1 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-inspect.test.ts +126 -0
- package/src/account-inspect.ts +132 -0
- package/src/accounts.test.ts +247 -0
- package/src/accounts.ts +196 -0
- package/src/actions/handle-action.guild-admin.ts +411 -0
- package/src/actions/handle-action.test.ts +185 -0
- package/src/actions/handle-action.ts +332 -0
- package/src/actions/runtime.guild.ts +446 -0
- package/src/actions/runtime.messaging.messages.ts +205 -0
- package/src/actions/runtime.messaging.reactions.ts +67 -0
- package/src/actions/runtime.messaging.runtime.ts +69 -0
- package/src/actions/runtime.messaging.send.ts +244 -0
- package/src/actions/runtime.messaging.shared.ts +92 -0
- package/src/actions/runtime.messaging.ts +37 -0
- package/src/actions/runtime.moderation-shared.ts +48 -0
- package/src/actions/runtime.moderation.authz.test.ts +151 -0
- package/src/actions/runtime.moderation.ts +116 -0
- package/src/actions/runtime.presence.test.ts +160 -0
- package/src/actions/runtime.presence.ts +117 -0
- package/src/actions/runtime.shared.ts +83 -0
- package/src/actions/runtime.test.ts +1056 -0
- package/src/actions/runtime.ts +81 -0
- package/src/api-barrel.test.ts +80 -0
- package/src/api.test.ts +130 -0
- package/src/api.ts +169 -0
- package/src/approval-handler.runtime.test.ts +41 -0
- package/src/approval-handler.runtime.ts +632 -0
- package/src/approval-native.test.ts +330 -0
- package/src/approval-native.ts +219 -0
- package/src/approval-runtime.ts +14 -0
- package/src/approval-shared.ts +53 -0
- package/src/audit-core.ts +141 -0
- package/src/audit.test.ts +145 -0
- package/src/audit.ts +32 -0
- package/src/channel-actions.contract.test.ts +45 -0
- package/src/channel-actions.runtime.ts +1 -0
- package/src/channel-actions.test.ts +236 -0
- package/src/channel-actions.ts +198 -0
- package/src/channel-api.ts +28 -0
- package/src/channel.conversation.ts +159 -0
- package/src/channel.loaders.ts +47 -0
- package/src/channel.runtime.ts +1 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.test.ts +539 -12
- package/src/channel.ts +596 -430
- package/src/chunk.test.ts +157 -0
- package/src/chunk.ts +321 -0
- package/src/client.proxy.test.ts +176 -0
- package/src/client.test.ts +76 -0
- package/src/client.ts +139 -0
- package/src/component-custom-id.ts +72 -0
- package/src/components-registry.ts +356 -0
- package/src/components.builders.ts +409 -0
- package/src/components.modal.ts +124 -0
- package/src/components.parse.ts +407 -0
- package/src/components.test.ts +312 -0
- package/src/components.ts +54 -0
- package/src/components.types.ts +187 -0
- package/src/config-schema.test.ts +325 -0
- package/src/config-schema.ts +6 -0
- package/src/config-ui-hints.ts +249 -0
- package/src/conversation-identity.ts +58 -0
- package/src/delivery-retry.ts +56 -0
- package/src/directory-cache.ts +116 -0
- package/src/directory-config.ts +58 -0
- package/src/directory-contract.test.ts +129 -0
- package/src/directory-live.test.ts +126 -0
- package/src/directory-live.ts +135 -0
- package/src/doctor-contract.ts +477 -0
- package/src/doctor-shared.ts +5 -0
- package/src/doctor.test.ts +405 -0
- package/src/doctor.ts +340 -0
- package/src/draft-chunking.test.ts +64 -0
- package/src/draft-chunking.ts +43 -0
- package/src/draft-stream.test.ts +159 -0
- package/src/draft-stream.ts +154 -0
- package/src/error-body.ts +38 -0
- package/src/exec-approvals.test.ts +88 -0
- package/src/exec-approvals.ts +110 -0
- package/src/gateway-logging.test.ts +98 -0
- package/src/gateway-logging.ts +67 -0
- package/src/group-policy.ts +113 -0
- package/src/guilds.ts +29 -0
- package/src/inbound-context.contract.test.ts +11 -0
- package/src/interactive-dispatch.ts +104 -0
- package/src/internal/api.commands.ts +51 -0
- package/src/internal/api.guild.ts +164 -0
- package/src/internal/api.interactions.ts +53 -0
- package/src/internal/api.messages.ts +113 -0
- package/src/internal/api.reactions.ts +38 -0
- package/src/internal/api.test.ts +262 -0
- package/src/internal/api.ts +61 -0
- package/src/internal/api.users.ts +19 -0
- package/src/internal/api.webhooks.ts +13 -0
- package/src/internal/client.test.ts +297 -0
- package/src/internal/client.ts +246 -0
- package/src/internal/command-deploy.ts +202 -0
- package/src/internal/commands.ts +188 -0
- package/src/internal/components.base.ts +65 -0
- package/src/internal/components.message.ts +279 -0
- package/src/internal/components.modal.ts +95 -0
- package/src/internal/components.ts +31 -0
- package/src/internal/discord.ts +11 -0
- package/src/internal/embeds.ts +35 -0
- package/src/internal/entity-cache.ts +98 -0
- package/src/internal/event-queue.ts +162 -0
- package/src/internal/gateway-close-codes.ts +25 -0
- package/src/internal/gateway-dispatch.ts +96 -0
- package/src/internal/gateway-identify-limiter.ts +26 -0
- package/src/internal/gateway-lifecycle.ts +61 -0
- package/src/internal/gateway-rate-limit.ts +104 -0
- package/src/internal/gateway.test.ts +475 -0
- package/src/internal/gateway.ts +437 -0
- package/src/internal/interaction-dispatch.test.ts +148 -0
- package/src/internal/interaction-dispatch.ts +130 -0
- package/src/internal/interaction-options.ts +98 -0
- package/src/internal/interaction-response.ts +53 -0
- package/src/internal/interactions.test.ts +253 -0
- package/src/internal/interactions.ts +337 -0
- package/src/internal/listeners.ts +85 -0
- package/src/internal/live-smoke.live.test.ts +26 -0
- package/src/internal/modal-fields.ts +95 -0
- package/src/internal/payload.ts +69 -0
- package/src/internal/rest-body.ts +115 -0
- package/src/internal/rest-errors.ts +88 -0
- package/src/internal/rest-routes.ts +50 -0
- package/src/internal/rest-scheduler.ts +412 -0
- package/src/internal/rest.test.ts +437 -0
- package/src/internal/rest.ts +213 -0
- package/src/internal/schemas.ts +36 -0
- package/src/internal/structures.ts +278 -0
- package/src/internal/test-builders.test-support.ts +163 -0
- package/src/internal/voice.ts +49 -0
- package/src/media-detection.ts +28 -0
- package/src/mentions.test.ts +111 -0
- package/src/mentions.ts +147 -0
- package/src/monitor/access-groups.ts +55 -0
- package/src/monitor/ack-reactions.ts +70 -0
- package/src/monitor/acp-bind-here.integration.test.ts +211 -0
- package/src/monitor/agent-components-auth.ts +7 -0
- package/src/monitor/agent-components-context.ts +144 -0
- package/src/monitor/agent-components-data.ts +224 -0
- package/src/monitor/agent-components-dm-auth.ts +221 -0
- package/src/monitor/agent-components-guild-auth.ts +322 -0
- package/src/monitor/agent-components-helpers.runtime.ts +5 -0
- package/src/monitor/agent-components-helpers.ts +34 -0
- package/src/monitor/agent-components-reply.ts +10 -0
- package/src/monitor/agent-components.deps.runtime.ts +2 -0
- package/src/monitor/agent-components.dispatch.ts +366 -0
- package/src/monitor/agent-components.handlers.ts +303 -0
- package/src/monitor/agent-components.modal.ts +160 -0
- package/src/monitor/agent-components.plugin-interactive.ts +187 -0
- package/src/monitor/agent-components.runtime.ts +14 -0
- package/src/monitor/agent-components.system-controls.ts +211 -0
- package/src/monitor/agent-components.ts +70 -0
- package/src/monitor/agent-components.types.ts +57 -0
- package/src/monitor/agent-components.wildcard-controls.ts +168 -0
- package/src/monitor/agent-components.wildcard.test.ts +71 -0
- package/src/monitor/allow-list.ts +623 -0
- package/src/monitor/auto-presence.test.ts +156 -0
- package/src/monitor/auto-presence.ts +356 -0
- package/src/monitor/channel-access.ts +70 -0
- package/src/monitor/commands.test.ts +24 -0
- package/src/monitor/commands.ts +9 -0
- package/src/monitor/dm-command-auth.test.ts +197 -0
- package/src/monitor/dm-command-auth.ts +158 -0
- package/src/monitor/dm-command-decision.test.ts +113 -0
- package/src/monitor/dm-command-decision.ts +49 -0
- package/src/monitor/exec-approvals.test.ts +226 -0
- package/src/monitor/exec-approvals.ts +158 -0
- package/src/monitor/format.ts +45 -0
- package/src/monitor/gateway-handle.ts +34 -0
- package/src/monitor/gateway-metadata.test.ts +29 -0
- package/src/monitor/gateway-metadata.ts +298 -0
- package/src/monitor/gateway-plugin.test.ts +297 -0
- package/src/monitor/gateway-plugin.ts +294 -0
- package/src/monitor/gateway-registry.ts +37 -0
- package/src/monitor/gateway-supervisor.test.ts +150 -0
- package/src/monitor/gateway-supervisor.ts +206 -0
- package/src/monitor/inbound-context.test-helpers.ts +37 -0
- package/src/monitor/inbound-context.test.ts +106 -0
- package/src/monitor/inbound-context.ts +103 -0
- package/src/monitor/inbound-dedupe.ts +79 -0
- package/src/monitor/inbound-job.test.ts +203 -0
- package/src/monitor/inbound-job.ts +118 -0
- package/src/monitor/listeners.queue.ts +91 -0
- package/src/monitor/listeners.reactions.ts +610 -0
- package/src/monitor/listeners.test.ts +200 -0
- package/src/monitor/listeners.ts +150 -0
- package/src/monitor/message-channel-info.ts +96 -0
- package/src/monitor/message-forwarded.ts +107 -0
- package/src/monitor/message-handler.batch-gate.test.ts +22 -0
- package/src/monitor/message-handler.batch-gate.ts +19 -0
- package/src/monitor/message-handler.bot-self-filter.test.ts +68 -0
- package/src/monitor/message-handler.context.ts +393 -0
- package/src/monitor/message-handler.dm-preflight.ts +123 -0
- package/src/monitor/message-handler.draft-preview.ts +246 -0
- package/src/monitor/message-handler.hydration.test.ts +80 -0
- package/src/monitor/message-handler.hydration.ts +198 -0
- package/src/monitor/message-handler.inbound-context.test.ts +59 -0
- package/src/monitor/message-handler.module-test-helpers.ts +31 -0
- package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
- package/src/monitor/message-handler.preflight-channel-context.ts +55 -0
- package/src/monitor/message-handler.preflight-context.ts +54 -0
- package/src/monitor/message-handler.preflight-helpers.ts +164 -0
- package/src/monitor/message-handler.preflight-history.ts +23 -0
- package/src/monitor/message-handler.preflight-logging.ts +36 -0
- package/src/monitor/message-handler.preflight-pluralkit.ts +27 -0
- package/src/monitor/message-handler.preflight-runtime.ts +28 -0
- package/src/monitor/message-handler.preflight-thread.ts +49 -0
- package/src/monitor/message-handler.preflight.acp-bindings.test.ts +369 -0
- package/src/monitor/message-handler.preflight.test-helpers.ts +111 -0
- package/src/monitor/message-handler.preflight.test.ts +1544 -0
- package/src/monitor/message-handler.preflight.ts +680 -0
- package/src/monitor/message-handler.preflight.types.ts +109 -0
- package/src/monitor/message-handler.process.test.ts +1301 -0
- package/src/monitor/message-handler.process.ts +684 -0
- package/src/monitor/message-handler.queue.test.ts +496 -0
- package/src/monitor/message-handler.routing-preflight.ts +112 -0
- package/src/monitor/message-handler.test-harness.ts +99 -0
- package/src/monitor/message-handler.test-helpers.ts +75 -0
- package/src/monitor/message-handler.ts +274 -0
- package/src/monitor/message-media.ts +507 -0
- package/src/monitor/message-run-queue.ts +101 -0
- package/src/monitor/message-text.ts +171 -0
- package/src/monitor/message-utils.test.ts +1151 -0
- package/src/monitor/message-utils.ts +32 -0
- package/src/monitor/model-picker-preferences.test.ts +67 -0
- package/src/monitor/model-picker-preferences.ts +184 -0
- package/src/monitor/model-picker.state.ts +364 -0
- package/src/monitor/model-picker.test-utils.ts +26 -0
- package/src/monitor/model-picker.test.ts +794 -0
- package/src/monitor/model-picker.ts +38 -0
- package/src/monitor/model-picker.view.ts +695 -0
- package/src/monitor/monitor.agent-components.test.ts +375 -0
- package/src/monitor/monitor.test.ts +849 -0
- package/src/monitor/monitor.threading-utils.test.ts +598 -0
- package/src/monitor/native-command-agent-reply.ts +123 -0
- package/src/monitor/native-command-arg-ui.ts +233 -0
- package/src/monitor/native-command-auth.ts +308 -0
- package/src/monitor/native-command-bypass.ts +13 -0
- package/src/monitor/native-command-context.test.ts +98 -0
- package/src/monitor/native-command-context.ts +103 -0
- package/src/monitor/native-command-dispatch.ts +35 -0
- package/src/monitor/native-command-model-picker-apply.ts +177 -0
- package/src/monitor/native-command-model-picker-interaction.ts +461 -0
- package/src/monitor/native-command-model-picker-ui.ts +368 -0
- package/src/monitor/native-command-reply.test.ts +68 -0
- package/src/monitor/native-command-reply.ts +183 -0
- package/src/monitor/native-command-route.ts +91 -0
- package/src/monitor/native-command-status.ts +76 -0
- package/src/monitor/native-command-ui.ts +26 -0
- package/src/monitor/native-command-ui.types.ts +20 -0
- package/src/monitor/native-command.args.ts +45 -0
- package/src/monitor/native-command.command-arg.test.ts +99 -0
- package/src/monitor/native-command.commands-allowfrom.test.ts +490 -0
- package/src/monitor/native-command.model-picker.test.ts +767 -0
- package/src/monitor/native-command.options.test.ts +369 -0
- package/src/monitor/native-command.options.ts +153 -0
- package/src/monitor/native-command.plugin-dispatch.test.ts +879 -0
- package/src/monitor/native-command.runtime.ts +50 -0
- package/src/monitor/native-command.status-direct.test.ts +272 -0
- package/src/monitor/native-command.test-helpers.ts +64 -0
- package/src/monitor/native-command.think-autocomplete.test.ts +416 -0
- package/src/monitor/native-command.ts +699 -0
- package/src/monitor/native-command.types.ts +9 -0
- package/src/monitor/native-interaction-channel-context.ts +50 -0
- package/src/monitor/preflight-audio.runtime.ts +9 -0
- package/src/monitor/preflight-audio.test.ts +157 -0
- package/src/monitor/preflight-audio.ts +130 -0
- package/src/monitor/presence-cache.ts +61 -0
- package/src/monitor/presence.test.ts +44 -0
- package/src/monitor/presence.ts +50 -0
- package/src/monitor/provider-session.runtime.ts +12 -0
- package/src/monitor/provider.acp.ts +89 -0
- package/src/monitor/provider.allowlist.test.ts +149 -0
- package/src/monitor/provider.allowlist.ts +394 -0
- package/src/monitor/provider.cleanup.ts +41 -0
- package/src/monitor/provider.commands.ts +129 -0
- package/src/monitor/provider.config-log.ts +45 -0
- package/src/monitor/provider.deploy-errors.ts +362 -0
- package/src/monitor/provider.deploy.ts +221 -0
- package/src/monitor/provider.interactions.ts +160 -0
- package/src/monitor/provider.lifecycle.test.ts +658 -0
- package/src/monitor/provider.lifecycle.ts +545 -0
- package/src/monitor/provider.proxy.test.ts +745 -0
- package/src/monitor/provider.rest-proxy.test.ts +121 -0
- package/src/monitor/provider.runtime.ts +1 -0
- package/src/monitor/provider.skill-dedupe.test.ts +42 -0
- package/src/monitor/provider.startup-log.ts +32 -0
- package/src/monitor/provider.startup.test.ts +426 -0
- package/src/monitor/provider.startup.ts +323 -0
- package/src/monitor/provider.test.ts +1111 -0
- package/src/monitor/provider.ts +713 -0
- package/src/monitor/reply-context.ts +64 -0
- package/src/monitor/reply-delivery.test.ts +244 -0
- package/src/monitor/reply-delivery.ts +203 -0
- package/src/monitor/rest-fetch.ts +43 -0
- package/src/monitor/route-resolution.test.ts +204 -0
- package/src/monitor/route-resolution.ts +140 -0
- package/src/monitor/sender-identity.ts +81 -0
- package/src/monitor/startup-status.test.ts +30 -0
- package/src/monitor/startup-status.ts +10 -0
- package/src/monitor/status.ts +22 -0
- package/src/monitor/system-events.ts +55 -0
- package/src/monitor/thread-bindings.config.ts +35 -0
- package/src/monitor/thread-bindings.discord-api.test.ts +229 -0
- package/src/monitor/thread-bindings.discord-api.ts +318 -0
- package/src/monitor/thread-bindings.lifecycle.test.ts +1871 -0
- package/src/monitor/thread-bindings.lifecycle.ts +354 -0
- package/src/monitor/thread-bindings.manager.ts +553 -0
- package/src/monitor/thread-bindings.messages.ts +6 -0
- package/src/monitor/thread-bindings.persona.test.ts +34 -0
- package/src/monitor/thread-bindings.persona.ts +25 -0
- package/src/monitor/thread-bindings.session-adapter.ts +229 -0
- package/src/monitor/thread-bindings.session-shared.ts +59 -0
- package/src/monitor/thread-bindings.session-updates.ts +35 -0
- package/src/monitor/thread-bindings.shared-state.test.ts +36 -0
- package/src/monitor/thread-bindings.state.ts +540 -0
- package/src/monitor/thread-bindings.ts +48 -0
- package/src/monitor/thread-bindings.types.ts +83 -0
- package/src/monitor/thread-channel-context.ts +112 -0
- package/src/monitor/thread-session-close.test.ts +180 -0
- package/src/monitor/thread-session-close.ts +63 -0
- package/src/monitor/thread-title.generate.test.ts +197 -0
- package/src/monitor/thread-title.test.ts +31 -0
- package/src/monitor/thread-title.ts +181 -0
- package/src/monitor/threading.auto-thread.test.ts +327 -0
- package/src/monitor/threading.auto-thread.ts +287 -0
- package/src/monitor/threading.cache.ts +45 -0
- package/src/monitor/threading.parent-info.test.ts +156 -0
- package/src/monitor/threading.starter.test.ts +260 -0
- package/src/monitor/threading.starter.ts +287 -0
- package/src/monitor/threading.ts +20 -0
- package/src/monitor/threading.types.ts +102 -0
- package/src/monitor/timeouts.ts +84 -0
- package/src/monitor/typing.test.ts +42 -0
- package/src/monitor/typing.ts +17 -0
- package/src/monitor.gateway.test.ts +187 -0
- package/src/monitor.gateway.ts +75 -0
- package/src/monitor.test.ts +1397 -0
- package/src/monitor.ts +28 -0
- package/src/normalize.test.ts +56 -0
- package/src/normalize.ts +86 -0
- package/src/outbound-adapter.interactive-order.test.ts +64 -0
- package/src/outbound-adapter.test-harness.ts +207 -0
- package/src/outbound-adapter.test.ts +696 -0
- package/src/outbound-adapter.ts +291 -0
- package/src/outbound-approval.ts +29 -0
- package/src/outbound-components.ts +81 -0
- package/src/outbound-payload.contract.test.ts +38 -0
- package/src/outbound-payload.ts +134 -0
- package/src/outbound-send-context.ts +92 -0
- package/src/outbound-session-route.test.ts +34 -0
- package/src/outbound-session-route.ts +72 -0
- package/src/pluralkit.test.ts +67 -0
- package/src/pluralkit.ts +58 -0
- package/src/preview-streaming.ts +32 -0
- package/src/probe.intents.test.ts +94 -0
- package/src/probe.parse-token.test.ts +43 -0
- package/src/probe.runtime.ts +1 -0
- package/src/probe.ts +237 -0
- package/src/proxy-fetch.ts +92 -0
- package/src/proxy-request-client.test.ts +78 -0
- package/src/proxy-request-client.ts +54 -0
- package/src/recipient-resolution.ts +39 -0
- package/src/resolve-allowlist-common.test.ts +36 -0
- package/src/resolve-allowlist-common.ts +39 -0
- package/src/resolve-channels.test.ts +340 -0
- package/src/resolve-channels.ts +369 -0
- package/src/resolve-users.test.ts +222 -0
- package/src/resolve-users.ts +184 -0
- package/src/retry.test.ts +83 -0
- package/src/retry.ts +98 -0
- package/src/runtime-api.ts +64 -0
- package/src/runtime.ts +22 -5
- package/src/secret-config-contract.ts +140 -0
- package/src/security-audit.runtime.ts +1 -0
- package/src/security-audit.test.ts +246 -0
- package/src/security-audit.ts +208 -0
- package/src/security-contract.ts +47 -0
- package/src/security-doctor.test.ts +25 -0
- package/src/security-doctor.ts +20 -0
- package/src/security.ts +60 -0
- package/src/send-target-parsing.ts +14 -0
- package/src/send.channels.ts +139 -0
- package/src/send.components.test.ts +275 -0
- package/src/send.components.ts +383 -0
- package/src/send.creates-thread.test.ts +643 -0
- package/src/send.emojis-stickers.ts +57 -0
- package/src/send.guild.ts +170 -0
- package/src/send.message-request.ts +97 -0
- package/src/send.messages.test.ts +53 -0
- package/src/send.messages.ts +225 -0
- package/src/send.outbound.ts +414 -0
- package/src/send.permissions.authz.test.ts +188 -0
- package/src/send.permissions.ts +283 -0
- package/src/send.reactions.ts +155 -0
- package/src/send.sends-basic-channel-messages.test.ts +919 -0
- package/src/send.shared.ts +445 -0
- package/src/send.test-harness.ts +56 -0
- package/src/send.ts +82 -0
- package/src/send.types.ts +188 -0
- package/src/send.typing.test.ts +41 -0
- package/src/send.typing.ts +9 -0
- package/src/send.voice.ts +134 -0
- package/src/send.webhook-activity.test.ts +105 -0
- package/src/send.webhook.proxy.test.ts +191 -0
- package/src/send.webhook.ts +133 -0
- package/src/session-contract.ts +3 -0
- package/src/session-key-normalization.test.ts +44 -0
- package/src/session-key-normalization.ts +47 -0
- package/src/setup-account-state.test.ts +91 -0
- package/src/setup-account-state.ts +144 -0
- package/src/setup-adapter.ts +12 -0
- package/src/setup-core.ts +180 -0
- package/src/setup-runtime-helpers.ts +10 -0
- package/src/setup-surface.test.ts +96 -0
- package/src/setup-surface.ts +129 -0
- package/src/shared-interactive.test.ts +153 -0
- package/src/shared-interactive.ts +124 -0
- package/src/shared.test.ts +159 -0
- package/src/shared.ts +190 -0
- package/src/status-issues.test.ts +70 -0
- package/src/status-issues.ts +169 -0
- package/src/subagent-hooks.test.ts +40 -44
- package/src/subagent-hooks.ts +185 -122
- package/src/target-parsing.ts +53 -0
- package/src/target-resolver.ts +129 -0
- package/src/targets.test.ts +367 -0
- package/src/targets.ts +12 -0
- package/src/test-http-helpers.ts +10 -0
- package/src/test-support/component-runtime.ts +190 -0
- package/src/test-support/config.ts +7 -0
- package/src/test-support/configured-binding-runtime.ts +29 -0
- package/src/test-support/partial-channel.ts +26 -0
- package/src/test-support/provider.test-support.ts +545 -0
- package/src/token.test.ts +107 -0
- package/src/token.ts +60 -0
- package/src/ui-colors.ts +27 -0
- package/src/ui.ts +20 -0
- package/src/voice/access.test.ts +217 -0
- package/src/voice/access.ts +124 -0
- package/src/voice/audio.ts +173 -0
- package/src/voice/capture-state.test.ts +48 -0
- package/src/voice/capture-state.ts +120 -0
- package/src/voice/command.test.ts +164 -0
- package/src/voice/command.ts +283 -0
- package/src/voice/config.ts +8 -0
- package/src/voice/manager.e2e.test.ts +928 -0
- package/src/voice/manager.ready-listener.test.ts +37 -0
- package/src/voice/manager.runtime.ts +11 -0
- package/src/voice/manager.ts +691 -0
- package/src/voice/prompt.test.ts +16 -0
- package/src/voice/prompt.ts +17 -0
- package/src/voice/receive-recovery.test.ts +79 -0
- package/src/voice/receive-recovery.ts +159 -0
- package/src/voice/sanitize.test.ts +34 -0
- package/src/voice/sanitize.ts +32 -0
- package/src/voice/sdk-runtime.ts +14 -0
- package/src/voice/segment.ts +156 -0
- package/src/voice/session.ts +50 -0
- package/src/voice/speaker-context.ts +127 -0
- package/src/voice/tts.ts +125 -0
- package/src/voice-message.test.ts +234 -0
- package/src/voice-message.ts +444 -0
- package/subagent-hooks-api.ts +27 -0
- package/test-api.ts +4 -0
- package/thread-binding-api.ts +1 -0
- package/timeouts.ts +6 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createAbortableFetchMock,
|
|
4
|
+
createJsonResponse,
|
|
5
|
+
} from "./internal/test-builders.test-support.js";
|
|
6
|
+
import { createDiscordRequestClient, DISCORD_REST_TIMEOUT_MS } from "./proxy-request-client.js";
|
|
7
|
+
|
|
8
|
+
describe("createDiscordRequestClient", () => {
|
|
9
|
+
it("preserves the REST client's abort signal for proxied fetch calls", async () => {
|
|
10
|
+
const fetchSpy = vi.fn(async (_input: string | URL | Request, init?: RequestInit) => {
|
|
11
|
+
expect(init?.signal).toBeDefined();
|
|
12
|
+
expect(init!.signal!.aborted).toBe(false);
|
|
13
|
+
return createJsonResponse([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const client = createDiscordRequestClient("Bot test-token", {
|
|
17
|
+
fetch: fetchSpy as never,
|
|
18
|
+
queueRequests: false,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await client.get("/channels/123/messages");
|
|
22
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("lets the REST client abort hanging proxied requests after its timeout", async () => {
|
|
26
|
+
const { fetch: fetchSpy } = createAbortableFetchMock();
|
|
27
|
+
|
|
28
|
+
const client = createDiscordRequestClient("Bot test-token", {
|
|
29
|
+
fetch: fetchSpy as never,
|
|
30
|
+
queueRequests: false,
|
|
31
|
+
timeout: 20,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await expect(client.get("/channels/123/messages")).rejects.toThrow();
|
|
35
|
+
}, 1_000);
|
|
36
|
+
|
|
37
|
+
it("lets abortAllRequests cancel active proxied fetches", async () => {
|
|
38
|
+
const abortable = createAbortableFetchMock();
|
|
39
|
+
|
|
40
|
+
const client = createDiscordRequestClient("Bot test-token", {
|
|
41
|
+
fetch: abortable.fetch as never,
|
|
42
|
+
queueRequests: false,
|
|
43
|
+
timeout: 5_000,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const request = client.get("/channels/123/messages");
|
|
47
|
+
await vi.waitFor(() => expect(abortable.fetch).toHaveBeenCalledTimes(1));
|
|
48
|
+
|
|
49
|
+
client.abortAllRequests();
|
|
50
|
+
|
|
51
|
+
await expect(request).rejects.toThrow();
|
|
52
|
+
expect(abortable.receivedSignal?.aborted).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("provides the REST client's timeout signal even without a caller signal", async () => {
|
|
56
|
+
let receivedSignal: AbortSignal | undefined;
|
|
57
|
+
|
|
58
|
+
const fetchSpy = vi.fn(async (_input: string | URL | Request, init?: RequestInit) => {
|
|
59
|
+
receivedSignal = init?.signal ?? undefined;
|
|
60
|
+
return createJsonResponse({});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const client = createDiscordRequestClient("Bot test-token", {
|
|
64
|
+
fetch: fetchSpy as never,
|
|
65
|
+
queueRequests: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await client.get("/channels/123/messages");
|
|
69
|
+
|
|
70
|
+
expect(receivedSignal).toBeDefined();
|
|
71
|
+
expect(receivedSignal!.aborted).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("exports a reasonable timeout constant", () => {
|
|
75
|
+
expect(DISCORD_REST_TIMEOUT_MS).toBeGreaterThanOrEqual(5_000);
|
|
76
|
+
expect(DISCORD_REST_TIMEOUT_MS).toBeLessThanOrEqual(30_000);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FormData as UndiciFormData } from "undici";
|
|
2
|
+
import { RequestClient, type RequestClientOptions } from "./internal/discord.js";
|
|
3
|
+
|
|
4
|
+
type ProxyRequestClientOptions = RequestClientOptions;
|
|
5
|
+
|
|
6
|
+
export const DISCORD_REST_TIMEOUT_MS = 15_000;
|
|
7
|
+
|
|
8
|
+
function toUndiciFormData(body: FormData): UndiciFormData {
|
|
9
|
+
const converted = new UndiciFormData();
|
|
10
|
+
for (const [key, value] of body.entries()) {
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
converted.append(key, value);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const filename = (value as Blob & { name?: unknown }).name;
|
|
16
|
+
if (typeof filename === "string" && filename.length > 0) {
|
|
17
|
+
converted.append(key, value, filename);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
converted.append(key, value);
|
|
21
|
+
}
|
|
22
|
+
return converted;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function wrapDiscordFetch(fetchImpl: NonNullable<RequestClientOptions["fetch"]>) {
|
|
26
|
+
return (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
27
|
+
if (init?.body instanceof FormData) {
|
|
28
|
+
// The proxy fetch path needs undici's FormData class to preserve multipart
|
|
29
|
+
// boundaries. Preserve the REST client's AbortController signal so timeout
|
|
30
|
+
// and abortAllRequests keep working.
|
|
31
|
+
return fetchImpl(input, {
|
|
32
|
+
...init,
|
|
33
|
+
body: toUndiciFormData(init.body) as unknown as BodyInit,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return fetchImpl(input, init);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createDiscordRequestClient(
|
|
41
|
+
token: string,
|
|
42
|
+
options?: ProxyRequestClientOptions,
|
|
43
|
+
): RequestClient {
|
|
44
|
+
if (!options?.fetch) {
|
|
45
|
+
return new RequestClient(token, options);
|
|
46
|
+
}
|
|
47
|
+
return new RequestClient(token, {
|
|
48
|
+
runtimeProfile: "persistent",
|
|
49
|
+
maxQueueSize: 1000,
|
|
50
|
+
timeout: DISCORD_REST_TIMEOUT_MS,
|
|
51
|
+
...options,
|
|
52
|
+
fetch: wrapDiscordFetch(options.fetch),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
+
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
3
|
+
import { resolveDiscordAccount } from "./accounts.js";
|
|
4
|
+
import { parseAndResolveDiscordTarget } from "./target-resolver.js";
|
|
5
|
+
import type { DiscordTargetParseOptions } from "./targets.js";
|
|
6
|
+
|
|
7
|
+
type DiscordRecipient =
|
|
8
|
+
| {
|
|
9
|
+
kind: "user";
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
| {
|
|
13
|
+
kind: "channel";
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function parseAndResolveRecipient(
|
|
18
|
+
raw: string,
|
|
19
|
+
cfg: OpenClawConfig,
|
|
20
|
+
accountId?: string,
|
|
21
|
+
parseOptions: DiscordTargetParseOptions = {},
|
|
22
|
+
): Promise<DiscordRecipient> {
|
|
23
|
+
if (!cfg) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Discord recipient resolution requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const resolvedCfg = requireRuntimeConfig(cfg, "Discord recipient resolution");
|
|
29
|
+
const accountInfo = resolveDiscordAccount({ cfg: resolvedCfg, accountId });
|
|
30
|
+
const resolved = await parseAndResolveDiscordTarget(
|
|
31
|
+
raw,
|
|
32
|
+
{
|
|
33
|
+
cfg: resolvedCfg,
|
|
34
|
+
accountId: accountInfo.accountId,
|
|
35
|
+
},
|
|
36
|
+
parseOptions,
|
|
37
|
+
);
|
|
38
|
+
return { kind: resolved.kind, id: resolved.id };
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildDiscordUnresolvedResults,
|
|
4
|
+
filterDiscordGuilds,
|
|
5
|
+
findDiscordGuildByName,
|
|
6
|
+
resolveDiscordAllowlistToken,
|
|
7
|
+
} from "./resolve-allowlist-common.js";
|
|
8
|
+
|
|
9
|
+
describe("resolve-allowlist-common", () => {
|
|
10
|
+
const guilds = [
|
|
11
|
+
{ id: "1", name: "Main Guild", slug: "main-guild" },
|
|
12
|
+
{ id: "2", name: "Ops Guild", slug: "ops-guild" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
it("resolves and filters guilds by id or name", () => {
|
|
16
|
+
expect(findDiscordGuildByName(guilds, "Main Guild")?.id).toBe("1");
|
|
17
|
+
expect(filterDiscordGuilds(guilds, { guildId: "2" })).toEqual([guilds[1]]);
|
|
18
|
+
expect(filterDiscordGuilds(guilds, { guildName: "main-guild" })).toEqual([guilds[0]]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("builds unresolved result rows in input order", () => {
|
|
22
|
+
const unresolved = buildDiscordUnresolvedResults(["a", "b"], (input) => ({
|
|
23
|
+
input,
|
|
24
|
+
resolved: false,
|
|
25
|
+
}));
|
|
26
|
+
expect(unresolved).toEqual([
|
|
27
|
+
{ input: "a", resolved: false },
|
|
28
|
+
{ input: "b", resolved: false },
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("normalizes allowlist token values", () => {
|
|
33
|
+
expect(resolveDiscordAllowlistToken(" discord-token ")).toBe("discord-token");
|
|
34
|
+
expect(resolveDiscordAllowlistToken("")).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DiscordGuildSummary } from "./guilds.js";
|
|
2
|
+
import { normalizeDiscordSlug } from "./monitor/allow-list.js";
|
|
3
|
+
import { normalizeDiscordToken } from "./token.js";
|
|
4
|
+
|
|
5
|
+
export function resolveDiscordAllowlistToken(token: string): string | undefined {
|
|
6
|
+
return normalizeDiscordToken(token, "channels.discord.token");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildDiscordUnresolvedResults<T extends { input: string; resolved: boolean }>(
|
|
10
|
+
entries: string[],
|
|
11
|
+
buildResult: (input: string) => T,
|
|
12
|
+
): T[] {
|
|
13
|
+
return entries.map((input) => buildResult(input));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function findDiscordGuildByName(
|
|
17
|
+
guilds: DiscordGuildSummary[],
|
|
18
|
+
input: string,
|
|
19
|
+
): DiscordGuildSummary | undefined {
|
|
20
|
+
const slug = normalizeDiscordSlug(input);
|
|
21
|
+
if (!slug) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return guilds.find((guild) => guild.slug === slug);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function filterDiscordGuilds(
|
|
28
|
+
guilds: DiscordGuildSummary[],
|
|
29
|
+
params: { guildId?: string; guildName?: string },
|
|
30
|
+
): DiscordGuildSummary[] {
|
|
31
|
+
if (params.guildId) {
|
|
32
|
+
return guilds.filter((guild) => guild.id === params.guildId);
|
|
33
|
+
}
|
|
34
|
+
if (params.guildName) {
|
|
35
|
+
const match = findDiscordGuildByName(guilds, params.guildName);
|
|
36
|
+
return match ? [match] : [];
|
|
37
|
+
}
|
|
38
|
+
return guilds;
|
|
39
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { withFetchPreconnect } from "openclaw/plugin-sdk/test-env";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { resolveDiscordChannelAllowlist } from "./resolve-channels.js";
|
|
4
|
+
import { jsonResponse, urlToString } from "./test-http-helpers.js";
|
|
5
|
+
|
|
6
|
+
describe("resolveDiscordChannelAllowlist", () => {
|
|
7
|
+
type DiscordChannel = { id: string; name: string; guild_id: string; type: number };
|
|
8
|
+
|
|
9
|
+
async function resolveWithChannelLookup(params: {
|
|
10
|
+
guilds: Array<{ id: string; name: string }>;
|
|
11
|
+
channel: DiscordChannel;
|
|
12
|
+
entry: string;
|
|
13
|
+
}) {
|
|
14
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
15
|
+
const url = urlToString(input);
|
|
16
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
17
|
+
return jsonResponse(params.guilds);
|
|
18
|
+
}
|
|
19
|
+
if (url.endsWith(`/channels/${params.channel.id}`)) {
|
|
20
|
+
return jsonResponse(params.channel);
|
|
21
|
+
}
|
|
22
|
+
return new Response("not found", { status: 404 });
|
|
23
|
+
});
|
|
24
|
+
return resolveDiscordChannelAllowlist({
|
|
25
|
+
token: "test",
|
|
26
|
+
entries: [params.entry],
|
|
27
|
+
fetcher,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function resolveGuild111Entry2024(params: {
|
|
32
|
+
channelLookup: () => Response;
|
|
33
|
+
guildChannels?: DiscordChannel[];
|
|
34
|
+
}) {
|
|
35
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
36
|
+
const url = urlToString(input);
|
|
37
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
38
|
+
return jsonResponse([{ id: "111", name: "Test Server" }]);
|
|
39
|
+
}
|
|
40
|
+
if (url.endsWith("/channels/2024")) {
|
|
41
|
+
return params.channelLookup();
|
|
42
|
+
}
|
|
43
|
+
if (url.endsWith("/guilds/111/channels")) {
|
|
44
|
+
return jsonResponse(
|
|
45
|
+
params.guildChannels ?? [
|
|
46
|
+
{ id: "c1", name: "2024", guild_id: "111", type: 0 },
|
|
47
|
+
{ id: "c2", name: "general", guild_id: "111", type: 0 },
|
|
48
|
+
],
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return new Response("not found", { status: 404 });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return resolveDiscordChannelAllowlist({
|
|
55
|
+
token: "test",
|
|
56
|
+
entries: ["111/2024"],
|
|
57
|
+
fetcher,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function expectUnresolved1112024(
|
|
62
|
+
res: Awaited<ReturnType<typeof resolveDiscordChannelAllowlist>>,
|
|
63
|
+
) {
|
|
64
|
+
expect(res[0]?.resolved).toBe(false);
|
|
65
|
+
expect(res[0]?.channelId).toBe("2024");
|
|
66
|
+
expect(res[0]?.guildId).toBe("111");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
it("resolves guild/channel by name", async () => {
|
|
70
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
71
|
+
const url = urlToString(input);
|
|
72
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
73
|
+
return jsonResponse([{ id: "g1", name: "My Guild" }]);
|
|
74
|
+
}
|
|
75
|
+
if (url.endsWith("/guilds/g1/channels")) {
|
|
76
|
+
return jsonResponse([
|
|
77
|
+
{ id: "c1", name: "general", guild_id: "g1", type: 0 },
|
|
78
|
+
{ id: "c2", name: "random", guild_id: "g1", type: 0 },
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
return new Response("not found", { status: 404 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
85
|
+
token: "test",
|
|
86
|
+
entries: ["My Guild/general"],
|
|
87
|
+
fetcher,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(res[0]?.resolved).toBe(true);
|
|
91
|
+
expect(res[0]?.guildId).toBe("g1");
|
|
92
|
+
expect(res[0]?.channelId).toBe("c1");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("resolves channel id to guild", async () => {
|
|
96
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
97
|
+
const url = urlToString(input);
|
|
98
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
99
|
+
return jsonResponse([{ id: "g1", name: "Guild One" }]);
|
|
100
|
+
}
|
|
101
|
+
if (url.endsWith("/channels/123")) {
|
|
102
|
+
return jsonResponse({ id: "123", name: "general", guild_id: "g1", type: 0 });
|
|
103
|
+
}
|
|
104
|
+
return new Response("not found", { status: 404 });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
108
|
+
token: "test",
|
|
109
|
+
entries: ["123"],
|
|
110
|
+
fetcher,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(res[0]?.resolved).toBe(true);
|
|
114
|
+
expect(res[0]?.guildId).toBe("g1");
|
|
115
|
+
expect(res[0]?.channelId).toBe("123");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("resolves guildId/channelId entries via channel lookup", async () => {
|
|
119
|
+
const res = await resolveWithChannelLookup({
|
|
120
|
+
guilds: [{ id: "111", name: "Guild One" }],
|
|
121
|
+
channel: { id: "222", name: "general", guild_id: "111", type: 0 },
|
|
122
|
+
entry: "111/222",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(res[0]).toMatchObject({
|
|
126
|
+
input: "111/222",
|
|
127
|
+
resolved: true,
|
|
128
|
+
guildId: "111",
|
|
129
|
+
channelId: "222",
|
|
130
|
+
channelName: "general",
|
|
131
|
+
guildName: "Guild One",
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("reports unresolved when channel id belongs to a different guild", async () => {
|
|
136
|
+
const res = await resolveWithChannelLookup({
|
|
137
|
+
guilds: [
|
|
138
|
+
{ id: "111", name: "Guild One" },
|
|
139
|
+
{ id: "333", name: "Guild Two" },
|
|
140
|
+
],
|
|
141
|
+
channel: { id: "222", name: "general", guild_id: "333", type: 0 },
|
|
142
|
+
entry: "111/222",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(res[0]).toMatchObject({
|
|
146
|
+
input: "111/222",
|
|
147
|
+
resolved: false,
|
|
148
|
+
guildId: "111",
|
|
149
|
+
guildName: "Guild One",
|
|
150
|
+
channelId: "222",
|
|
151
|
+
channelName: "general",
|
|
152
|
+
note: "channel belongs to guild Guild Two",
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("resolves numeric channel id when guild is specified by name", async () => {
|
|
157
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
158
|
+
const url = urlToString(input);
|
|
159
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
160
|
+
return jsonResponse([{ id: "111", name: "My Guild" }]);
|
|
161
|
+
}
|
|
162
|
+
if (url.endsWith("/guilds/111/channels")) {
|
|
163
|
+
return jsonResponse([{ id: "444555666", name: "general", guild_id: "111", type: 0 }]);
|
|
164
|
+
}
|
|
165
|
+
return new Response("not found", { status: 404 });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
169
|
+
token: "test",
|
|
170
|
+
entries: ["My Guild/444555666"],
|
|
171
|
+
fetcher,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(res[0]?.resolved).toBe(true);
|
|
175
|
+
expect(res[0]?.channelId).toBe("444555666");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("marks invalid numeric channelId as unresolved without aborting batch", async () => {
|
|
179
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
180
|
+
const url = urlToString(input);
|
|
181
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
182
|
+
return jsonResponse([{ id: "111", name: "Test Server" }]);
|
|
183
|
+
}
|
|
184
|
+
if (url.endsWith("/guilds/111/channels")) {
|
|
185
|
+
return jsonResponse([{ id: "444555666", name: "general", guild_id: "111", type: 0 }]);
|
|
186
|
+
}
|
|
187
|
+
if (url.endsWith("/channels/999000111")) {
|
|
188
|
+
return new Response("not found", { status: 404 });
|
|
189
|
+
}
|
|
190
|
+
if (url.endsWith("/channels/444555666")) {
|
|
191
|
+
return jsonResponse({
|
|
192
|
+
id: "444555666",
|
|
193
|
+
name: "general",
|
|
194
|
+
guild_id: "111",
|
|
195
|
+
type: 0,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return new Response("not found", { status: 404 });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
202
|
+
token: "test",
|
|
203
|
+
entries: ["111/999000111", "111/444555666"],
|
|
204
|
+
fetcher,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(res).toHaveLength(2);
|
|
208
|
+
expect(res[0]?.resolved).toBe(false);
|
|
209
|
+
expect(res[0]?.channelId).toBe("999000111");
|
|
210
|
+
expect(res[0]?.guildId).toBe("111");
|
|
211
|
+
expect(res[1]?.resolved).toBe(true);
|
|
212
|
+
expect(res[1]?.channelId).toBe("444555666");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("treats 403 channel lookup as unresolved without aborting batch", async () => {
|
|
216
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
217
|
+
const url = urlToString(input);
|
|
218
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
219
|
+
return jsonResponse([{ id: "111", name: "Test Server" }]);
|
|
220
|
+
}
|
|
221
|
+
if (url.endsWith("/guilds/111/channels")) {
|
|
222
|
+
return jsonResponse([{ id: "444555666", name: "general", guild_id: "111", type: 0 }]);
|
|
223
|
+
}
|
|
224
|
+
if (url.endsWith("/channels/777888999")) {
|
|
225
|
+
return new Response("Missing Access", { status: 403 });
|
|
226
|
+
}
|
|
227
|
+
if (url.endsWith("/channels/444555666")) {
|
|
228
|
+
return jsonResponse({
|
|
229
|
+
id: "444555666",
|
|
230
|
+
name: "general",
|
|
231
|
+
guild_id: "111",
|
|
232
|
+
type: 0,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return new Response("not found", { status: 404 });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
239
|
+
token: "test",
|
|
240
|
+
entries: ["111/777888999", "111/444555666"],
|
|
241
|
+
fetcher,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(res).toHaveLength(2);
|
|
245
|
+
expect(res[0]?.resolved).toBe(false);
|
|
246
|
+
expect(res[0]?.channelId).toBe("777888999");
|
|
247
|
+
expect(res[0]?.guildId).toBe("111");
|
|
248
|
+
expect(res[1]?.resolved).toBe(true);
|
|
249
|
+
expect(res[1]?.channelId).toBe("444555666");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("falls back to name matching when numeric channel name is not a valid ID", async () => {
|
|
253
|
+
const res = await resolveGuild111Entry2024({
|
|
254
|
+
channelLookup: () => new Response("not found", { status: 404 }),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(res[0]?.resolved).toBe(true);
|
|
258
|
+
expect(res[0]?.guildId).toBe("111");
|
|
259
|
+
expect(res[0]?.channelId).toBe("c1");
|
|
260
|
+
expect(res[0]?.channelName).toBe("2024");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("does not fall back to name matching when channel lookup returns 403", async () => {
|
|
264
|
+
const res = await resolveGuild111Entry2024({
|
|
265
|
+
channelLookup: () => new Response("Missing Access", { status: 403 }),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expectUnresolved1112024(res);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("does not fall back to name matching when channel payload is malformed", async () => {
|
|
272
|
+
const res = await resolveGuild111Entry2024({
|
|
273
|
+
channelLookup: () => jsonResponse({ id: "2024", name: "unknown", type: 0 }),
|
|
274
|
+
guildChannels: [{ id: "c1", name: "2024", guild_id: "111", type: 0 }],
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
expectUnresolved1112024(res);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("resolves guild: prefixed id as guild (not channel)", async () => {
|
|
281
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
282
|
+
const url = urlToString(input);
|
|
283
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
284
|
+
return jsonResponse([{ id: "111222333444555666", name: "Guild One" }]);
|
|
285
|
+
}
|
|
286
|
+
// Should never be called — if it is, the ID was misrouted as a channel
|
|
287
|
+
if (url.includes("/channels/")) {
|
|
288
|
+
throw new Error("guild id was incorrectly routed to /channels/");
|
|
289
|
+
}
|
|
290
|
+
return new Response("not found", { status: 404 });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
294
|
+
token: "test",
|
|
295
|
+
entries: ["guild:111222333444555666"],
|
|
296
|
+
fetcher,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(res[0]?.resolved).toBe(true);
|
|
300
|
+
expect(res[0]?.guildId).toBe("111222333444555666");
|
|
301
|
+
expect(res[0]?.channelId).toBeUndefined();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("bare numeric guild id is misrouted as channel id (regression)", async () => {
|
|
305
|
+
// Demonstrates why provider.ts must prefix guild-only entries with "guild:"
|
|
306
|
+
// In reality, Discord returns 404 when a guild ID is sent to /channels/<guildId>,
|
|
307
|
+
// which causes fetchDiscord to throw and the entire resolver to crash.
|
|
308
|
+
const fetcher = withFetchPreconnect(async (input: RequestInfo | URL) => {
|
|
309
|
+
const url = urlToString(input);
|
|
310
|
+
if (url.endsWith("/users/@me/guilds")) {
|
|
311
|
+
return jsonResponse([{ id: "999", name: "My Server" }]);
|
|
312
|
+
}
|
|
313
|
+
// Guild ID hitting /channels/ returns 404 — just like real Discord
|
|
314
|
+
if (url.includes("/channels/")) {
|
|
315
|
+
return new Response(JSON.stringify({ message: "Unknown Channel" }), { status: 404 });
|
|
316
|
+
}
|
|
317
|
+
return new Response("not found", { status: 404 });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Without the guild: prefix, a bare numeric string hits /channels/999 → 404 → unresolved
|
|
321
|
+
const res = await resolveDiscordChannelAllowlist({
|
|
322
|
+
token: "test",
|
|
323
|
+
entries: ["999"],
|
|
324
|
+
fetcher,
|
|
325
|
+
});
|
|
326
|
+
expect(res[0]?.resolved).toBe(false);
|
|
327
|
+
expect(res[0]?.channelId).toBe("999");
|
|
328
|
+
expect(res[0]?.guildId).toBeUndefined();
|
|
329
|
+
|
|
330
|
+
// With the guild: prefix, it correctly resolves as a guild (never hits /channels/)
|
|
331
|
+
const res2 = await resolveDiscordChannelAllowlist({
|
|
332
|
+
token: "test",
|
|
333
|
+
entries: ["guild:999"],
|
|
334
|
+
fetcher,
|
|
335
|
+
});
|
|
336
|
+
expect(res2[0]?.resolved).toBe(true);
|
|
337
|
+
expect(res2[0]?.guildId).toBe("999");
|
|
338
|
+
expect(res2[0]?.channelId).toBeUndefined();
|
|
339
|
+
});
|
|
340
|
+
});
|