@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,325 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DiscordConfigSchema } from "../config-api.js";
|
|
3
|
+
|
|
4
|
+
function expectValidDiscordConfig(config: unknown) {
|
|
5
|
+
const res = DiscordConfigSchema.safeParse(config);
|
|
6
|
+
expect(res.success).toBe(true);
|
|
7
|
+
if (!res.success) {
|
|
8
|
+
throw new Error("expected Discord config to be valid");
|
|
9
|
+
}
|
|
10
|
+
return res.data;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function expectInvalidDiscordConfig(config: unknown) {
|
|
14
|
+
const res = DiscordConfigSchema.safeParse(config);
|
|
15
|
+
expect(res.success).toBe(false);
|
|
16
|
+
if (res.success) {
|
|
17
|
+
throw new Error("expected Discord config to be invalid");
|
|
18
|
+
}
|
|
19
|
+
return res.error.issues;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("discord config schema", () => {
|
|
23
|
+
it('rejects dmPolicy="open" without allowFrom "*"', () => {
|
|
24
|
+
const issues = expectInvalidDiscordConfig({
|
|
25
|
+
dmPolicy: "open",
|
|
26
|
+
allowFrom: ["123"],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(issues[0]?.path.join(".")).toBe("allowFrom");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('rejects dmPolicy="open" with empty allowFrom', () => {
|
|
33
|
+
const issues = expectInvalidDiscordConfig({
|
|
34
|
+
dmPolicy: "open",
|
|
35
|
+
allowFrom: [],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(issues[0]?.path.join(".")).toBe("allowFrom");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('rejects legacy dm.policy="open" with empty dm.allowFrom', () => {
|
|
42
|
+
const issues = expectInvalidDiscordConfig({
|
|
43
|
+
dm: { policy: "open", allowFrom: [] },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(issues[0]?.path.join(".")).toBe("dm.allowFrom");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('accepts legacy dm.policy="open" with top-level allowFrom alias', () => {
|
|
50
|
+
expectValidDiscordConfig({
|
|
51
|
+
dm: { policy: "open", allowFrom: ["123"] },
|
|
52
|
+
allowFrom: ["*"],
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("accepts textChunkLimit without reviving legacy message limits", () => {
|
|
57
|
+
const cfg = expectValidDiscordConfig({
|
|
58
|
+
enabled: true,
|
|
59
|
+
textChunkLimit: 1999,
|
|
60
|
+
maxLinesPerMessage: 17,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(cfg.textChunkLimit).toBe(1999);
|
|
64
|
+
expect(cfg.maxLinesPerMessage).toBe(17);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("defaults groupPolicy to allowlist", () => {
|
|
68
|
+
const cfg = expectValidDiscordConfig({});
|
|
69
|
+
|
|
70
|
+
expect(cfg.groupPolicy).toBe("allowlist");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("accepts historyLimit", () => {
|
|
74
|
+
const cfg = expectValidDiscordConfig({ historyLimit: 3 });
|
|
75
|
+
|
|
76
|
+
expect(cfg.historyLimit).toBe(3);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("accepts Discord application IDs at top-level and account scope", () => {
|
|
80
|
+
const cfg = expectValidDiscordConfig({
|
|
81
|
+
applicationId: "123456789012345678",
|
|
82
|
+
accounts: {
|
|
83
|
+
work: {
|
|
84
|
+
applicationId: 234567890123456,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(cfg.applicationId).toBe("123456789012345678");
|
|
90
|
+
expect(cfg.accounts?.work?.applicationId).toBe("234567890123456");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("rejects unsafe numeric Discord application IDs", () => {
|
|
94
|
+
const issues = expectInvalidDiscordConfig({
|
|
95
|
+
applicationId: 106232522769186816,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
issues.some((issue) => issue.message.includes("not a valid non-negative safe integer")),
|
|
100
|
+
).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("loads guild map and dm group settings", () => {
|
|
104
|
+
const cfg = expectValidDiscordConfig({
|
|
105
|
+
enabled: true,
|
|
106
|
+
dm: {
|
|
107
|
+
enabled: true,
|
|
108
|
+
allowFrom: ["steipete"],
|
|
109
|
+
groupEnabled: true,
|
|
110
|
+
groupChannels: ["openclaw-dm"],
|
|
111
|
+
},
|
|
112
|
+
actions: {
|
|
113
|
+
emojiUploads: true,
|
|
114
|
+
stickerUploads: false,
|
|
115
|
+
channels: true,
|
|
116
|
+
},
|
|
117
|
+
guilds: {
|
|
118
|
+
"123": {
|
|
119
|
+
slug: "friends-of-openclaw",
|
|
120
|
+
requireMention: false,
|
|
121
|
+
users: ["steipete"],
|
|
122
|
+
channels: {
|
|
123
|
+
general: { enabled: true, autoThread: true },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(cfg.enabled).toBe(true);
|
|
130
|
+
expect(cfg.dm?.groupEnabled).toBe(true);
|
|
131
|
+
expect(cfg.dm?.groupChannels).toEqual(["openclaw-dm"]);
|
|
132
|
+
expect(cfg.actions?.emojiUploads).toBe(true);
|
|
133
|
+
expect(cfg.actions?.stickerUploads).toBe(false);
|
|
134
|
+
expect(cfg.actions?.channels).toBe(true);
|
|
135
|
+
expect(cfg.guilds?.["123"]?.slug).toBe("friends-of-openclaw");
|
|
136
|
+
expect(cfg.guilds?.["123"]?.channels?.general?.enabled).toBe(true);
|
|
137
|
+
expect(cfg.guilds?.["123"]?.channels?.general?.autoThread).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("accepts voice model override field", () => {
|
|
141
|
+
const cfg = expectValidDiscordConfig({
|
|
142
|
+
voice: {
|
|
143
|
+
model: "openai/gpt-5.4-mini",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(cfg.voice?.model).toBe("openai/gpt-5.4-mini");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("accepts Discord voice timing overrides", () => {
|
|
151
|
+
const cfg = expectValidDiscordConfig({
|
|
152
|
+
voice: {
|
|
153
|
+
connectTimeoutMs: 45_000,
|
|
154
|
+
reconnectGraceMs: 20_000,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(cfg.voice?.connectTimeoutMs).toBe(45_000);
|
|
159
|
+
expect(cfg.voice?.reconnectGraceMs).toBe(20_000);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("rejects invalid Discord voice timing overrides", () => {
|
|
163
|
+
for (const voice of [
|
|
164
|
+
{ connectTimeoutMs: 0 },
|
|
165
|
+
{ connectTimeoutMs: 120_001 },
|
|
166
|
+
{ reconnectGraceMs: -1 },
|
|
167
|
+
{ reconnectGraceMs: 1.5 },
|
|
168
|
+
]) {
|
|
169
|
+
expectInvalidDiscordConfig({ voice });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("coerces safe-integer numeric allowlist entries to strings", () => {
|
|
174
|
+
const cfg = expectValidDiscordConfig({
|
|
175
|
+
allowFrom: [123],
|
|
176
|
+
dm: { allowFrom: [456], groupChannels: [789] },
|
|
177
|
+
guilds: {
|
|
178
|
+
"123": {
|
|
179
|
+
users: [111],
|
|
180
|
+
roles: [222],
|
|
181
|
+
channels: {
|
|
182
|
+
general: { users: [333], roles: [444] },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
execApprovals: { approvers: [555] },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(cfg.allowFrom).toEqual(["123"]);
|
|
190
|
+
expect(cfg.dm?.allowFrom).toEqual(["456"]);
|
|
191
|
+
expect(cfg.dm?.groupChannels).toEqual(["789"]);
|
|
192
|
+
expect(cfg.guilds?.["123"]?.users).toEqual(["111"]);
|
|
193
|
+
expect(cfg.guilds?.["123"]?.roles).toEqual(["222"]);
|
|
194
|
+
expect(cfg.guilds?.["123"]?.channels?.general?.users).toEqual(["333"]);
|
|
195
|
+
expect(cfg.guilds?.["123"]?.channels?.general?.roles).toEqual(["444"]);
|
|
196
|
+
expect(cfg.execApprovals?.approvers).toEqual(["555"]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("rejects numeric IDs that are not valid non-negative safe integers", () => {
|
|
200
|
+
const cases = [106232522769186816, -1, 123.45];
|
|
201
|
+
for (const id of cases) {
|
|
202
|
+
const issues = expectInvalidDiscordConfig({ allowFrom: [id] });
|
|
203
|
+
|
|
204
|
+
expect(
|
|
205
|
+
issues.some((issue) => issue.message.includes("not a valid non-negative safe integer")),
|
|
206
|
+
).toBe(true);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it.each([
|
|
211
|
+
{ name: "status-only presence", config: { status: "idle" } },
|
|
212
|
+
{
|
|
213
|
+
name: "custom activity when type is omitted",
|
|
214
|
+
config: { activity: "Focus time" },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "custom activity type",
|
|
218
|
+
config: { activity: "Chilling", activityType: 4 },
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "auto presence config",
|
|
222
|
+
config: {
|
|
223
|
+
autoPresence: {
|
|
224
|
+
enabled: true,
|
|
225
|
+
intervalMs: 30000,
|
|
226
|
+
minUpdateIntervalMs: 15000,
|
|
227
|
+
exhaustedText: "token exhausted",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
] as const)("accepts $name", ({ config }) => {
|
|
232
|
+
expect(DiscordConfigSchema.safeParse(config).success).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it.each([
|
|
236
|
+
{
|
|
237
|
+
name: "streaming activity without url",
|
|
238
|
+
config: { activity: "Live", activityType: 1 },
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "activityUrl without streaming type",
|
|
242
|
+
config: { activity: "Live", activityUrl: "https://twitch.tv/openclaw" },
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "auto presence min update interval above check interval",
|
|
246
|
+
config: {
|
|
247
|
+
autoPresence: {
|
|
248
|
+
enabled: true,
|
|
249
|
+
intervalMs: 5000,
|
|
250
|
+
minUpdateIntervalMs: 6000,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
] as const)("rejects $name", ({ config }) => {
|
|
255
|
+
expect(DiscordConfigSchema.safeParse(config).success).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("accepts agentComponents.enabled at channel scope", () => {
|
|
259
|
+
const res = DiscordConfigSchema.safeParse({
|
|
260
|
+
agentComponents: {
|
|
261
|
+
enabled: true,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(res.success).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("accepts agentComponents.enabled at account scope", () => {
|
|
269
|
+
const res = DiscordConfigSchema.safeParse({
|
|
270
|
+
accounts: {
|
|
271
|
+
work: {
|
|
272
|
+
agentComponents: {
|
|
273
|
+
enabled: false,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(res.success).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("accepts thread.inheritParent at top-level and account scope", () => {
|
|
283
|
+
const cases = [
|
|
284
|
+
{
|
|
285
|
+
thread: {
|
|
286
|
+
inheritParent: true,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
accounts: {
|
|
291
|
+
work: {
|
|
292
|
+
thread: {
|
|
293
|
+
inheritParent: true,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
] as const;
|
|
299
|
+
|
|
300
|
+
for (const config of cases) {
|
|
301
|
+
const res = DiscordConfigSchema.safeParse(config);
|
|
302
|
+
expect(res.success).toBe(true);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("rejects unknown fields under agentComponents", () => {
|
|
307
|
+
const res = DiscordConfigSchema.safeParse({
|
|
308
|
+
agentComponents: {
|
|
309
|
+
enabled: true,
|
|
310
|
+
invalidField: true,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(res.success).toBe(false);
|
|
315
|
+
if (!res.success) {
|
|
316
|
+
expect(
|
|
317
|
+
res.error.issues.some(
|
|
318
|
+
(issue) =>
|
|
319
|
+
issue.path.join(".") === "agentComponents" &&
|
|
320
|
+
issue.message.toLowerCase().includes("unrecognized"),
|
|
321
|
+
),
|
|
322
|
+
).toBe(true);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { buildChannelConfigSchema, DiscordConfigSchema } from "../config-api.js";
|
|
2
|
+
import { discordChannelConfigUiHints } from "./config-ui-hints.js";
|
|
3
|
+
|
|
4
|
+
export const DiscordChannelConfigSchema = buildChannelConfigSchema(DiscordConfigSchema, {
|
|
5
|
+
uiHints: discordChannelConfigUiHints,
|
|
6
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { ChannelConfigUiHint } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
+
|
|
3
|
+
export const discordChannelConfigUiHints = {
|
|
4
|
+
"": {
|
|
5
|
+
label: "Discord",
|
|
6
|
+
help: "Discord channel provider configuration for bot auth, retry policy, streaming, thread bindings, and optional voice capabilities. Keep privileged intents and advanced features disabled unless needed.",
|
|
7
|
+
},
|
|
8
|
+
dmPolicy: {
|
|
9
|
+
label: "Discord DM Policy",
|
|
10
|
+
help: 'Direct message access control ("pairing" recommended). "open" requires channels.discord.allowFrom=["*"].',
|
|
11
|
+
},
|
|
12
|
+
"dm.policy": {
|
|
13
|
+
label: "Discord DM Policy",
|
|
14
|
+
help: 'Direct message access control ("pairing" recommended). "open" requires channels.discord.allowFrom=["*"] (legacy: channels.discord.dm.allowFrom).',
|
|
15
|
+
},
|
|
16
|
+
configWrites: {
|
|
17
|
+
label: "Discord Config Writes",
|
|
18
|
+
help: "Allow Discord to write config in response to channel events/commands (default: true).",
|
|
19
|
+
},
|
|
20
|
+
proxy: {
|
|
21
|
+
label: "Discord Proxy URL",
|
|
22
|
+
help: "Proxy URL for Discord gateway + API requests (app-id lookup and allowlist resolution). Set per account via channels.discord.accounts.<id>.proxy.",
|
|
23
|
+
},
|
|
24
|
+
"commands.native": {
|
|
25
|
+
label: "Discord Native Commands",
|
|
26
|
+
help: 'Override native commands for Discord (bool or "auto").',
|
|
27
|
+
},
|
|
28
|
+
"commands.nativeSkills": {
|
|
29
|
+
label: "Discord Native Skill Commands",
|
|
30
|
+
help: 'Override native skill commands for Discord (bool or "auto").',
|
|
31
|
+
},
|
|
32
|
+
streaming: {
|
|
33
|
+
label: "Discord Streaming Mode",
|
|
34
|
+
help: 'Unified Discord stream preview mode: "off" | "partial" | "block" | "progress". "progress" maps to "partial" on Discord. Legacy boolean/streamMode keys are auto-mapped.',
|
|
35
|
+
},
|
|
36
|
+
"streaming.mode": {
|
|
37
|
+
label: "Discord Streaming Mode",
|
|
38
|
+
help: 'Canonical Discord preview mode: "off" | "partial" | "block" | "progress". "progress" maps to "partial" on Discord.',
|
|
39
|
+
},
|
|
40
|
+
"streaming.chunkMode": {
|
|
41
|
+
label: "Discord Chunk Mode",
|
|
42
|
+
help: 'Chunking mode for outbound Discord text delivery: "length" (default) or "newline".',
|
|
43
|
+
},
|
|
44
|
+
"streaming.block.enabled": {
|
|
45
|
+
label: "Discord Block Streaming Enabled",
|
|
46
|
+
help: 'Enable chunked block-style Discord preview delivery when channels.discord.streaming.mode="block".',
|
|
47
|
+
},
|
|
48
|
+
"streaming.block.coalesce": {
|
|
49
|
+
label: "Discord Block Streaming Coalesce",
|
|
50
|
+
help: "Merge streamed Discord block replies before final delivery.",
|
|
51
|
+
},
|
|
52
|
+
"streaming.preview.chunk.minChars": {
|
|
53
|
+
label: "Discord Draft Chunk Min Chars",
|
|
54
|
+
help: 'Minimum chars before emitting a Discord stream preview update when channels.discord.streaming.mode="block" (default: 200).',
|
|
55
|
+
},
|
|
56
|
+
"streaming.preview.chunk.maxChars": {
|
|
57
|
+
label: "Discord Draft Chunk Max Chars",
|
|
58
|
+
help: 'Target max size for a Discord stream preview chunk when channels.discord.streaming.mode="block" (default: 800; clamped to channels.discord.textChunkLimit).',
|
|
59
|
+
},
|
|
60
|
+
"streaming.preview.chunk.breakPreference": {
|
|
61
|
+
label: "Discord Draft Chunk Break Preference",
|
|
62
|
+
help: "Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph.",
|
|
63
|
+
},
|
|
64
|
+
"streaming.preview.toolProgress": {
|
|
65
|
+
label: "Discord Draft Tool Progress",
|
|
66
|
+
help: "Show tool/progress activity in the live draft preview message (default: true). Set false to keep tool updates as separate messages.",
|
|
67
|
+
},
|
|
68
|
+
"retry.attempts": {
|
|
69
|
+
label: "Discord Retry Attempts",
|
|
70
|
+
help: "Max retry attempts for outbound Discord API calls (default: 3).",
|
|
71
|
+
},
|
|
72
|
+
"retry.minDelayMs": {
|
|
73
|
+
label: "Discord Retry Min Delay (ms)",
|
|
74
|
+
help: "Minimum retry delay in ms for Discord outbound calls.",
|
|
75
|
+
},
|
|
76
|
+
"retry.maxDelayMs": {
|
|
77
|
+
label: "Discord Retry Max Delay (ms)",
|
|
78
|
+
help: "Maximum retry delay cap in ms for Discord outbound calls.",
|
|
79
|
+
},
|
|
80
|
+
"retry.jitter": {
|
|
81
|
+
label: "Discord Retry Jitter",
|
|
82
|
+
help: "Jitter factor (0-1) applied to Discord retry delays.",
|
|
83
|
+
},
|
|
84
|
+
maxLinesPerMessage: {
|
|
85
|
+
label: "Discord Max Lines Per Message",
|
|
86
|
+
help: "Soft max line count per Discord message (default: 17).",
|
|
87
|
+
},
|
|
88
|
+
"thread.inheritParent": {
|
|
89
|
+
label: "Discord Thread Parent Inheritance",
|
|
90
|
+
help: "If true, Discord thread sessions inherit the parent channel transcript (default: false).",
|
|
91
|
+
},
|
|
92
|
+
"eventQueue.listenerTimeout": {
|
|
93
|
+
label: "Discord EventQueue Listener Timeout (ms)",
|
|
94
|
+
help: "Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts.<id>.eventQueue.listenerTimeout.",
|
|
95
|
+
},
|
|
96
|
+
"eventQueue.maxQueueSize": {
|
|
97
|
+
label: "Discord EventQueue Max Queue Size",
|
|
98
|
+
help: "Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts.<id>.eventQueue.maxQueueSize.",
|
|
99
|
+
},
|
|
100
|
+
"eventQueue.maxConcurrency": {
|
|
101
|
+
label: "Discord EventQueue Max Concurrency",
|
|
102
|
+
help: "Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts.<id>.eventQueue.maxConcurrency.",
|
|
103
|
+
},
|
|
104
|
+
"threadBindings.enabled": {
|
|
105
|
+
label: "Discord Thread Binding Enabled",
|
|
106
|
+
help: "Enable Discord thread binding features (/focus, bound-thread routing/delivery, and thread-bound subagent sessions). Overrides session.threadBindings.enabled when set.",
|
|
107
|
+
},
|
|
108
|
+
"threadBindings.idleHours": {
|
|
109
|
+
label: "Discord Thread Binding Idle Timeout (hours)",
|
|
110
|
+
help: "Inactivity window in hours for Discord thread-bound sessions (/focus and spawned thread sessions). Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set.",
|
|
111
|
+
},
|
|
112
|
+
"threadBindings.maxAgeHours": {
|
|
113
|
+
label: "Discord Thread Binding Max Age (hours)",
|
|
114
|
+
help: "Optional hard max age in hours for Discord thread-bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set.",
|
|
115
|
+
},
|
|
116
|
+
"threadBindings.spawnSessions": {
|
|
117
|
+
label: "Discord Thread-Bound Session Spawn",
|
|
118
|
+
help: "Allow sessions_spawn(thread=true) and ACP thread spawns to auto-create and bind Discord threads (default: true). Set false to disable for this account/channel.",
|
|
119
|
+
},
|
|
120
|
+
"threadBindings.defaultSpawnContext": {
|
|
121
|
+
label: "Discord Thread Spawn Context",
|
|
122
|
+
help: 'Default native subagent context for thread-bound spawns. "fork" starts from the requester transcript; "isolated" starts clean. Default: "fork".',
|
|
123
|
+
},
|
|
124
|
+
"ui.components.accentColor": {
|
|
125
|
+
label: "Discord Component Accent Color",
|
|
126
|
+
help: "Accent color for Discord component containers (hex). Set per account via channels.discord.accounts.<id>.ui.components.accentColor.",
|
|
127
|
+
},
|
|
128
|
+
"intents.presence": {
|
|
129
|
+
label: "Discord Presence Intent",
|
|
130
|
+
help: "Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false.",
|
|
131
|
+
},
|
|
132
|
+
"intents.guildMembers": {
|
|
133
|
+
label: "Discord Guild Members Intent",
|
|
134
|
+
help: "Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false.",
|
|
135
|
+
},
|
|
136
|
+
"intents.voiceStates": {
|
|
137
|
+
label: "Discord Voice States Intent",
|
|
138
|
+
help: "Enable the Guild Voice States intent. Defaults to the effective Discord voice setting; set true only for Discord voice channel conversations.",
|
|
139
|
+
},
|
|
140
|
+
gatewayInfoTimeoutMs: {
|
|
141
|
+
label: "Discord Gateway Metadata Timeout (ms)",
|
|
142
|
+
help: "Timeout for Discord /gateway/bot metadata lookup before falling back to the default gateway URL. Default is 30000; OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS can override when config is unset.",
|
|
143
|
+
},
|
|
144
|
+
gatewayReadyTimeoutMs: {
|
|
145
|
+
label: "Discord Gateway READY Timeout (ms)",
|
|
146
|
+
help: "Startup wait for the Discord gateway READY event before restarting the socket. Default is 15000; OPENCLAW_DISCORD_READY_TIMEOUT_MS can override when config is unset.",
|
|
147
|
+
},
|
|
148
|
+
gatewayRuntimeReadyTimeoutMs: {
|
|
149
|
+
label: "Discord Gateway Runtime READY Timeout (ms)",
|
|
150
|
+
help: "Runtime reconnect wait for the Discord gateway READY event before force-stopping the lifecycle. Default is 30000; OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS can override when config is unset.",
|
|
151
|
+
},
|
|
152
|
+
"voice.enabled": {
|
|
153
|
+
label: "Discord Voice Enabled",
|
|
154
|
+
help: "Enable Discord voice channel conversations. Text-only Discord configs leave voice off by default; set true to enable /vc commands and the Guild Voice States intent.",
|
|
155
|
+
},
|
|
156
|
+
"voice.model": {
|
|
157
|
+
label: "Discord Voice Model",
|
|
158
|
+
help: "Optional LLM model override for Discord voice channel responses (for example openai/gpt-5.4-mini). Leave unset to inherit the routed agent model.",
|
|
159
|
+
},
|
|
160
|
+
"voice.autoJoin": {
|
|
161
|
+
label: "Discord Voice Auto-Join",
|
|
162
|
+
help: "Voice channels to auto-join on startup (list of guildId/channelId entries).",
|
|
163
|
+
},
|
|
164
|
+
"voice.daveEncryption": {
|
|
165
|
+
label: "Discord Voice DAVE Encryption",
|
|
166
|
+
help: "Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this).",
|
|
167
|
+
},
|
|
168
|
+
"voice.decryptionFailureTolerance": {
|
|
169
|
+
label: "Discord Voice Decrypt Failure Tolerance",
|
|
170
|
+
help: "Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24).",
|
|
171
|
+
},
|
|
172
|
+
"voice.connectTimeoutMs": {
|
|
173
|
+
label: "Discord Voice Connect Timeout (ms)",
|
|
174
|
+
help: "Initial @discordjs/voice Ready wait before a join is treated as failed. Default: 30000.",
|
|
175
|
+
},
|
|
176
|
+
"voice.reconnectGraceMs": {
|
|
177
|
+
label: "Discord Voice Reconnect Grace (ms)",
|
|
178
|
+
help: "Grace period for a disconnected Discord voice session to enter Signalling or Connecting before OpenClaw destroys it. Default: 15000.",
|
|
179
|
+
},
|
|
180
|
+
"voice.tts": {
|
|
181
|
+
label: "Discord Voice Text-to-Speech",
|
|
182
|
+
help: "Optional TTS overrides for Discord voice playback (merged with messages.tts).",
|
|
183
|
+
},
|
|
184
|
+
"pluralkit.enabled": {
|
|
185
|
+
label: "Discord PluralKit Enabled",
|
|
186
|
+
help: "Resolve PluralKit proxied messages and treat system members as distinct senders.",
|
|
187
|
+
},
|
|
188
|
+
"pluralkit.token": {
|
|
189
|
+
label: "Discord PluralKit Token",
|
|
190
|
+
help: "Optional PluralKit token for resolving private systems or members.",
|
|
191
|
+
},
|
|
192
|
+
activity: {
|
|
193
|
+
label: "Discord Presence Activity",
|
|
194
|
+
help: "Discord presence activity text (defaults to custom status).",
|
|
195
|
+
},
|
|
196
|
+
status: {
|
|
197
|
+
label: "Discord Presence Status",
|
|
198
|
+
help: "Discord presence status (online, dnd, idle, invisible).",
|
|
199
|
+
},
|
|
200
|
+
"autoPresence.enabled": {
|
|
201
|
+
label: "Discord Auto Presence Enabled",
|
|
202
|
+
help: "Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd.",
|
|
203
|
+
},
|
|
204
|
+
"autoPresence.intervalMs": {
|
|
205
|
+
label: "Discord Auto Presence Check Interval (ms)",
|
|
206
|
+
help: "How often to evaluate Discord auto-presence state in milliseconds (default: 30000).",
|
|
207
|
+
},
|
|
208
|
+
"autoPresence.minUpdateIntervalMs": {
|
|
209
|
+
label: "Discord Auto Presence Min Update Interval (ms)",
|
|
210
|
+
help: "Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes.",
|
|
211
|
+
},
|
|
212
|
+
"autoPresence.healthyText": {
|
|
213
|
+
label: "Discord Auto Presence Healthy Text",
|
|
214
|
+
help: "Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set.",
|
|
215
|
+
},
|
|
216
|
+
"autoPresence.degradedText": {
|
|
217
|
+
label: "Discord Auto Presence Degraded Text",
|
|
218
|
+
help: "Optional custom status text while runtime/model availability is degraded or unknown (idle).",
|
|
219
|
+
},
|
|
220
|
+
"autoPresence.exhaustedText": {
|
|
221
|
+
label: "Discord Auto Presence Exhausted Text",
|
|
222
|
+
help: "Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder.",
|
|
223
|
+
},
|
|
224
|
+
activityType: {
|
|
225
|
+
label: "Discord Presence Activity Type",
|
|
226
|
+
help: "Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing).",
|
|
227
|
+
},
|
|
228
|
+
activityUrl: {
|
|
229
|
+
label: "Discord Presence Activity URL",
|
|
230
|
+
help: "Discord presence streaming URL (required for activityType=1).",
|
|
231
|
+
},
|
|
232
|
+
allowBots: {
|
|
233
|
+
label: "Discord Allow Bot Messages",
|
|
234
|
+
help: 'Allow bot-authored messages to trigger Discord replies (default: false). Set "mentions" to only accept bot messages that mention the bot.',
|
|
235
|
+
},
|
|
236
|
+
mentionAliases: {
|
|
237
|
+
label: "Discord Mention Aliases",
|
|
238
|
+
help: "Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts.<id>.mentionAliases.",
|
|
239
|
+
},
|
|
240
|
+
token: {
|
|
241
|
+
label: "Discord Bot Token",
|
|
242
|
+
help: "Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.",
|
|
243
|
+
sensitive: true,
|
|
244
|
+
},
|
|
245
|
+
applicationId: {
|
|
246
|
+
label: "Discord Application ID",
|
|
247
|
+
help: "Optional Discord application/client ID. Set this when hosted environments cannot reach Discord's application lookup endpoint during startup.",
|
|
248
|
+
},
|
|
249
|
+
} satisfies Record<string, ChannelConfigUiHint>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeOptionalLowercaseString,
|
|
3
|
+
normalizeOptionalString,
|
|
4
|
+
} from "openclaw/plugin-sdk/text-runtime";
|
|
5
|
+
import { parseDiscordTarget } from "./target-parsing.js";
|
|
6
|
+
|
|
7
|
+
function normalizeDiscordTarget(
|
|
8
|
+
raw: string | null | undefined,
|
|
9
|
+
defaultKind: "user" | "channel",
|
|
10
|
+
): string | undefined {
|
|
11
|
+
const trimmed = normalizeOptionalString(raw);
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return parseDiscordTarget(trimmed, { defaultKind })?.normalized;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildDiscordConversationIdentity(
|
|
19
|
+
kind: "user" | "channel",
|
|
20
|
+
rawId: string | null | undefined,
|
|
21
|
+
): string | undefined {
|
|
22
|
+
const trimmed = normalizeOptionalString(rawId);
|
|
23
|
+
return trimmed ? `${kind}:${trimmed}` : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveDiscordConversationIdentity(params: {
|
|
27
|
+
isDirectMessage: boolean;
|
|
28
|
+
userId?: string | null;
|
|
29
|
+
channelId?: string | null;
|
|
30
|
+
}): string | undefined {
|
|
31
|
+
return params.isDirectMessage
|
|
32
|
+
? buildDiscordConversationIdentity("user", params.userId)
|
|
33
|
+
: buildDiscordConversationIdentity("channel", params.channelId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveDiscordCurrentConversationIdentity(params: {
|
|
37
|
+
chatType?: string | null;
|
|
38
|
+
from?: string | null;
|
|
39
|
+
originatingTo?: string | null;
|
|
40
|
+
commandTo?: string | null;
|
|
41
|
+
fallbackTo?: string | null;
|
|
42
|
+
}): string | undefined {
|
|
43
|
+
if (normalizeOptionalLowercaseString(params.chatType) === "direct") {
|
|
44
|
+
const senderTarget = normalizeDiscordTarget(params.from, "user");
|
|
45
|
+
if (senderTarget?.startsWith("user:")) {
|
|
46
|
+
return senderTarget;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const candidate of [params.originatingTo, params.commandTo, params.fallbackTo]) {
|
|
51
|
+
const target = normalizeDiscordTarget(candidate, "channel");
|
|
52
|
+
if (target) {
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
+
import {
|
|
3
|
+
resolveRetryConfig,
|
|
4
|
+
retryAsync,
|
|
5
|
+
type RetryConfig,
|
|
6
|
+
} from "openclaw/plugin-sdk/retry-runtime";
|
|
7
|
+
import { resolveDiscordAccount } from "./accounts.js";
|
|
8
|
+
import { DiscordError } from "./internal/discord.js";
|
|
9
|
+
|
|
10
|
+
const DISCORD_DELIVERY_RETRY_DEFAULTS = {
|
|
11
|
+
attempts: 3,
|
|
12
|
+
minDelayMs: 1000,
|
|
13
|
+
maxDelayMs: 30_000,
|
|
14
|
+
jitter: 0,
|
|
15
|
+
} satisfies Required<RetryConfig>;
|
|
16
|
+
|
|
17
|
+
export function isRetryableDiscordDeliveryError(err: unknown): boolean {
|
|
18
|
+
if (err instanceof DiscordError) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const status = (err as { status?: number }).status ?? (err as { statusCode?: number }).statusCode;
|
|
22
|
+
return status === 429 || (status !== undefined && status >= 500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getDiscordDeliveryRetryAfterMs(err: unknown): number | undefined {
|
|
26
|
+
if (!err || typeof err !== "object") {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (
|
|
30
|
+
"retryAfter" in err &&
|
|
31
|
+
typeof err.retryAfter === "number" &&
|
|
32
|
+
Number.isFinite(err.retryAfter)
|
|
33
|
+
) {
|
|
34
|
+
return err.retryAfter * 1000;
|
|
35
|
+
}
|
|
36
|
+
const retryAfterRaw = (err as { headers?: Record<string, string> }).headers?.["retry-after"];
|
|
37
|
+
if (!retryAfterRaw) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const retryAfterMs = Number(retryAfterRaw) * 1000;
|
|
41
|
+
return Number.isFinite(retryAfterMs) ? retryAfterMs : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function withDiscordDeliveryRetry<T>(params: {
|
|
45
|
+
cfg: OpenClawConfig;
|
|
46
|
+
accountId?: string | null;
|
|
47
|
+
fn: () => Promise<T>;
|
|
48
|
+
}): Promise<T> {
|
|
49
|
+
const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
50
|
+
const retryConfig = resolveRetryConfig(DISCORD_DELIVERY_RETRY_DEFAULTS, account.config.retry);
|
|
51
|
+
return await retryAsync(params.fn, {
|
|
52
|
+
...retryConfig,
|
|
53
|
+
shouldRetry: (err) => isRetryableDiscordDeliveryError(err),
|
|
54
|
+
retryAfterMs: getDiscordDeliveryRetryAfterMs,
|
|
55
|
+
});
|
|
56
|
+
}
|