@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,154 @@
|
|
|
1
|
+
import { createFinalizableDraftLifecycle } from "openclaw/plugin-sdk/channel-lifecycle";
|
|
2
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
3
|
+
import {
|
|
4
|
+
createChannelMessage,
|
|
5
|
+
deleteChannelMessage,
|
|
6
|
+
editChannelMessage,
|
|
7
|
+
type RequestClient,
|
|
8
|
+
} from "./internal/discord.js";
|
|
9
|
+
|
|
10
|
+
/** Discord messages cap at 2000 characters. */
|
|
11
|
+
const DISCORD_STREAM_MAX_CHARS = 2000;
|
|
12
|
+
const DEFAULT_THROTTLE_MS = 1200;
|
|
13
|
+
const DISCORD_PREVIEW_ALLOWED_MENTIONS = { parse: [] };
|
|
14
|
+
|
|
15
|
+
type DiscordDraftStream = {
|
|
16
|
+
update: (text: string) => void;
|
|
17
|
+
flush: () => Promise<void>;
|
|
18
|
+
messageId: () => string | undefined;
|
|
19
|
+
clear: () => Promise<void>;
|
|
20
|
+
discardPending: () => Promise<void>;
|
|
21
|
+
seal: () => Promise<void>;
|
|
22
|
+
stop: () => Promise<void>;
|
|
23
|
+
/** Reset internal state so the next update creates a new message instead of editing. */
|
|
24
|
+
forceNewMessage: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function createDiscordDraftStream(params: {
|
|
28
|
+
rest: RequestClient;
|
|
29
|
+
channelId: string;
|
|
30
|
+
maxChars?: number;
|
|
31
|
+
replyToMessageId?: string | (() => string | undefined);
|
|
32
|
+
throttleMs?: number;
|
|
33
|
+
/** Minimum chars before sending first message (debounce for push notifications) */
|
|
34
|
+
minInitialChars?: number;
|
|
35
|
+
log?: (message: string) => void;
|
|
36
|
+
warn?: (message: string) => void;
|
|
37
|
+
}): DiscordDraftStream {
|
|
38
|
+
const maxChars = Math.min(params.maxChars ?? DISCORD_STREAM_MAX_CHARS, DISCORD_STREAM_MAX_CHARS);
|
|
39
|
+
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
40
|
+
const minInitialChars = params.minInitialChars;
|
|
41
|
+
const channelId = params.channelId;
|
|
42
|
+
const rest = params.rest;
|
|
43
|
+
const resolveReplyToMessageId = () =>
|
|
44
|
+
typeof params.replyToMessageId === "function"
|
|
45
|
+
? params.replyToMessageId()
|
|
46
|
+
: params.replyToMessageId;
|
|
47
|
+
|
|
48
|
+
const streamState = { stopped: false, final: false };
|
|
49
|
+
let streamMessageId: string | undefined;
|
|
50
|
+
let lastSentText = "";
|
|
51
|
+
|
|
52
|
+
const sendOrEditStreamMessage = async (text: string): Promise<boolean> => {
|
|
53
|
+
// Allow final flush even if stopped (e.g., after clear()).
|
|
54
|
+
if (streamState.stopped && !streamState.final) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const trimmed = text.trimEnd();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (trimmed.length > maxChars) {
|
|
62
|
+
// Discord messages cap at 2000 chars.
|
|
63
|
+
// Stop streaming once we exceed the cap to avoid repeated API failures.
|
|
64
|
+
streamState.stopped = true;
|
|
65
|
+
params.warn?.(`discord stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (trimmed === lastSentText) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Debounce first preview send for better push notification quality.
|
|
73
|
+
if (streamMessageId === undefined && minInitialChars != null && !streamState.final) {
|
|
74
|
+
if (trimmed.length < minInitialChars) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
lastSentText = trimmed;
|
|
80
|
+
try {
|
|
81
|
+
if (streamMessageId !== undefined) {
|
|
82
|
+
// Edit existing message
|
|
83
|
+
await editChannelMessage(rest, channelId, streamMessageId, {
|
|
84
|
+
body: { content: trimmed, allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS },
|
|
85
|
+
});
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
// Send new message
|
|
89
|
+
const replyToMessageId = resolveReplyToMessageId()?.trim();
|
|
90
|
+
const messageReference = replyToMessageId
|
|
91
|
+
? { message_id: replyToMessageId, fail_if_not_exists: false }
|
|
92
|
+
: undefined;
|
|
93
|
+
const sent = await createChannelMessage<{ id?: string }>(rest, channelId, {
|
|
94
|
+
body: {
|
|
95
|
+
content: trimmed,
|
|
96
|
+
allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS,
|
|
97
|
+
...(messageReference ? { message_reference: messageReference } : {}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const sentMessageId = sent?.id;
|
|
101
|
+
if (typeof sentMessageId !== "string" || !sentMessageId) {
|
|
102
|
+
streamState.stopped = true;
|
|
103
|
+
params.warn?.("discord stream preview stopped (missing message id from send)");
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
streamMessageId = sentMessageId;
|
|
107
|
+
return true;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
streamState.stopped = true;
|
|
110
|
+
params.warn?.(`discord stream preview failed: ${formatErrorMessage(err)}`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const readMessageId = () => streamMessageId;
|
|
116
|
+
const clearMessageId = () => {
|
|
117
|
+
streamMessageId = undefined;
|
|
118
|
+
};
|
|
119
|
+
const isValidStreamMessageId = (value: unknown): value is string => typeof value === "string";
|
|
120
|
+
const deleteStreamMessage = async (messageId: string) => {
|
|
121
|
+
await deleteChannelMessage(rest, channelId, messageId);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const { loop, update, stop, clear, discardPending, seal } = createFinalizableDraftLifecycle({
|
|
125
|
+
throttleMs,
|
|
126
|
+
state: streamState,
|
|
127
|
+
sendOrEditStreamMessage,
|
|
128
|
+
readMessageId,
|
|
129
|
+
clearMessageId,
|
|
130
|
+
isValidMessageId: isValidStreamMessageId,
|
|
131
|
+
deleteMessage: deleteStreamMessage,
|
|
132
|
+
warn: params.warn,
|
|
133
|
+
warnPrefix: "discord stream preview cleanup failed",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const forceNewMessage = () => {
|
|
137
|
+
streamMessageId = undefined;
|
|
138
|
+
lastSentText = "";
|
|
139
|
+
loop.resetPending();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
params.log?.(`discord stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
update,
|
|
146
|
+
flush: loop.flush,
|
|
147
|
+
messageId: () => streamMessageId,
|
|
148
|
+
clear,
|
|
149
|
+
discardPending,
|
|
150
|
+
seal,
|
|
151
|
+
stop,
|
|
152
|
+
forceNewMessage,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DISCORD_RESPONSE_BODY_SUMMARY_MAX_CHARS = 240;
|
|
2
|
+
|
|
3
|
+
export function summarizeDiscordResponseBody(
|
|
4
|
+
body: string,
|
|
5
|
+
opts: { emptyText?: string } = {},
|
|
6
|
+
): string | undefined {
|
|
7
|
+
const summary = body
|
|
8
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ")
|
|
9
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ")
|
|
10
|
+
.replace(/<[^>]+>/g, " ")
|
|
11
|
+
.replace(/ /gi, " ")
|
|
12
|
+
.replace(/&/gi, "&")
|
|
13
|
+
.replace(/</gi, "<")
|
|
14
|
+
.replace(/>/gi, ">")
|
|
15
|
+
.replace(/\s+/g, " ")
|
|
16
|
+
.trim();
|
|
17
|
+
if (!summary) {
|
|
18
|
+
return opts.emptyText;
|
|
19
|
+
}
|
|
20
|
+
return summary.slice(0, DISCORD_RESPONSE_BODY_SUMMARY_MAX_CHARS);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isDiscordHtmlResponseBody(body: string, contentType?: string | null): boolean {
|
|
24
|
+
return (
|
|
25
|
+
/\bhtml\b/i.test(contentType ?? "") ||
|
|
26
|
+
/^\s*<!doctype\s+html\b/i.test(body) ||
|
|
27
|
+
/^\s*<html\b/i.test(body)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isDiscordRateLimitResponseBody(body: string): boolean {
|
|
32
|
+
const normalized = body.toLowerCase();
|
|
33
|
+
return (
|
|
34
|
+
normalized.includes("error 1015") ||
|
|
35
|
+
normalized.includes("cloudflare") ||
|
|
36
|
+
normalized.includes("rate limit")
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
getDiscordExecApprovalApprovers,
|
|
5
|
+
isDiscordExecApprovalApprover,
|
|
6
|
+
isDiscordExecApprovalClientEnabled,
|
|
7
|
+
} from "./exec-approvals.js";
|
|
8
|
+
|
|
9
|
+
function buildConfig(
|
|
10
|
+
execApprovals?: NonNullable<NonNullable<OpenClawConfig["channels"]>["discord"]>["execApprovals"],
|
|
11
|
+
channelOverrides?: Partial<NonNullable<NonNullable<OpenClawConfig["channels"]>["discord"]>>,
|
|
12
|
+
): OpenClawConfig {
|
|
13
|
+
return {
|
|
14
|
+
channels: {
|
|
15
|
+
discord: {
|
|
16
|
+
token: "discord-token",
|
|
17
|
+
...channelOverrides,
|
|
18
|
+
execApprovals,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
} as OpenClawConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("discord exec approvals", () => {
|
|
25
|
+
it("requires explicit enablement even when owner approvers resolve", () => {
|
|
26
|
+
expect(isDiscordExecApprovalClientEnabled({ cfg: buildConfig() })).toBe(false);
|
|
27
|
+
expect(
|
|
28
|
+
isDiscordExecApprovalClientEnabled({
|
|
29
|
+
cfg: buildConfig({ enabled: true }),
|
|
30
|
+
}),
|
|
31
|
+
).toBe(false);
|
|
32
|
+
expect(
|
|
33
|
+
isDiscordExecApprovalClientEnabled({
|
|
34
|
+
cfg: buildConfig({ approvers: ["123"] }),
|
|
35
|
+
}),
|
|
36
|
+
).toBe(false);
|
|
37
|
+
expect(
|
|
38
|
+
isDiscordExecApprovalClientEnabled({
|
|
39
|
+
cfg: {
|
|
40
|
+
...buildConfig(),
|
|
41
|
+
commands: { ownerAllowFrom: ["discord:789"] },
|
|
42
|
+
} as OpenClawConfig,
|
|
43
|
+
}),
|
|
44
|
+
).toBe(false);
|
|
45
|
+
expect(
|
|
46
|
+
isDiscordExecApprovalClientEnabled({
|
|
47
|
+
cfg: buildConfig({ enabled: "auto", approvers: ["123"] }),
|
|
48
|
+
}),
|
|
49
|
+
).toBe(true);
|
|
50
|
+
expect(
|
|
51
|
+
isDiscordExecApprovalClientEnabled({
|
|
52
|
+
cfg: buildConfig({ enabled: false, approvers: ["123"] }),
|
|
53
|
+
}),
|
|
54
|
+
).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("prefers explicit approvers when configured", () => {
|
|
58
|
+
const cfg = buildConfig({ approvers: ["456"] }, { allowFrom: ["123"], defaultTo: "user:789" });
|
|
59
|
+
|
|
60
|
+
expect(getDiscordExecApprovalApprovers({ cfg })).toEqual(["456"]);
|
|
61
|
+
expect(isDiscordExecApprovalApprover({ cfg, senderId: "456" })).toBe(true);
|
|
62
|
+
expect(isDiscordExecApprovalApprover({ cfg, senderId: "123" })).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("does not infer approvers from allowFrom or default DM routes", () => {
|
|
66
|
+
const cfg = buildConfig(
|
|
67
|
+
{ enabled: true },
|
|
68
|
+
{
|
|
69
|
+
allowFrom: ["123"],
|
|
70
|
+
dm: { allowFrom: ["456"] },
|
|
71
|
+
defaultTo: "user:789",
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(getDiscordExecApprovalApprovers({ cfg })).toEqual([]);
|
|
76
|
+
expect(isDiscordExecApprovalApprover({ cfg, senderId: "789" })).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("falls back to commands.ownerAllowFrom for exec approvers", () => {
|
|
80
|
+
const cfg = {
|
|
81
|
+
...buildConfig(),
|
|
82
|
+
commands: { ownerAllowFrom: ["discord:123", "user:456", "789"] },
|
|
83
|
+
} as OpenClawConfig;
|
|
84
|
+
|
|
85
|
+
expect(getDiscordExecApprovalApprovers({ cfg })).toEqual(["123", "456", "789"]);
|
|
86
|
+
expect(isDiscordExecApprovalApprover({ cfg, senderId: "456" })).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ChannelOutboundPayloadHint } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
3
|
+
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-types";
|
|
4
|
+
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
|
5
|
+
import { resolveDiscordAccount } from "./accounts.js";
|
|
6
|
+
import {
|
|
7
|
+
getExecApprovalReplyMetadata,
|
|
8
|
+
isChannelExecApprovalClientEnabledFromConfig,
|
|
9
|
+
matchesApprovalRequestFilters,
|
|
10
|
+
resolveApprovalApprovers,
|
|
11
|
+
} from "./approval-runtime.js";
|
|
12
|
+
import { parseDiscordTarget } from "./target-parsing.js";
|
|
13
|
+
|
|
14
|
+
function normalizeDiscordApproverId(value: string): string | undefined {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (/^\d+$/.test(trimmed)) {
|
|
20
|
+
return trimmed;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const target = parseDiscordTarget(trimmed);
|
|
24
|
+
return target?.kind === "user" ? target.id : undefined;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveDiscordOwnerApprovers(cfg: OpenClawConfig): string[] {
|
|
31
|
+
const ownerAllowFrom = cfg.commands?.ownerAllowFrom;
|
|
32
|
+
if (!Array.isArray(ownerAllowFrom) || ownerAllowFrom.length === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return resolveApprovalApprovers({
|
|
36
|
+
explicit: ownerAllowFrom,
|
|
37
|
+
normalizeApprover: (value) => normalizeDiscordApproverId(String(value)),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getDiscordExecApprovalApprovers(params: {
|
|
42
|
+
cfg: OpenClawConfig;
|
|
43
|
+
accountId?: string | null;
|
|
44
|
+
configOverride?: DiscordExecApprovalConfig | null;
|
|
45
|
+
}): string[] {
|
|
46
|
+
return resolveApprovalApprovers({
|
|
47
|
+
explicit:
|
|
48
|
+
params.configOverride?.approvers ??
|
|
49
|
+
resolveDiscordAccount(params).config.execApprovals?.approvers ??
|
|
50
|
+
resolveDiscordOwnerApprovers(params.cfg),
|
|
51
|
+
normalizeApprover: (value) => normalizeDiscordApproverId(String(value)),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isDiscordExecApprovalClientEnabled(params: {
|
|
56
|
+
cfg: OpenClawConfig;
|
|
57
|
+
accountId?: string | null;
|
|
58
|
+
configOverride?: DiscordExecApprovalConfig | null;
|
|
59
|
+
}): boolean {
|
|
60
|
+
const config = params.configOverride ?? resolveDiscordAccount(params).config.execApprovals;
|
|
61
|
+
return isChannelExecApprovalClientEnabledFromConfig({
|
|
62
|
+
enabled: config?.enabled,
|
|
63
|
+
approverCount: getDiscordExecApprovalApprovers({
|
|
64
|
+
cfg: params.cfg,
|
|
65
|
+
accountId: params.accountId,
|
|
66
|
+
configOverride: params.configOverride,
|
|
67
|
+
}).length,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isDiscordExecApprovalApprover(params: {
|
|
72
|
+
cfg: OpenClawConfig;
|
|
73
|
+
accountId?: string | null;
|
|
74
|
+
senderId?: string | null;
|
|
75
|
+
configOverride?: DiscordExecApprovalConfig | null;
|
|
76
|
+
}): boolean {
|
|
77
|
+
const senderId = params.senderId?.trim();
|
|
78
|
+
if (!senderId) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return getDiscordExecApprovalApprovers({
|
|
82
|
+
cfg: params.cfg,
|
|
83
|
+
accountId: params.accountId,
|
|
84
|
+
configOverride: params.configOverride,
|
|
85
|
+
}).includes(senderId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function shouldSuppressLocalDiscordExecApprovalPrompt(params: {
|
|
89
|
+
cfg: OpenClawConfig;
|
|
90
|
+
accountId?: string | null;
|
|
91
|
+
payload: ReplyPayload;
|
|
92
|
+
hint?: ChannelOutboundPayloadHint;
|
|
93
|
+
}): boolean {
|
|
94
|
+
const metadata = getExecApprovalReplyMetadata(params.payload);
|
|
95
|
+
const config = resolveDiscordAccount(params).config.execApprovals;
|
|
96
|
+
return (
|
|
97
|
+
params.hint?.kind === "approval-pending" &&
|
|
98
|
+
params.hint.nativeRouteActive === true &&
|
|
99
|
+
isDiscordExecApprovalClientEnabled(params) &&
|
|
100
|
+
metadata !== null &&
|
|
101
|
+
matchesApprovalRequestFilters({
|
|
102
|
+
request: {
|
|
103
|
+
agentId: metadata.agentId,
|
|
104
|
+
sessionKey: metadata.sessionKey,
|
|
105
|
+
},
|
|
106
|
+
agentFilter: config?.agentFilter,
|
|
107
|
+
sessionFilter: config?.sessionFilter,
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
|
5
|
+
logVerbose: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
let logVerbose: typeof import("openclaw/plugin-sdk/runtime-env").logVerbose;
|
|
9
|
+
let attachDiscordGatewayLogging: typeof import("./gateway-logging.js").attachDiscordGatewayLogging;
|
|
10
|
+
|
|
11
|
+
const makeRuntime = () => ({
|
|
12
|
+
log: vi.fn(),
|
|
13
|
+
error: vi.fn(),
|
|
14
|
+
exit: vi.fn(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("attachDiscordGatewayLogging", () => {
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
({ logVerbose } = await import("openclaw/plugin-sdk/runtime-env"));
|
|
20
|
+
({ attachDiscordGatewayLogging } = await import("./gateway-logging.js"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it("logs debug events and promotes reconnect/close to info", () => {
|
|
27
|
+
const emitter = new EventEmitter();
|
|
28
|
+
const runtime = makeRuntime();
|
|
29
|
+
|
|
30
|
+
const cleanup = attachDiscordGatewayLogging({
|
|
31
|
+
emitter,
|
|
32
|
+
runtime,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
emitter.emit("debug", "Gateway websocket opened");
|
|
36
|
+
emitter.emit("debug", "Gateway websocket closed: 1001");
|
|
37
|
+
emitter.emit("debug", "Gateway reconnect scheduled in 1000ms (close, resume=true)");
|
|
38
|
+
emitter.emit("debug", "Gateway forcing fresh IDENTIFY after 3 failed resume attempts");
|
|
39
|
+
|
|
40
|
+
const logVerboseMock = vi.mocked(logVerbose);
|
|
41
|
+
expect(logVerboseMock).toHaveBeenCalledTimes(4);
|
|
42
|
+
expect(runtime.log).toHaveBeenCalledTimes(3);
|
|
43
|
+
expect(runtime.log).toHaveBeenNthCalledWith(
|
|
44
|
+
1,
|
|
45
|
+
"discord gateway: Gateway websocket closed: 1001",
|
|
46
|
+
);
|
|
47
|
+
expect(runtime.log).toHaveBeenNthCalledWith(
|
|
48
|
+
2,
|
|
49
|
+
"discord gateway: Gateway reconnect scheduled in 1000ms (close, resume=true)",
|
|
50
|
+
);
|
|
51
|
+
expect(runtime.log).toHaveBeenNthCalledWith(
|
|
52
|
+
3,
|
|
53
|
+
"discord gateway: Gateway forcing fresh IDENTIFY after 3 failed resume attempts",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
cleanup();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("logs warnings and metrics only to verbose", () => {
|
|
60
|
+
const emitter = new EventEmitter();
|
|
61
|
+
const runtime = makeRuntime();
|
|
62
|
+
|
|
63
|
+
const cleanup = attachDiscordGatewayLogging({
|
|
64
|
+
emitter,
|
|
65
|
+
runtime,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
emitter.emit("warning", "High latency detected: 1200ms");
|
|
69
|
+
emitter.emit("metrics", { latency: 42, errors: 1 });
|
|
70
|
+
|
|
71
|
+
const logVerboseMock = vi.mocked(logVerbose);
|
|
72
|
+
expect(logVerboseMock).toHaveBeenCalledTimes(2);
|
|
73
|
+
expect(runtime.log).not.toHaveBeenCalled();
|
|
74
|
+
|
|
75
|
+
cleanup();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("removes listeners on cleanup", () => {
|
|
79
|
+
const emitter = new EventEmitter();
|
|
80
|
+
const runtime = makeRuntime();
|
|
81
|
+
|
|
82
|
+
const cleanup = attachDiscordGatewayLogging({
|
|
83
|
+
emitter,
|
|
84
|
+
runtime,
|
|
85
|
+
});
|
|
86
|
+
cleanup();
|
|
87
|
+
|
|
88
|
+
const logVerboseMock = vi.mocked(logVerbose);
|
|
89
|
+
logVerboseMock.mockClear();
|
|
90
|
+
|
|
91
|
+
emitter.emit("debug", "Gateway websocket closed: 1001");
|
|
92
|
+
emitter.emit("warning", "High latency detected: 1200ms");
|
|
93
|
+
emitter.emit("metrics", { latency: 42 });
|
|
94
|
+
|
|
95
|
+
expect(logVerboseMock).not.toHaveBeenCalled();
|
|
96
|
+
expect(runtime.log).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EventEmitter } from "node:events";
|
|
2
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
3
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
4
|
+
|
|
5
|
+
type GatewayEmitter = Pick<EventEmitter, "on" | "removeListener">;
|
|
6
|
+
|
|
7
|
+
const INFO_DEBUG_MARKERS = [
|
|
8
|
+
"Gateway websocket closed",
|
|
9
|
+
"Gateway reconnect scheduled in",
|
|
10
|
+
"Gateway forcing fresh IDENTIFY after",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const shouldPromoteGatewayDebug = (message: string) =>
|
|
14
|
+
INFO_DEBUG_MARKERS.some((marker) => message.includes(marker));
|
|
15
|
+
|
|
16
|
+
const formatGatewayMetrics = (metrics: unknown) => {
|
|
17
|
+
if (metrics === null || metrics === undefined) {
|
|
18
|
+
return String(metrics);
|
|
19
|
+
}
|
|
20
|
+
if (typeof metrics === "string") {
|
|
21
|
+
return metrics;
|
|
22
|
+
}
|
|
23
|
+
if (typeof metrics === "number" || typeof metrics === "boolean" || typeof metrics === "bigint") {
|
|
24
|
+
return String(metrics);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(metrics);
|
|
28
|
+
} catch {
|
|
29
|
+
return "[unserializable metrics]";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function attachDiscordGatewayLogging(params: {
|
|
34
|
+
emitter?: GatewayEmitter;
|
|
35
|
+
runtime: RuntimeEnv;
|
|
36
|
+
}) {
|
|
37
|
+
const { emitter, runtime } = params;
|
|
38
|
+
if (!emitter) {
|
|
39
|
+
return () => {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const onGatewayDebug = (msg: unknown) => {
|
|
43
|
+
const message = String(msg);
|
|
44
|
+
logVerbose(`discord gateway: ${message}`);
|
|
45
|
+
if (shouldPromoteGatewayDebug(message)) {
|
|
46
|
+
runtime.log?.(`discord gateway: ${message}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onGatewayWarning = (warning: unknown) => {
|
|
51
|
+
logVerbose(`discord gateway warning: ${String(warning)}`);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const onGatewayMetrics = (metrics: unknown) => {
|
|
55
|
+
logVerbose(`discord gateway metrics: ${formatGatewayMetrics(metrics)}`);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
emitter.on("debug", onGatewayDebug);
|
|
59
|
+
emitter.on("warning", onGatewayWarning);
|
|
60
|
+
emitter.on("metrics", onGatewayMetrics);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
emitter.removeListener("debug", onGatewayDebug);
|
|
64
|
+
emitter.removeListener("warning", onGatewayWarning);
|
|
65
|
+
emitter.removeListener("metrics", onGatewayMetrics);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ChannelGroupContext } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
+
import {
|
|
3
|
+
resolveToolsBySender,
|
|
4
|
+
type GroupToolPolicyBySenderConfig,
|
|
5
|
+
type GroupToolPolicyConfig,
|
|
6
|
+
} from "openclaw/plugin-sdk/channel-policy";
|
|
7
|
+
import { normalizeAtHashSlug } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
8
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
9
|
+
import type { DiscordConfig } from "./runtime-api.js";
|
|
10
|
+
|
|
11
|
+
function normalizeDiscordSlug(value?: string | null) {
|
|
12
|
+
return normalizeAtHashSlug(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type SenderScopedToolsEntry = {
|
|
16
|
+
tools?: GroupToolPolicyConfig;
|
|
17
|
+
toolsBySender?: GroupToolPolicyBySenderConfig;
|
|
18
|
+
requireMention?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function resolveDiscordGuildEntry(guilds: DiscordConfig["guilds"], groupSpace?: string | null) {
|
|
22
|
+
if (!guilds || Object.keys(guilds).length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const space = normalizeOptionalString(groupSpace) ?? "";
|
|
26
|
+
if (space && guilds[space]) {
|
|
27
|
+
return guilds[space];
|
|
28
|
+
}
|
|
29
|
+
const normalized = normalizeDiscordSlug(space);
|
|
30
|
+
if (normalized && guilds[normalized]) {
|
|
31
|
+
return guilds[normalized];
|
|
32
|
+
}
|
|
33
|
+
if (normalized) {
|
|
34
|
+
const match = Object.values(guilds).find(
|
|
35
|
+
(entry) => normalizeDiscordSlug(entry?.slug ?? undefined) === normalized,
|
|
36
|
+
);
|
|
37
|
+
if (match) {
|
|
38
|
+
return match;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return guilds["*"] ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveDiscordChannelEntry<TEntry extends SenderScopedToolsEntry>(
|
|
45
|
+
channelEntries: Record<string, TEntry> | undefined,
|
|
46
|
+
params: { groupId?: string | null; groupChannel?: string | null },
|
|
47
|
+
): TEntry | undefined {
|
|
48
|
+
if (!channelEntries || Object.keys(channelEntries).length === 0) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const groupChannel = params.groupChannel;
|
|
52
|
+
const channelSlug = normalizeDiscordSlug(groupChannel);
|
|
53
|
+
return (
|
|
54
|
+
(params.groupId ? channelEntries[params.groupId] : undefined) ??
|
|
55
|
+
(channelSlug
|
|
56
|
+
? (channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`])
|
|
57
|
+
: undefined) ??
|
|
58
|
+
(groupChannel ? channelEntries[normalizeDiscordSlug(groupChannel)] : undefined)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveSenderToolsEntry(
|
|
63
|
+
entry: SenderScopedToolsEntry | undefined | null,
|
|
64
|
+
params: ChannelGroupContext,
|
|
65
|
+
): GroupToolPolicyConfig | undefined {
|
|
66
|
+
if (!entry) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const senderPolicy = resolveToolsBySender({
|
|
70
|
+
toolsBySender: entry.toolsBySender,
|
|
71
|
+
senderId: params.senderId,
|
|
72
|
+
senderName: params.senderName,
|
|
73
|
+
senderUsername: params.senderUsername,
|
|
74
|
+
senderE164: params.senderE164,
|
|
75
|
+
});
|
|
76
|
+
return senderPolicy ?? entry.tools;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveDiscordPolicyContext(params: ChannelGroupContext) {
|
|
80
|
+
const guilds =
|
|
81
|
+
(params.accountId
|
|
82
|
+
? params.cfg.channels?.discord?.accounts?.[params.accountId]?.guilds
|
|
83
|
+
: undefined) ?? params.cfg.channels?.discord?.guilds;
|
|
84
|
+
const guildEntry = resolveDiscordGuildEntry(guilds, params.groupSpace);
|
|
85
|
+
const channelEntries = guildEntry?.channels;
|
|
86
|
+
const channelEntry =
|
|
87
|
+
channelEntries && Object.keys(channelEntries).length > 0
|
|
88
|
+
? resolveDiscordChannelEntry(channelEntries, params)
|
|
89
|
+
: undefined;
|
|
90
|
+
return { guildEntry, channelEntry };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveDiscordGroupRequireMention(params: ChannelGroupContext): boolean {
|
|
94
|
+
const context = resolveDiscordPolicyContext(params);
|
|
95
|
+
if (typeof context.channelEntry?.requireMention === "boolean") {
|
|
96
|
+
return context.channelEntry.requireMention;
|
|
97
|
+
}
|
|
98
|
+
if (typeof context.guildEntry?.requireMention === "boolean") {
|
|
99
|
+
return context.guildEntry.requireMention;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function resolveDiscordGroupToolPolicy(
|
|
105
|
+
params: ChannelGroupContext,
|
|
106
|
+
): GroupToolPolicyConfig | undefined {
|
|
107
|
+
const context = resolveDiscordPolicyContext(params);
|
|
108
|
+
const channelPolicy = resolveSenderToolsEntry(context.channelEntry, params);
|
|
109
|
+
if (channelPolicy) {
|
|
110
|
+
return channelPolicy;
|
|
111
|
+
}
|
|
112
|
+
return resolveSenderToolsEntry(context.guildEntry, params);
|
|
113
|
+
}
|
package/src/guilds.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { fetchDiscord } from "./api.js";
|
|
2
|
+
import { normalizeDiscordSlug } from "./monitor/allow-list.js";
|
|
3
|
+
|
|
4
|
+
export type DiscordGuildSummary = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function listGuilds(
|
|
11
|
+
token: string,
|
|
12
|
+
fetcher: typeof fetch,
|
|
13
|
+
): Promise<DiscordGuildSummary[]> {
|
|
14
|
+
const raw = await fetchDiscord<Array<{ id?: string; name?: string }>>(
|
|
15
|
+
"/users/@me/guilds",
|
|
16
|
+
token,
|
|
17
|
+
fetcher,
|
|
18
|
+
);
|
|
19
|
+
return raw
|
|
20
|
+
.filter(
|
|
21
|
+
(guild): guild is { id: string; name: string } =>
|
|
22
|
+
typeof guild.id === "string" && typeof guild.name === "string",
|
|
23
|
+
)
|
|
24
|
+
.map((guild) => ({
|
|
25
|
+
id: guild.id,
|
|
26
|
+
name: guild.name,
|
|
27
|
+
slug: normalizeDiscordSlug(guild.name),
|
|
28
|
+
}));
|
|
29
|
+
}
|