@openclaw/discord 2026.3.13 → 2026.5.2-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 +3326 -1
- package/package.json +68 -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 +306 -0
- package/src/actions/handle-action.ts +372 -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 +248 -0
- package/src/actions/runtime.messaging.shared.ts +97 -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 +1087 -0
- package/src/actions/runtime.ts +87 -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 +275 -0
- package/src/channel-actions.ts +203 -0
- package/src/channel-api.ts +29 -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 +547 -12
- package/src/channel.ts +597 -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 +132 -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 +408 -0
- package/src/internal/client.ts +308 -0
- package/src/internal/command-deploy.ts +237 -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 +603 -0
- package/src/internal/gateway.ts +476 -0
- package/src/internal/interaction-dispatch.test.ts +148 -0
- package/src/internal/interaction-dispatch.ts +162 -0
- package/src/internal/interaction-options.ts +98 -0
- package/src/internal/interaction-response.ts +53 -0
- package/src/internal/interactions.test.ts +325 -0
- package/src/internal/interactions.ts +378 -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 +557 -0
- package/src/internal/rest.test.ts +673 -0
- package/src/internal/rest.ts +322 -0
- package/src/internal/schemas.ts +36 -0
- package/src/internal/structures.test.ts +43 -0
- package/src/internal/structures.ts +280 -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 +154 -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 +58 -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.test.ts +14 -0
- package/src/monitor/allow-list.ts +633 -0
- package/src/monitor/auto-presence.test.ts +156 -0
- package/src/monitor/auto-presence.ts +356 -0
- package/src/monitor/channel-access.test.ts +99 -0
- package/src/monitor/channel-access.ts +102 -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 +406 -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.test.ts +18 -0
- package/src/monitor/message-handler.preflight-channel-context.ts +58 -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 +26 -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 +1623 -0
- package/src/monitor/message-handler.preflight.ts +679 -0
- package/src/monitor/message-handler.preflight.types.ts +110 -0
- package/src/monitor/message-handler.process.test.ts +1369 -0
- package/src/monitor/message-handler.process.ts +686 -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 +509 -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 +1157 -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 +125 -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 +185 -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 +961 -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 +700 -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 +713 -0
- package/src/monitor/provider.lifecycle.ts +552 -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 +310 -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 +21 -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 +381 -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 +413 -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 +941 -0
- package/src/send.shared.ts +447 -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 +212 -0
- package/src/setup-runtime-helpers.ts +10 -0
- package/src/setup-surface.test.ts +137 -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 +165 -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 +130 -81
- package/src/subagent-hooks.ts +184 -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,298 @@
|
|
|
1
|
+
import type { APIGatewayBotInfo } from "discord-api-types/v10";
|
|
2
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
3
|
+
import { captureHttpExchange } from "openclaw/plugin-sdk/proxy-capture";
|
|
4
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
5
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
import { Check, Errors } from "typebox/value";
|
|
8
|
+
import { isDiscordRateLimitResponseBody, summarizeDiscordResponseBody } from "../error-body.js";
|
|
9
|
+
import { withAbortTimeout } from "./timeouts.js";
|
|
10
|
+
|
|
11
|
+
const DISCORD_GATEWAY_BOT_URL = "https://discord.com/api/v10/gateway/bot";
|
|
12
|
+
const DISCORD_API_HOST = "discord.com";
|
|
13
|
+
const DEFAULT_DISCORD_GATEWAY_URL = "wss://gateway.discord.gg/";
|
|
14
|
+
const DEFAULT_DISCORD_GATEWAY_INFO_TIMEOUT_MS = 30_000;
|
|
15
|
+
const MAX_DISCORD_GATEWAY_INFO_TIMEOUT_MS = 120_000;
|
|
16
|
+
const DISCORD_GATEWAY_INFO_TIMEOUT_ENV = "OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS";
|
|
17
|
+
const DISCORD_GATEWAY_METADATA_FALLBACK_LOG_INTERVAL_MS = 60_000;
|
|
18
|
+
|
|
19
|
+
type DiscordGatewayMetadataResponse = Pick<Response, "ok" | "status" | "text">;
|
|
20
|
+
export type DiscordGatewayFetchInit = Record<string, unknown> & {
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
export type DiscordGatewayFetch = (
|
|
24
|
+
input: string,
|
|
25
|
+
init?: DiscordGatewayFetchInit,
|
|
26
|
+
) => Promise<DiscordGatewayMetadataResponse>;
|
|
27
|
+
|
|
28
|
+
type DiscordGatewayMetadataError = Error & { transient?: boolean };
|
|
29
|
+
|
|
30
|
+
const discordGatewayBotInfoSchema = Type.Object({
|
|
31
|
+
url: Type.String({ minLength: 1 }),
|
|
32
|
+
shards: Type.Integer({ minimum: 1 }),
|
|
33
|
+
session_start_limit: Type.Object({
|
|
34
|
+
total: Type.Integer({ minimum: 0 }),
|
|
35
|
+
remaining: Type.Integer({ minimum: 0 }),
|
|
36
|
+
reset_after: Type.Number({ minimum: 0 }),
|
|
37
|
+
max_concurrency: Type.Integer({ minimum: 1 }),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const gatewayMetadataFallbackLogLastAt = new WeakMap<RuntimeEnv, number>();
|
|
42
|
+
|
|
43
|
+
function resolveFetchInputUrl(input: RequestInfo | URL): string {
|
|
44
|
+
if (typeof input === "string") {
|
|
45
|
+
return input;
|
|
46
|
+
}
|
|
47
|
+
if (input instanceof URL) {
|
|
48
|
+
return input.toString();
|
|
49
|
+
}
|
|
50
|
+
return input.url;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function materializeGuardedResponse(response: Response): Promise<Response> {
|
|
54
|
+
const body = await response.arrayBuffer();
|
|
55
|
+
return new Response(body, {
|
|
56
|
+
status: response.status,
|
|
57
|
+
statusText: response.statusText,
|
|
58
|
+
headers: response.headers,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeGatewayInfoTimeoutMs(value: unknown): number | undefined {
|
|
63
|
+
const numeric =
|
|
64
|
+
typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
65
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return Math.min(Math.floor(numeric), MAX_DISCORD_GATEWAY_INFO_TIMEOUT_MS);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveDiscordGatewayInfoTimeoutMs(params?: {
|
|
72
|
+
configuredTimeoutMs?: number;
|
|
73
|
+
env?: NodeJS.ProcessEnv;
|
|
74
|
+
}): number {
|
|
75
|
+
return (
|
|
76
|
+
normalizeGatewayInfoTimeoutMs(params?.configuredTimeoutMs) ??
|
|
77
|
+
normalizeGatewayInfoTimeoutMs(params?.env?.[DISCORD_GATEWAY_INFO_TIMEOUT_ENV]) ??
|
|
78
|
+
DEFAULT_DISCORD_GATEWAY_INFO_TIMEOUT_MS
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function summarizeGatewayResponseBody(body: string): string {
|
|
83
|
+
return summarizeDiscordResponseBody(body, { emptyText: "<empty>" }) ?? "<empty>";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isDiscordGatewayRateLimitResponse(status: number, body: string): boolean {
|
|
87
|
+
return status === 429 && isDiscordRateLimitResponseBody(body);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isTransientDiscordGatewayResponse(status: number, body: string): boolean {
|
|
91
|
+
if (status >= 500) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (isDiscordGatewayRateLimitResponse(status, body)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const normalized = body.toLowerCase();
|
|
98
|
+
return (
|
|
99
|
+
normalized.includes("upstream connect error") ||
|
|
100
|
+
normalized.includes("disconnect/reset before headers") ||
|
|
101
|
+
normalized.includes("reset reason:")
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createGatewayMetadataError(params: {
|
|
106
|
+
detail: string;
|
|
107
|
+
transient: boolean;
|
|
108
|
+
cause?: unknown;
|
|
109
|
+
}): Error {
|
|
110
|
+
const error = new Error(
|
|
111
|
+
params.transient
|
|
112
|
+
? "Failed to get gateway information from Discord: fetch failed"
|
|
113
|
+
: `Failed to get gateway information from Discord: ${params.detail}`,
|
|
114
|
+
{
|
|
115
|
+
cause: params.cause ?? (params.transient ? new Error(params.detail) : undefined),
|
|
116
|
+
},
|
|
117
|
+
) as DiscordGatewayMetadataError;
|
|
118
|
+
Object.defineProperty(error, "transient", {
|
|
119
|
+
value: params.transient,
|
|
120
|
+
enumerable: false,
|
|
121
|
+
});
|
|
122
|
+
return error;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isTransientGatewayMetadataError(error: unknown): boolean {
|
|
126
|
+
return Boolean((error as DiscordGatewayMetadataError | undefined)?.transient);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createDefaultGatewayInfo(): APIGatewayBotInfo {
|
|
130
|
+
return {
|
|
131
|
+
url: DEFAULT_DISCORD_GATEWAY_URL,
|
|
132
|
+
shards: 1,
|
|
133
|
+
session_start_limit: {
|
|
134
|
+
total: 1,
|
|
135
|
+
remaining: 1,
|
|
136
|
+
reset_after: 0,
|
|
137
|
+
max_concurrency: 1,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function summarizeGatewaySchemaErrors(value: unknown): string {
|
|
143
|
+
const errors = Errors(discordGatewayBotInfoSchema, value);
|
|
144
|
+
if (errors.length === 0) {
|
|
145
|
+
return "unknown schema mismatch";
|
|
146
|
+
}
|
|
147
|
+
return errors
|
|
148
|
+
.slice(0, 3)
|
|
149
|
+
.map((error) => `${error.instancePath || "/"} ${error.message}`)
|
|
150
|
+
.join("; ");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function parseDiscordGatewayInfoBody(body: string): APIGatewayBotInfo {
|
|
154
|
+
const parsed = JSON.parse(body) as unknown;
|
|
155
|
+
if (!Check(discordGatewayBotInfoSchema, parsed)) {
|
|
156
|
+
throw new Error(summarizeGatewaySchemaErrors(parsed));
|
|
157
|
+
}
|
|
158
|
+
return parsed;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function fetchDiscordGatewayInfo(params: {
|
|
162
|
+
token: string;
|
|
163
|
+
fetchImpl: DiscordGatewayFetch;
|
|
164
|
+
fetchInit?: DiscordGatewayFetchInit;
|
|
165
|
+
}): Promise<APIGatewayBotInfo> {
|
|
166
|
+
let response: DiscordGatewayMetadataResponse;
|
|
167
|
+
try {
|
|
168
|
+
response = await params.fetchImpl(DISCORD_GATEWAY_BOT_URL, {
|
|
169
|
+
...params.fetchInit,
|
|
170
|
+
headers: {
|
|
171
|
+
...params.fetchInit?.headers,
|
|
172
|
+
Authorization: `Bot ${params.token}`,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw createGatewayMetadataError({
|
|
177
|
+
detail: formatErrorMessage(error),
|
|
178
|
+
transient: true,
|
|
179
|
+
cause: error,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let body: string;
|
|
184
|
+
try {
|
|
185
|
+
body = await response.text();
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw createGatewayMetadataError({
|
|
188
|
+
detail: formatErrorMessage(error),
|
|
189
|
+
transient: true,
|
|
190
|
+
cause: error,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const summary = summarizeGatewayResponseBody(body);
|
|
194
|
+
const transient = isTransientDiscordGatewayResponse(response.status, body);
|
|
195
|
+
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
throw createGatewayMetadataError({
|
|
198
|
+
detail: `Discord API /gateway/bot failed (${response.status}): ${summary}`,
|
|
199
|
+
transient,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
return parseDiscordGatewayInfoBody(body);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw createGatewayMetadataError({
|
|
207
|
+
detail: `Discord API /gateway/bot returned invalid metadata: ${formatErrorMessage(error)} (${summary})`,
|
|
208
|
+
transient,
|
|
209
|
+
cause: error,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function fetchDiscordGatewayInfoWithTimeout(params: {
|
|
215
|
+
token: string;
|
|
216
|
+
fetchImpl: DiscordGatewayFetch;
|
|
217
|
+
fetchInit?: DiscordGatewayFetchInit;
|
|
218
|
+
timeoutMs?: number;
|
|
219
|
+
}): Promise<APIGatewayBotInfo> {
|
|
220
|
+
const timeoutMs = Math.max(1, params.timeoutMs ?? DEFAULT_DISCORD_GATEWAY_INFO_TIMEOUT_MS);
|
|
221
|
+
return await withAbortTimeout({
|
|
222
|
+
timeoutMs,
|
|
223
|
+
createTimeoutError: () =>
|
|
224
|
+
createGatewayMetadataError({
|
|
225
|
+
detail: `Discord API /gateway/bot timed out after ${timeoutMs}ms`,
|
|
226
|
+
transient: true,
|
|
227
|
+
cause: new Error("gateway metadata timeout"),
|
|
228
|
+
}),
|
|
229
|
+
run: async (signal) =>
|
|
230
|
+
await fetchDiscordGatewayInfo({
|
|
231
|
+
token: params.token,
|
|
232
|
+
fetchImpl: params.fetchImpl,
|
|
233
|
+
fetchInit: {
|
|
234
|
+
...params.fetchInit,
|
|
235
|
+
signal,
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function resolveGatewayInfoWithFallback(params: { runtime?: RuntimeEnv; error: unknown }): {
|
|
242
|
+
info: APIGatewayBotInfo;
|
|
243
|
+
usedFallback: boolean;
|
|
244
|
+
} {
|
|
245
|
+
if (!isTransientGatewayMetadataError(params.error)) {
|
|
246
|
+
throw params.error;
|
|
247
|
+
}
|
|
248
|
+
const message = formatErrorMessage(params.error);
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
if (params.runtime) {
|
|
251
|
+
const previous = gatewayMetadataFallbackLogLastAt.get(params.runtime);
|
|
252
|
+
if (
|
|
253
|
+
previous === undefined ||
|
|
254
|
+
now - previous >= DISCORD_GATEWAY_METADATA_FALLBACK_LOG_INTERVAL_MS
|
|
255
|
+
) {
|
|
256
|
+
params.runtime.log?.(
|
|
257
|
+
`discord: gateway metadata lookup failed transiently; using default gateway url (${message})`,
|
|
258
|
+
);
|
|
259
|
+
gatewayMetadataFallbackLogLastAt.set(params.runtime, now);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
info: createDefaultGatewayInfo(),
|
|
264
|
+
usedFallback: true,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function fetchDiscordGatewayMetadataDirect(
|
|
269
|
+
input: string,
|
|
270
|
+
init?: DiscordGatewayFetchInit,
|
|
271
|
+
capture?: false | { flowId: string; meta: Record<string, unknown> },
|
|
272
|
+
): Promise<Response> {
|
|
273
|
+
const guarded = await fetchWithSsrFGuard({
|
|
274
|
+
url: resolveFetchInputUrl(input),
|
|
275
|
+
init: init as RequestInit,
|
|
276
|
+
policy: { allowedHostnames: [DISCORD_API_HOST] },
|
|
277
|
+
capture: false,
|
|
278
|
+
auditContext: "discord.gateway.metadata",
|
|
279
|
+
});
|
|
280
|
+
let response: Response;
|
|
281
|
+
try {
|
|
282
|
+
response = await materializeGuardedResponse(guarded.response);
|
|
283
|
+
} finally {
|
|
284
|
+
await guarded.release();
|
|
285
|
+
}
|
|
286
|
+
if (capture) {
|
|
287
|
+
captureHttpExchange({
|
|
288
|
+
url: input,
|
|
289
|
+
method: (init?.method as string | undefined) ?? "GET",
|
|
290
|
+
requestHeaders: init?.headers as Headers | Record<string, string> | undefined,
|
|
291
|
+
requestBody: (init as RequestInit & { body?: BodyInit | null })?.body ?? null,
|
|
292
|
+
response,
|
|
293
|
+
flowId: capture.flowId,
|
|
294
|
+
meta: capture.meta,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return response;
|
|
298
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT } from "./gateway-handle.js";
|
|
4
|
+
|
|
5
|
+
const { GatewayIntents, GatewayPlugin } = vi.hoisted(() => {
|
|
6
|
+
const GatewayIntents = {
|
|
7
|
+
Guilds: 1 << 0,
|
|
8
|
+
GuildMessages: 1 << 1,
|
|
9
|
+
MessageContent: 1 << 2,
|
|
10
|
+
DirectMessages: 1 << 3,
|
|
11
|
+
GuildMessageReactions: 1 << 4,
|
|
12
|
+
DirectMessageReactions: 1 << 5,
|
|
13
|
+
GuildPresences: 1 << 6,
|
|
14
|
+
GuildMembers: 1 << 7,
|
|
15
|
+
GuildVoiceStates: 1 << 8,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
class TestEmitter {
|
|
19
|
+
private readonly listenersByEvent = new Map<string, Array<(value: unknown) => void>>();
|
|
20
|
+
|
|
21
|
+
on(event: string, listener: (value: unknown) => void) {
|
|
22
|
+
const listeners = this.listenersByEvent.get(event) ?? [];
|
|
23
|
+
listeners.push(listener);
|
|
24
|
+
this.listenersByEvent.set(event, listeners);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
emit(event: string, value: unknown) {
|
|
28
|
+
for (const listener of this.listenersByEvent.get(event) ?? []) {
|
|
29
|
+
listener(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class GatewayPlugin {
|
|
35
|
+
options: unknown;
|
|
36
|
+
gatewayInfo: unknown;
|
|
37
|
+
emitter = new TestEmitter();
|
|
38
|
+
isConnecting: boolean = false;
|
|
39
|
+
heartbeatInterval?: NodeJS.Timeout;
|
|
40
|
+
firstHeartbeatTimeout?: NodeJS.Timeout;
|
|
41
|
+
ws?: unknown;
|
|
42
|
+
|
|
43
|
+
constructor(options?: unknown) {
|
|
44
|
+
this.options = options;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async registerClient(_client: unknown): Promise<void> {}
|
|
48
|
+
|
|
49
|
+
connect(_resume = false): void {
|
|
50
|
+
if (this.isConnecting) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { GatewayIntents, GatewayPlugin };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
vi.mock("../internal/gateway.js", () => ({
|
|
60
|
+
GatewayIntents,
|
|
61
|
+
GatewayPlugin,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
vi.mock("openclaw/plugin-sdk/proxy-capture", () => ({
|
|
65
|
+
captureHttpExchange: vi.fn(),
|
|
66
|
+
captureWsEvent: vi.fn(),
|
|
67
|
+
resolveEffectiveDebugProxyUrl: () => undefined,
|
|
68
|
+
resolveDebugProxySettings: () => ({ enabled: false }),
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
|
72
|
+
danger: (value: string) => value,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
describe("createDiscordGatewayPlugin", () => {
|
|
76
|
+
let createDiscordGatewayPlugin: typeof import("./gateway-plugin.js").createDiscordGatewayPlugin;
|
|
77
|
+
let parseDiscordGatewayInfoBody: typeof import("./gateway-plugin.js").parseDiscordGatewayInfoBody;
|
|
78
|
+
let resolveDiscordGatewayIntents: typeof import("./gateway-plugin.js").resolveDiscordGatewayIntents;
|
|
79
|
+
let resolveDiscordGatewayInfoTimeoutMs: typeof import("./gateway-plugin.js").resolveDiscordGatewayInfoTimeoutMs;
|
|
80
|
+
|
|
81
|
+
beforeAll(async () => {
|
|
82
|
+
({
|
|
83
|
+
createDiscordGatewayPlugin,
|
|
84
|
+
parseDiscordGatewayInfoBody,
|
|
85
|
+
resolveDiscordGatewayIntents,
|
|
86
|
+
resolveDiscordGatewayInfoTimeoutMs,
|
|
87
|
+
} = await import("./gateway-plugin.js"));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function createPlugin(
|
|
91
|
+
testing?: NonNullable<Parameters<typeof createDiscordGatewayPlugin>[0]["__testing"]>,
|
|
92
|
+
discordConfig: Parameters<typeof createDiscordGatewayPlugin>[0]["discordConfig"] = {},
|
|
93
|
+
) {
|
|
94
|
+
return createDiscordGatewayPlugin({
|
|
95
|
+
discordConfig,
|
|
96
|
+
runtime: {
|
|
97
|
+
log: vi.fn(),
|
|
98
|
+
error: vi.fn(),
|
|
99
|
+
exit: vi.fn(),
|
|
100
|
+
},
|
|
101
|
+
...(testing ? { __testing: testing } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
it("omits GuildVoiceStates by default for text-only Discord configs", () => {
|
|
106
|
+
expect(resolveDiscordGatewayIntents() & GatewayIntents.GuildVoiceStates).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("includes GuildVoiceStates when voice is enabled", () => {
|
|
110
|
+
const intents = resolveDiscordGatewayIntents({ voiceEnabled: true });
|
|
111
|
+
|
|
112
|
+
expect(intents & GatewayIntents.GuildVoiceStates).toBe(GatewayIntents.GuildVoiceStates);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("omits GuildVoiceStates when voice is disabled", () => {
|
|
116
|
+
const intents = resolveDiscordGatewayIntents({ voiceEnabled: false });
|
|
117
|
+
|
|
118
|
+
expect(intents & GatewayIntents.GuildVoiceStates).toBe(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("lets intents.voiceStates override voice enablement", () => {
|
|
122
|
+
const enabled = resolveDiscordGatewayIntents({
|
|
123
|
+
intentsConfig: { voiceStates: true },
|
|
124
|
+
voiceEnabled: false,
|
|
125
|
+
});
|
|
126
|
+
const disabled = resolveDiscordGatewayIntents({
|
|
127
|
+
intentsConfig: { voiceStates: false },
|
|
128
|
+
voiceEnabled: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(enabled & GatewayIntents.GuildVoiceStates).toBe(GatewayIntents.GuildVoiceStates);
|
|
132
|
+
expect(disabled & GatewayIntents.GuildVoiceStates).toBe(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("includes optional configured privileged intents", () => {
|
|
136
|
+
const intents = resolveDiscordGatewayIntents({
|
|
137
|
+
intentsConfig: { presence: true, guildMembers: true },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(intents & GatewayIntents.GuildPresences).toBe(GatewayIntents.GuildPresences);
|
|
141
|
+
expect(intents & GatewayIntents.GuildMembers).toBe(GatewayIntents.GuildMembers);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("resolves gateway metadata timeout from config, env, then default", () => {
|
|
145
|
+
expect(resolveDiscordGatewayInfoTimeoutMs({ configuredTimeoutMs: 45_000 })).toBe(45_000);
|
|
146
|
+
expect(
|
|
147
|
+
resolveDiscordGatewayInfoTimeoutMs({
|
|
148
|
+
env: { OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS: "25000" },
|
|
149
|
+
}),
|
|
150
|
+
).toBe(25_000);
|
|
151
|
+
expect(resolveDiscordGatewayInfoTimeoutMs({ env: {} })).toBe(30_000);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("parses valid Discord gateway metadata", () => {
|
|
155
|
+
expect(
|
|
156
|
+
parseDiscordGatewayInfoBody(
|
|
157
|
+
JSON.stringify({
|
|
158
|
+
url: "wss://gateway.discord.gg",
|
|
159
|
+
shards: 1,
|
|
160
|
+
session_start_limit: {
|
|
161
|
+
total: 1000,
|
|
162
|
+
remaining: 999,
|
|
163
|
+
reset_after: 0,
|
|
164
|
+
max_concurrency: 1,
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
),
|
|
168
|
+
).toEqual({
|
|
169
|
+
url: "wss://gateway.discord.gg",
|
|
170
|
+
shards: 1,
|
|
171
|
+
session_start_limit: {
|
|
172
|
+
total: 1000,
|
|
173
|
+
remaining: 999,
|
|
174
|
+
reset_after: 0,
|
|
175
|
+
max_concurrency: 1,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("rejects malformed Discord gateway metadata", () => {
|
|
181
|
+
expect(() =>
|
|
182
|
+
parseDiscordGatewayInfoBody(
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
url: "",
|
|
185
|
+
shards: 0,
|
|
186
|
+
session_start_limit: {
|
|
187
|
+
total: 1000,
|
|
188
|
+
remaining: 999,
|
|
189
|
+
reset_after: 0,
|
|
190
|
+
max_concurrency: 1,
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
),
|
|
194
|
+
).toThrow(/url|shards/);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("omits voice states when Discord voice is disabled in account config", () => {
|
|
198
|
+
const plugin = createPlugin(undefined, { voice: { enabled: false } });
|
|
199
|
+
const options = (plugin as unknown as { options?: { intents?: number } }).options;
|
|
200
|
+
|
|
201
|
+
expect((options?.intents ?? 0) & GatewayIntents.GuildVoiceStates).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("omits voice states when Discord voice config is absent", () => {
|
|
205
|
+
const plugin = createPlugin(undefined, {});
|
|
206
|
+
const options = (plugin as unknown as { options?: { intents?: number } }).options;
|
|
207
|
+
|
|
208
|
+
expect((options?.intents ?? 0) & GatewayIntents.GuildVoiceStates).toBe(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("keeps voice states for existing Discord voice config blocks", () => {
|
|
212
|
+
const plugin = createPlugin(undefined, { voice: {} });
|
|
213
|
+
const options = (plugin as unknown as { options?: { intents?: number } }).options;
|
|
214
|
+
|
|
215
|
+
expect((options?.intents ?? 0) & GatewayIntents.GuildVoiceStates).toBe(
|
|
216
|
+
GatewayIntents.GuildVoiceStates,
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("leaves autoInteractions disabled so OpenClaw owns interaction handoff", () => {
|
|
221
|
+
const plugin = createPlugin();
|
|
222
|
+
|
|
223
|
+
expect((plugin as unknown as { options?: { autoInteractions?: boolean } }).options).toEqual(
|
|
224
|
+
expect.objectContaining({ autoInteractions: false }),
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("keeps OpenClaw metadata timeout out of gateway options", () => {
|
|
229
|
+
const plugin = createDiscordGatewayPlugin({
|
|
230
|
+
discordConfig: { gatewayInfoTimeoutMs: 5_000 },
|
|
231
|
+
runtime: {
|
|
232
|
+
log: vi.fn(),
|
|
233
|
+
error: vi.fn(),
|
|
234
|
+
exit: vi.fn(),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(
|
|
239
|
+
(plugin as unknown as { options?: { gatewayInfoTimeoutMs?: number } }).options
|
|
240
|
+
?.gatewayInfoTimeoutMs,
|
|
241
|
+
).toBeUndefined();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("emits transport activity for current gateway socket messages", () => {
|
|
245
|
+
const socket = new EventEmitter() as EventEmitter & { binaryType?: string };
|
|
246
|
+
const plugin = createPlugin({
|
|
247
|
+
webSocketCtor: function WebSocketCtor() {
|
|
248
|
+
return socket;
|
|
249
|
+
} as unknown as NonNullable<
|
|
250
|
+
Parameters<typeof createDiscordGatewayPlugin>[0]["__testing"]
|
|
251
|
+
>["webSocketCtor"],
|
|
252
|
+
});
|
|
253
|
+
const activitySpy = vi.fn();
|
|
254
|
+
(
|
|
255
|
+
plugin as unknown as {
|
|
256
|
+
emitter: { on: (event: string, listener: (value: unknown) => void) => void };
|
|
257
|
+
}
|
|
258
|
+
).emitter.on(DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT, activitySpy);
|
|
259
|
+
|
|
260
|
+
const createdSocket = (
|
|
261
|
+
plugin as unknown as { createWebSocket: (url: string) => typeof socket }
|
|
262
|
+
).createWebSocket("wss://gateway.discord.gg");
|
|
263
|
+
(plugin as unknown as { ws: unknown }).ws = createdSocket;
|
|
264
|
+
|
|
265
|
+
createdSocket.emit("message", Buffer.from("{}"));
|
|
266
|
+
|
|
267
|
+
expect(activitySpy).toHaveBeenCalledWith({ at: expect.any(Number) });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("ignores messages from stale gateway sockets", () => {
|
|
271
|
+
const staleSocket = new EventEmitter() as EventEmitter & { binaryType?: string };
|
|
272
|
+
const currentSocket = new EventEmitter();
|
|
273
|
+
const plugin = createPlugin({
|
|
274
|
+
webSocketCtor: function WebSocketCtor() {
|
|
275
|
+
return staleSocket;
|
|
276
|
+
} as unknown as NonNullable<
|
|
277
|
+
Parameters<typeof createDiscordGatewayPlugin>[0]["__testing"]
|
|
278
|
+
>["webSocketCtor"],
|
|
279
|
+
});
|
|
280
|
+
const activitySpy = vi.fn();
|
|
281
|
+
(
|
|
282
|
+
plugin as unknown as {
|
|
283
|
+
emitter: { on: (event: string, listener: (value: unknown) => void) => void };
|
|
284
|
+
}
|
|
285
|
+
).emitter.on(DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT, activitySpy);
|
|
286
|
+
|
|
287
|
+
const createdSocket = (
|
|
288
|
+
plugin as unknown as { createWebSocket: (url: string) => typeof staleSocket }
|
|
289
|
+
).createWebSocket("wss://gateway.discord.gg");
|
|
290
|
+
expect(createdSocket).toBe(staleSocket);
|
|
291
|
+
(plugin as unknown as { ws: unknown }).ws = currentSocket;
|
|
292
|
+
|
|
293
|
+
staleSocket.emit("message", Buffer.from("{}"));
|
|
294
|
+
|
|
295
|
+
expect(activitySpy).not.toHaveBeenCalled();
|
|
296
|
+
});
|
|
297
|
+
});
|