@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,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Voice Message Support
|
|
3
|
+
*
|
|
4
|
+
* Implements sending voice messages via Discord's API.
|
|
5
|
+
* Voice messages require:
|
|
6
|
+
* - OGG/Opus format audio
|
|
7
|
+
* - Waveform data (base64 encoded, up to 256 samples, 0-255 values)
|
|
8
|
+
* - Duration in seconds
|
|
9
|
+
* - Message flag 8192 (IS_VOICE_MESSAGE)
|
|
10
|
+
* - No other content (text, embeds, etc.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
import fs from "node:fs/promises";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
17
|
+
import {
|
|
18
|
+
parseFfprobeCodecAndSampleRate,
|
|
19
|
+
runFfmpeg,
|
|
20
|
+
runFfprobe,
|
|
21
|
+
} from "openclaw/plugin-sdk/media-runtime";
|
|
22
|
+
import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS } from "openclaw/plugin-sdk/media-runtime";
|
|
23
|
+
import { unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
|
|
24
|
+
import type { RetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
|
25
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
26
|
+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
27
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
28
|
+
import { DiscordError, RateLimitError, type RequestClient } from "./internal/discord.js";
|
|
29
|
+
import { readDiscordMessage, readRetryAfter } from "./internal/rest-errors.js";
|
|
30
|
+
|
|
31
|
+
const DISCORD_VOICE_MESSAGE_FLAG = 1 << 13;
|
|
32
|
+
const SUPPRESS_NOTIFICATIONS_FLAG = 1 << 12;
|
|
33
|
+
const WAVEFORM_SAMPLES = 256;
|
|
34
|
+
const DISCORD_OPUS_SAMPLE_RATE_HZ = 48_000;
|
|
35
|
+
|
|
36
|
+
function createRateLimitError(
|
|
37
|
+
response: Response,
|
|
38
|
+
body: { message: string; retry_after: number; global: boolean },
|
|
39
|
+
request?: Request,
|
|
40
|
+
): RateLimitError {
|
|
41
|
+
const fallbackRequest =
|
|
42
|
+
request ??
|
|
43
|
+
new Request("https://discord.com/api/v10/channels/voice/messages", {
|
|
44
|
+
method: "POST",
|
|
45
|
+
});
|
|
46
|
+
const RateLimitErrorCtor = RateLimitError as unknown as new (
|
|
47
|
+
response: Response,
|
|
48
|
+
body: { message: string; retry_after: number; global: boolean },
|
|
49
|
+
request?: Request,
|
|
50
|
+
) => RateLimitError;
|
|
51
|
+
return new RateLimitErrorCtor(response, body, fallbackRequest);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type VoiceMessageMetadata = {
|
|
55
|
+
durationSecs: number;
|
|
56
|
+
waveform: string; // base64 encoded
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get audio duration using ffprobe
|
|
61
|
+
*/
|
|
62
|
+
export async function getAudioDuration(filePath: string): Promise<number> {
|
|
63
|
+
try {
|
|
64
|
+
const stdout = await runFfprobe([
|
|
65
|
+
"-v",
|
|
66
|
+
"error",
|
|
67
|
+
"-show_entries",
|
|
68
|
+
"format=duration",
|
|
69
|
+
"-of",
|
|
70
|
+
"csv=p=0",
|
|
71
|
+
filePath,
|
|
72
|
+
]);
|
|
73
|
+
const duration = Number.parseFloat(stdout.trim());
|
|
74
|
+
if (Number.isNaN(duration)) {
|
|
75
|
+
throw new Error("Could not parse duration");
|
|
76
|
+
}
|
|
77
|
+
return Math.round(duration * 100) / 100; // Round to 2 decimal places
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const errMessage = formatErrorMessage(err);
|
|
80
|
+
throw new Error(`Failed to get audio duration: ${errMessage}`, { cause: err });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate waveform data from audio file using ffmpeg
|
|
86
|
+
* Returns base64 encoded byte array of amplitude samples (0-255)
|
|
87
|
+
*/
|
|
88
|
+
export async function generateWaveform(filePath: string): Promise<string> {
|
|
89
|
+
try {
|
|
90
|
+
// Extract raw PCM and sample amplitude values
|
|
91
|
+
return await generateWaveformFromPcm(filePath);
|
|
92
|
+
} catch {
|
|
93
|
+
// If PCM extraction fails, generate a placeholder waveform
|
|
94
|
+
return generatePlaceholderWaveform();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate waveform by extracting raw PCM data and sampling amplitudes
|
|
100
|
+
*/
|
|
101
|
+
async function generateWaveformFromPcm(filePath: string): Promise<string> {
|
|
102
|
+
const tempDir = resolvePreferredOpenClawTmpDir();
|
|
103
|
+
const tempPcm = path.join(tempDir, `waveform-${crypto.randomUUID()}.raw`);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Convert to raw 16-bit signed PCM, mono, 8kHz
|
|
107
|
+
await runFfmpeg([
|
|
108
|
+
"-y",
|
|
109
|
+
"-i",
|
|
110
|
+
filePath,
|
|
111
|
+
"-vn",
|
|
112
|
+
"-sn",
|
|
113
|
+
"-dn",
|
|
114
|
+
"-t",
|
|
115
|
+
String(MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS),
|
|
116
|
+
"-f",
|
|
117
|
+
"s16le",
|
|
118
|
+
"-acodec",
|
|
119
|
+
"pcm_s16le",
|
|
120
|
+
"-ac",
|
|
121
|
+
"1",
|
|
122
|
+
"-ar",
|
|
123
|
+
"8000",
|
|
124
|
+
tempPcm,
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const pcmData = await fs.readFile(tempPcm);
|
|
128
|
+
const samples = new Int16Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 2);
|
|
129
|
+
|
|
130
|
+
// Sample the PCM data to get WAVEFORM_SAMPLES points
|
|
131
|
+
const step = Math.max(1, Math.floor(samples.length / WAVEFORM_SAMPLES));
|
|
132
|
+
const waveform: number[] = [];
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < WAVEFORM_SAMPLES && i * step < samples.length; i++) {
|
|
135
|
+
// Get average absolute amplitude for this segment
|
|
136
|
+
let sum = 0;
|
|
137
|
+
let count = 0;
|
|
138
|
+
for (let j = 0; j < step && i * step + j < samples.length; j++) {
|
|
139
|
+
sum += Math.abs(samples[i * step + j]);
|
|
140
|
+
count++;
|
|
141
|
+
}
|
|
142
|
+
const avg = count > 0 ? sum / count : 0;
|
|
143
|
+
// Normalize to 0-255 (16-bit signed max is 32767)
|
|
144
|
+
const normalized = Math.min(255, Math.round((avg / 32767) * 255));
|
|
145
|
+
waveform.push(normalized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Pad with zeros if we don't have enough samples
|
|
149
|
+
while (waveform.length < WAVEFORM_SAMPLES) {
|
|
150
|
+
waveform.push(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Buffer.from(waveform).toString("base64");
|
|
154
|
+
} finally {
|
|
155
|
+
await unlinkIfExists(tempPcm);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate a placeholder waveform (for when audio processing fails)
|
|
161
|
+
*/
|
|
162
|
+
function generatePlaceholderWaveform(): string {
|
|
163
|
+
// Generate a simple sine-wave-like pattern
|
|
164
|
+
const waveform: number[] = [];
|
|
165
|
+
for (let i = 0; i < WAVEFORM_SAMPLES; i++) {
|
|
166
|
+
const value = Math.round(128 + 64 * Math.sin((i / WAVEFORM_SAMPLES) * Math.PI * 8));
|
|
167
|
+
waveform.push(Math.min(255, Math.max(0, value)));
|
|
168
|
+
}
|
|
169
|
+
return Buffer.from(waveform).toString("base64");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Convert audio file to OGG/Opus format if needed
|
|
174
|
+
* Returns path to the OGG file (may be same as input if already OGG/Opus)
|
|
175
|
+
*/
|
|
176
|
+
export async function ensureOggOpus(filePath: string): Promise<{ path: string; cleanup: boolean }> {
|
|
177
|
+
const trimmed = filePath.trim();
|
|
178
|
+
// Defense-in-depth: callers should never hand ffmpeg/ffprobe a URL/protocol path.
|
|
179
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Voice message conversion requires a local file path; received a URL/protocol source: ${trimmed}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
|
|
186
|
+
|
|
187
|
+
// Check if already OGG
|
|
188
|
+
if (ext === ".ogg") {
|
|
189
|
+
// Fast-path only when the file is Opus at Discord's expected 48kHz.
|
|
190
|
+
try {
|
|
191
|
+
const stdout = await runFfprobe([
|
|
192
|
+
"-v",
|
|
193
|
+
"error",
|
|
194
|
+
"-select_streams",
|
|
195
|
+
"a:0",
|
|
196
|
+
"-show_entries",
|
|
197
|
+
"stream=codec_name,sample_rate",
|
|
198
|
+
"-of",
|
|
199
|
+
"csv=p=0",
|
|
200
|
+
filePath,
|
|
201
|
+
]);
|
|
202
|
+
const { codec, sampleRateHz } = parseFfprobeCodecAndSampleRate(stdout);
|
|
203
|
+
if (codec === "opus" && sampleRateHz === DISCORD_OPUS_SAMPLE_RATE_HZ) {
|
|
204
|
+
return { path: filePath, cleanup: false };
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// If probe fails, convert anyway
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Convert to OGG/Opus
|
|
212
|
+
// Always resample to 48kHz to ensure Discord voice messages play at correct speed
|
|
213
|
+
// (Discord expects 48kHz; lower sample rates like 24kHz from some TTS providers cause 0.5x playback)
|
|
214
|
+
const tempDir = resolvePreferredOpenClawTmpDir();
|
|
215
|
+
const outputPath = path.join(tempDir, `voice-${crypto.randomUUID()}.ogg`);
|
|
216
|
+
|
|
217
|
+
await runFfmpeg([
|
|
218
|
+
"-y",
|
|
219
|
+
"-i",
|
|
220
|
+
filePath,
|
|
221
|
+
"-vn",
|
|
222
|
+
"-sn",
|
|
223
|
+
"-dn",
|
|
224
|
+
"-t",
|
|
225
|
+
String(MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS),
|
|
226
|
+
"-ar",
|
|
227
|
+
String(DISCORD_OPUS_SAMPLE_RATE_HZ),
|
|
228
|
+
"-c:a",
|
|
229
|
+
"libopus",
|
|
230
|
+
"-b:a",
|
|
231
|
+
"64k",
|
|
232
|
+
outputPath,
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
return { path: outputPath, cleanup: true };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get voice message metadata (duration and waveform)
|
|
240
|
+
*/
|
|
241
|
+
export async function getVoiceMessageMetadata(filePath: string): Promise<VoiceMessageMetadata> {
|
|
242
|
+
const [durationSecs, waveform] = await Promise.all([
|
|
243
|
+
getAudioDuration(filePath),
|
|
244
|
+
generateWaveform(filePath),
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
return { durationSecs, waveform };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
type UploadUrlResponse = {
|
|
251
|
+
attachments: Array<{
|
|
252
|
+
id: number;
|
|
253
|
+
upload_url: string;
|
|
254
|
+
upload_filename: string;
|
|
255
|
+
}>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
function coerceDiscordErrorBody(raw: string): unknown {
|
|
259
|
+
if (!raw) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(raw);
|
|
264
|
+
} catch {
|
|
265
|
+
return { message: raw.slice(0, 200) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function createVoiceRequestError(
|
|
270
|
+
response: Response,
|
|
271
|
+
fallbackMessage: string,
|
|
272
|
+
): Promise<Error> {
|
|
273
|
+
const raw = await response.text().catch(() => "");
|
|
274
|
+
const parsed = coerceDiscordErrorBody(raw);
|
|
275
|
+
if (response.status === 429) {
|
|
276
|
+
throw createRateLimitError(response, {
|
|
277
|
+
message: readDiscordMessage(parsed, "You are being rate limited."),
|
|
278
|
+
retry_after: readRetryAfter(parsed, response, 1),
|
|
279
|
+
global:
|
|
280
|
+
parsed && typeof parsed === "object" && "global" in parsed
|
|
281
|
+
? Boolean((parsed as { global?: unknown }).global)
|
|
282
|
+
: false,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return new DiscordError(
|
|
286
|
+
response,
|
|
287
|
+
parsed ?? {
|
|
288
|
+
message: fallbackMessage,
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function requestVoiceUploadUrl(params: {
|
|
294
|
+
rest: RequestClient;
|
|
295
|
+
channelId: string;
|
|
296
|
+
botToken: string;
|
|
297
|
+
filename: string;
|
|
298
|
+
fileSize: number;
|
|
299
|
+
}): Promise<UploadUrlResponse> {
|
|
300
|
+
const url = `${params.rest.options?.baseUrl ?? "https://discord.com/api"}/channels/${params.channelId}/attachments`;
|
|
301
|
+
const uploadUrlInit: RequestInit = {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
Authorization: `Bot ${params.botToken}`,
|
|
305
|
+
"Content-Type": "application/json",
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
files: [{ filename: params.filename, file_size: params.fileSize, id: "0" }],
|
|
309
|
+
}),
|
|
310
|
+
};
|
|
311
|
+
const { response: res, release } = await fetchWithSsrFGuard({
|
|
312
|
+
url,
|
|
313
|
+
init: uploadUrlInit,
|
|
314
|
+
auditContext: "discord.voice.upload-url",
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
if (!res.ok) {
|
|
318
|
+
throw await createVoiceRequestError(res, "Upload URL request failed");
|
|
319
|
+
}
|
|
320
|
+
return (await res.json()) as UploadUrlResponse;
|
|
321
|
+
} finally {
|
|
322
|
+
await release();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function uploadVoiceAttachment(params: {
|
|
327
|
+
uploadUrl: string;
|
|
328
|
+
audioBuffer: Buffer;
|
|
329
|
+
}): Promise<void> {
|
|
330
|
+
const { response: uploadResponse, release } = await fetchWithSsrFGuard({
|
|
331
|
+
url: params.uploadUrl,
|
|
332
|
+
init: {
|
|
333
|
+
method: "PUT",
|
|
334
|
+
headers: {
|
|
335
|
+
"Content-Type": "audio/ogg",
|
|
336
|
+
},
|
|
337
|
+
body: new Uint8Array(params.audioBuffer),
|
|
338
|
+
},
|
|
339
|
+
auditContext: "discord.voice.attachment-upload",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
if (!uploadResponse.ok) {
|
|
344
|
+
throw await createVoiceRequestError(uploadResponse, "Failed to upload voice message");
|
|
345
|
+
}
|
|
346
|
+
} finally {
|
|
347
|
+
await release();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Send a voice message to Discord
|
|
353
|
+
*
|
|
354
|
+
* This follows Discord's voice message protocol:
|
|
355
|
+
* 1. Request upload URL from Discord
|
|
356
|
+
* 2. Upload the OGG file to the provided URL
|
|
357
|
+
* 3. Send the message with flag 8192 and attachment metadata
|
|
358
|
+
*/
|
|
359
|
+
export async function sendDiscordVoiceMessage(
|
|
360
|
+
rest: RequestClient,
|
|
361
|
+
channelId: string,
|
|
362
|
+
audioBuffer: Buffer,
|
|
363
|
+
metadata: VoiceMessageMetadata,
|
|
364
|
+
replyTo: string | undefined,
|
|
365
|
+
request: RetryRunner,
|
|
366
|
+
silent?: boolean,
|
|
367
|
+
token?: string,
|
|
368
|
+
): Promise<{ id: string; channel_id: string }> {
|
|
369
|
+
const filename = "voice-message.ogg";
|
|
370
|
+
const fileSize = audioBuffer.byteLength;
|
|
371
|
+
|
|
372
|
+
// Step 1: Request upload URL from Discord
|
|
373
|
+
// RequestClient auto-converts "files" bodies to multipart/form-data, but Discord's
|
|
374
|
+
// /attachments endpoint expects JSON, so this path uses a guarded raw HTTP call.
|
|
375
|
+
const botToken = token;
|
|
376
|
+
if (!botToken) {
|
|
377
|
+
throw new Error("Discord bot token is required for voice message upload");
|
|
378
|
+
}
|
|
379
|
+
const { upload_filename } = await request(async () => {
|
|
380
|
+
const uploadUrlResponse = await requestVoiceUploadUrl({
|
|
381
|
+
rest,
|
|
382
|
+
channelId,
|
|
383
|
+
botToken,
|
|
384
|
+
filename,
|
|
385
|
+
fileSize,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (!uploadUrlResponse.attachments?.[0]) {
|
|
389
|
+
throw new Error("Failed to get upload URL for voice message");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const attachment = uploadUrlResponse.attachments[0];
|
|
393
|
+
await uploadVoiceAttachment({
|
|
394
|
+
uploadUrl: attachment.upload_url,
|
|
395
|
+
audioBuffer,
|
|
396
|
+
});
|
|
397
|
+
return attachment;
|
|
398
|
+
}, "voice-upload");
|
|
399
|
+
|
|
400
|
+
// Step 3: Send the message with voice message flag and metadata
|
|
401
|
+
const flags = silent
|
|
402
|
+
? DISCORD_VOICE_MESSAGE_FLAG | SUPPRESS_NOTIFICATIONS_FLAG
|
|
403
|
+
: DISCORD_VOICE_MESSAGE_FLAG;
|
|
404
|
+
const messagePayload: {
|
|
405
|
+
flags: number;
|
|
406
|
+
attachments: Array<{
|
|
407
|
+
id: string;
|
|
408
|
+
filename: string;
|
|
409
|
+
uploaded_filename: string;
|
|
410
|
+
duration_secs: number;
|
|
411
|
+
waveform: string;
|
|
412
|
+
}>;
|
|
413
|
+
message_reference?: { message_id: string; fail_if_not_exists: boolean };
|
|
414
|
+
} = {
|
|
415
|
+
flags,
|
|
416
|
+
attachments: [
|
|
417
|
+
{
|
|
418
|
+
id: "0",
|
|
419
|
+
filename,
|
|
420
|
+
uploaded_filename: upload_filename,
|
|
421
|
+
duration_secs: metadata.durationSecs,
|
|
422
|
+
waveform: metadata.waveform,
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// Note: Voice messages cannot have content, but can have message_reference for replies
|
|
428
|
+
if (replyTo) {
|
|
429
|
+
messagePayload.message_reference = {
|
|
430
|
+
message_id: replyTo,
|
|
431
|
+
fail_if_not_exists: false,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const res = (await request(
|
|
436
|
+
() =>
|
|
437
|
+
rest.post(`/channels/${channelId}/messages`, {
|
|
438
|
+
body: messagePayload,
|
|
439
|
+
}) as Promise<{ id: string; channel_id: string }>,
|
|
440
|
+
"voice-message",
|
|
441
|
+
)) as { id: string; channel_id: string };
|
|
442
|
+
|
|
443
|
+
return res;
|
|
444
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
|
|
3
|
+
type DiscordSubagentHooksModule = typeof import("./src/subagent-hooks.js");
|
|
4
|
+
|
|
5
|
+
let discordSubagentHooksPromise: Promise<DiscordSubagentHooksModule> | null = null;
|
|
6
|
+
|
|
7
|
+
function loadDiscordSubagentHooksModule() {
|
|
8
|
+
discordSubagentHooksPromise ??= import("./src/subagent-hooks.js");
|
|
9
|
+
return discordSubagentHooksPromise;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Subagent hooks live behind a dedicated barrel so the bundled entry can
|
|
13
|
+
// register one stable hook wiring path while keeping the handler module lazy.
|
|
14
|
+
export function registerDiscordSubagentHooks(api: OpenClawPluginApi): void {
|
|
15
|
+
api.on("subagent_spawning", async (event) => {
|
|
16
|
+
const { handleDiscordSubagentSpawning } = await loadDiscordSubagentHooksModule();
|
|
17
|
+
return await handleDiscordSubagentSpawning(api, event);
|
|
18
|
+
});
|
|
19
|
+
api.on("subagent_ended", async (event) => {
|
|
20
|
+
const { handleDiscordSubagentEnded } = await loadDiscordSubagentHooksModule();
|
|
21
|
+
handleDiscordSubagentEnded(event);
|
|
22
|
+
});
|
|
23
|
+
api.on("subagent_delivery_target", async (event) => {
|
|
24
|
+
const { handleDiscordSubagentDeliveryTarget } = await loadDiscordSubagentHooksModule();
|
|
25
|
+
return handleDiscordSubagentDeliveryTarget(event);
|
|
26
|
+
});
|
|
27
|
+
}
|
package/test-api.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { discordPlugin } from "./src/channel.js";
|
|
2
|
+
export { buildFinalizedDiscordDirectInboundContext } from "./src/monitor/inbound-context.test-helpers.js";
|
|
3
|
+
export { __testing as discordThreadBindingTesting } from "./src/monitor/thread-bindings.manager.js";
|
|
4
|
+
export { discordOutbound } from "./src/outbound-adapter.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const defaultTopLevelPlacement = "child" as const;
|
package/timeouts.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.package-boundary.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "."
|
|
5
|
+
},
|
|
6
|
+
"include": ["./*.ts", "./src/**/*.ts"],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"./**/*.test.ts",
|
|
9
|
+
"./dist/**",
|
|
10
|
+
"./node_modules/**",
|
|
11
|
+
"./src/test-support/**",
|
|
12
|
+
"./src/**/*test-helpers.ts",
|
|
13
|
+
"./src/**/*test-harness.ts",
|
|
14
|
+
"./src/**/*test-support.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|