@openclaw/discord 2026.9.1-beta.1 → 2026.9.2

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.
Files changed (165) hide show
  1. package/dist/account-inspect-BY-ve3yH.js +45 -0
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CfQ-vSyf.js → accounts-CwJQoLjM.js} +89 -45
  4. package/dist/action-runtime-api.js +1 -1
  5. package/dist/{activities-api-q-9PaaHH.js → activities-api-CTcGq3Hf.js} +30 -17
  6. package/dist/activities-api.js +1 -1
  7. package/dist/{allow-list-BDMimASD.js → allow-list-DBTtR2dz.js} +13 -35
  8. package/dist/{api-DNVbJyEy.js → api-BfmEP6wp.js} +6 -5
  9. package/dist/api.js +18 -18
  10. package/dist/{approval-handler.runtime-B7qhK3fR.js → approval-handler.runtime-DUSe5fyH.js} +19 -12
  11. package/dist/{audit-CBEv_ERu.js → audit-DpPfwoJM.js} +8 -7
  12. package/dist/{channel-Dx1tMTfx.js → channel-BHajPgwR.js} +29 -23
  13. package/dist/{channel-actions.runtime-CLWWu9nC.js → channel-actions.runtime-DS8pXo0S.js} +38 -38
  14. package/dist/channel-config-api.js +1 -1
  15. package/dist/channel-plugin-api.js +1 -1
  16. package/dist/{channel.setup-DOzXS2Pk.js → channel.setup-BWmMe1tT.js} +18 -18
  17. package/dist/command-owners-D78sAoOz.js +10 -0
  18. package/dist/{components-B9V5o361.js → components-BimX47Mq.js} +111 -149
  19. package/dist/{config-schema-BR13IAx6.js → config-schema-B797-Spt.js} +19 -12
  20. package/dist/{conversation-binding-route-DJYrY2TQ.js → conversation-binding-route-DuR5NGIx.js} +1 -1
  21. package/dist/{directory-config-BK6eqekJ.js → directory-config-CD2v2Szs.js} +2 -2
  22. package/dist/directory-contract-api.js +1 -1
  23. package/dist/{directory-live-DSAJSrJk.js → directory-live-DaJzWJ2N.js} +4 -4
  24. package/dist/{doctor-Cm7Y1pJ2.js → doctor-D4HH1inA.js} +9 -6
  25. package/dist/{doctor-contract-DsWvKIXF.js → doctor-contract-Co5vRCZ6.js} +9 -6
  26. package/dist/doctor-contract-api.js +31 -28
  27. package/dist/{handle-action.guild-admin-BdwCRHmm.js → handle-action.guild-admin-4We6wWQU.js} +6 -3
  28. package/dist/index.js +1 -1
  29. package/dist/ingress-identity-XNmW-mZS.js +60 -0
  30. package/dist/{message-handler-BXpWZyni.js → message-handler-DhgBabyU.js} +61 -70
  31. package/dist/{message-handler.preflight-Ba5LAY_X.js → message-handler.preflight-oZ7dQCd0.js} +41 -101
  32. package/dist/{message-handler.process-3HEzmemS.js → message-handler.process-BzCYf1pa.js} +82 -108
  33. package/dist/{message-text-aiwO5R2F.js → message-text-DK4NnBg-.js} +11 -6
  34. package/dist/{outbound-adapter-BE4jSq2d.js → outbound-adapter-CtggmGh9.js} +52 -67
  35. package/dist/{outbound-components-oLpg8Qzb.js → outbound-components-Dn8SJWMS.js} +5 -4
  36. package/dist/{outbound-session-route-Cg15ZFJd.js → outbound-session-route-Q_HEusBQ.js} +57 -15
  37. package/dist/{owner-access-UlYi9t0M.js → owner-access-Cp8r6Ipf.js} +9 -7
  38. package/dist/{pluralkit-Tiyl70dx.js → pluralkit-BY75YcK0.js} +2 -2
  39. package/dist/{probe-n6ZXxK8V.js → probe-3ovFAp3b.js} +3 -3
  40. package/dist/{probe.runtime-CN4bdPfZ.js → probe.runtime-BckHn3cJ.js} +1 -1
  41. package/dist/{provider-session.runtime-B9xe-Qaf.js → provider-session.runtime-BL2lHBH7.js} +3 -3
  42. package/dist/{provider-B11xwilq.js → provider-uKmmrDM4.js} +299 -136
  43. package/dist/provider.runtime-C-IkWF2w.js +2 -0
  44. package/dist/{realtime-session.runtime-CU8IQUS-.js → realtime-session.runtime-CGYm1t_0.js} +798 -448
  45. package/dist/{resolve-allowlist-common-BLoYaxEG.js → resolve-allowlist-common-sKIKy-cF.js} +3 -3
  46. package/dist/{resolve-channels-C2sT_n67.js → resolve-channels-CoiMlWy0.js} +4 -4
  47. package/dist/{resolve-users-CzpjLwjy.js → resolve-users-DJpiokfW.js} +5 -4
  48. package/dist/{retry-Cv4jCU5Z.js → retry-DQt1zDgH.js} +356 -214
  49. package/dist/{retry-after-DdLPmPoa.js → retry-after-BrLjLUJi.js} +2 -1
  50. package/dist/{runtime-CY4PRMF-.js → runtime-BHsdEdr5.js} +2 -2
  51. package/dist/{runtime-BHrTlFkf.js → runtime-Ghtfh5ZA.js} +151 -109
  52. package/dist/runtime-api.js +20 -20
  53. package/dist/{secret-config-contract-B3i6WxV5.js → secret-config-contract-DFu9hfU8.js} +14 -6
  54. package/dist/secret-contract-api.js +1 -1
  55. package/dist/{security-audit-BfJhAXZp.js → security-audit-DKfK-iPY.js} +10 -11
  56. package/dist/security-audit-contract-api.js +1 -1
  57. package/dist/{security-audit.runtime-BVKF9VtY.js → security-audit.runtime-sKZyKgpx.js} +1 -1
  58. package/dist/{send-C49MFF9j.js → send-CZeWKUiA.js} +132 -100
  59. package/dist/{send.components-DKKAw5VX.js → send.components-BG5IDABj.js} +21 -15
  60. package/dist/{send.outbound-zam0TrZx.js → send.outbound-DHuFUL6W.js} +42 -63
  61. package/dist/{send.shared-JjljcV4O.js → send.shared-B0n_fWzu.js} +77 -149
  62. package/dist/{sender-identity-BJtfyise.js → sender-identity-B8Ncfi63.js} +1 -1
  63. package/dist/setup-plugin-api.js +1 -1
  64. package/dist/{shared-_5EcP4jW.js → shared-C3dKun_X.js} +24 -15
  65. package/dist/{subagent-hooks-DQzvOptn.js → subagent-hooks-ZoQXEI1E.js} +2 -2
  66. package/dist/subagent-hooks-api.js +1 -1
  67. package/dist/{system-events-C7yxaHWP.js → system-events-DFF_awl7.js} +2 -2
  68. package/dist/{target-resolver-vxIh6O3-.js → target-resolver-DLtHDwMK.js} +6 -5
  69. package/dist/targets-BObRyaKX.js +3 -0
  70. package/dist/{thread-bindings-DW5F9vat.js → thread-bindings-DKvsNjQR.js} +7 -6
  71. package/dist/{thread-bindings.manager-ByOtwjo_.js → thread-bindings.manager-pbbRT5gb.js} +38 -29
  72. package/dist/{thread-bindings.session-updates-CJb-BpPL.js → thread-bindings.session-updates-t6jeFB1-.js} +3 -2
  73. package/dist/{thread-bindings.state-D8OMdk4O.js → thread-bindings.state-DtgOqsbX.js} +34 -1
  74. package/dist/{threading-agH1T_6s.js → threading-BCx49X-6.js} +8 -6
  75. package/dist/{timeouts-B9cy6RvQ.js → timeouts-D86uWLt_.js} +1 -1
  76. package/dist/{transcripts-source-BR11iHA4.js → transcripts-source-Dc6Nnli5.js} +55 -7
  77. package/dist/transcripts-source-api.js +1 -1
  78. package/dist/{typing-Dc_YMY2e.js → typing-CuopV-Hi.js} +2 -2
  79. package/dist/{voice-runtime-DIeaa8fJ.js → voice-runtime-BQnILUbG.js} +492 -105
  80. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/README.md +1 -1
  81. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/conforms.d.ts +2 -0
  82. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/package.json +3 -3
  83. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubArray.d.ts +2 -0
  84. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubObject.d.ts +2 -0
  85. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubString.d.ts +2 -0
  86. package/node_modules/@discordjs/voice/node_modules/@types/node/README.md +1 -1
  87. package/node_modules/@discordjs/voice/node_modules/@types/node/buffer.buffer.d.ts +5 -5
  88. package/node_modules/@discordjs/voice/node_modules/@types/node/crypto.d.ts +2 -2
  89. package/node_modules/@discordjs/voice/node_modules/@types/node/http.d.ts +16 -0
  90. package/node_modules/@discordjs/voice/node_modules/@types/node/http2.d.ts +10 -2
  91. package/node_modules/@discordjs/voice/node_modules/@types/node/package.json +2 -2
  92. package/node_modules/@discordjs/voice/node_modules/@types/node/process.d.ts +52 -0
  93. package/node_modules/@discordjs/voice/node_modules/@types/node/quic.d.ts +210 -28
  94. package/node_modules/@discordjs/voice/node_modules/@types/node/sqlite.d.ts +6 -2
  95. package/node_modules/@discordjs/voice/node_modules/@types/node/test.d.ts +36 -0
  96. package/node_modules/typebox/build/format/email.mjs +1 -1
  97. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  98. package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
  99. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  100. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  101. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  102. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  103. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  104. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  105. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  106. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  107. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  108. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  109. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  110. package/node_modules/typebox/package.json +1 -1
  111. package/node_modules/undici/docs/docs/api/Client.md +4 -0
  112. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +3 -2
  113. package/node_modules/undici/docs/docs/api/EventSource.md +4 -0
  114. package/node_modules/undici/docs/docs/api/Interceptors.md +7 -3
  115. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +7 -1
  116. package/node_modules/undici/lib/cache/memory-cache-store.js +2 -2
  117. package/node_modules/undici/lib/core/symbols.js +2 -0
  118. package/node_modules/undici/lib/dispatcher/agent.js +7 -2
  119. package/node_modules/undici/lib/dispatcher/balanced-pool.js +7 -4
  120. package/node_modules/undici/lib/dispatcher/client-h1.js +12 -4
  121. package/node_modules/undici/lib/dispatcher/client-h2.js +43 -3
  122. package/node_modules/undici/lib/dispatcher/client.js +3 -2
  123. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +13 -1
  124. package/node_modules/undici/lib/dispatcher/dispatcher.js +15 -1
  125. package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +3 -0
  126. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +10 -3
  127. package/node_modules/undici/lib/dispatcher/proxy-agent.js +18 -1
  128. package/node_modules/undici/lib/dispatcher/retry-agent.js +3 -0
  129. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +1 -1
  130. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +91 -21
  131. package/node_modules/undici/lib/handler/cache-handler.js +29 -5
  132. package/node_modules/undici/lib/handler/decorator-handler.js +7 -1
  133. package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
  134. package/node_modules/undici/lib/handler/redirect-handler.js +21 -4
  135. package/node_modules/undici/lib/handler/retry-handler.js +148 -30
  136. package/node_modules/undici/lib/interceptor/cache.js +65 -12
  137. package/node_modules/undici/lib/interceptor/decompress.js +115 -13
  138. package/node_modules/undici/lib/interceptor/deduplicate.js +5 -4
  139. package/node_modules/undici/lib/interceptor/dns.js +7 -0
  140. package/node_modules/undici/lib/interceptor/dump.js +10 -23
  141. package/node_modules/undici/lib/mock/mock-agent.js +13 -1
  142. package/node_modules/undici/lib/mock/mock-utils.js +11 -10
  143. package/node_modules/undici/lib/util/cache.js +45 -3
  144. package/node_modules/undici/lib/web/cookies/parse.js +150 -147
  145. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
  146. package/node_modules/undici/lib/web/eventsource/eventsource.js +13 -3
  147. package/node_modules/undici/lib/web/fetch/index.js +6 -0
  148. package/node_modules/undici/lib/web/fetch/request.js +2 -3
  149. package/node_modules/undici/lib/web/fetch/util.js +6 -1
  150. package/node_modules/undici/lib/web/webidl/index.js +3 -3
  151. package/node_modules/undici/lib/web/websocket/connection.js +1 -1
  152. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
  153. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
  154. package/node_modules/undici/package.json +3 -3
  155. package/node_modules/undici/types/client.d.ts +10 -0
  156. package/node_modules/undici/types/interceptors.d.ts +2 -0
  157. package/node_modules/undici/types/mock-agent.d.ts +11 -10
  158. package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -0
  159. package/openclaw.plugin.json +15 -8
  160. package/package.json +7 -6
  161. package/skills/discord/SKILL.md +1 -1
  162. package/dist/account-inspect-BDfKyuvL.js +0 -89
  163. package/dist/provider.runtime-DiYaHvOz.js +0 -2
  164. package/dist/sdk-runtime-4geQHtXc.js +0 -338
  165. package/dist/targets-CtTKvntJ.js +0 -3
@@ -1,24 +1,368 @@
1
- import { D as ReadyListener, L as isUnknownDiscordVoiceStateError, O as ResumedListener, Yt as getGuildVoiceState, _ as parseDiscordTarget, j as VoiceStateUpdateListener, y as discord_exports } from "./retry-Cv4jCU5Z.js";
1
+ import { M as VoiceStateUpdateListener, O as ReadyListener, R as isUnknownDiscordVoiceStateError, Xt as getGuildVoiceState, b as discord_exports, k as ResumedListener, sn as __exportAll, v as parseDiscordTarget } from "./retry-DQt1zDgH.js";
2
2
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
3
- import { a as normalizeDiscordSlug, h as resolveDiscordOwnerAccess, x as formatDiscordUserTag } from "./allow-list-BDMimASD.js";
4
- import { a as formatMention } from "./send.outbound-zam0TrZx.js";
5
- import { n as resolveDiscordVoiceEnabled, r as authorizeDiscordVoiceIngress, t as resolveDiscordVoiceAccess } from "./owner-access-UlYi9t0M.js";
6
- import { p as buildDiscordGroupSystemPrompt, y as resolveFetchedDiscordThreadLikeChannelContext } from "./provider-B11xwilq.js";
7
- import { _ as decodeOpusStreamChunks, a as VOICE_CONNECT_READY_TIMEOUT_MS, b as maybeControlDiscordVoiceAgentRun, c as isVoiceChannel, d as resolveVoiceTimeoutMs, g as decodeOpusStream, h as createDiscordOpusPlaybackStream, i as PLAYBACK_READY_TIMEOUT_MS, l as logVoiceVerbose, n as CAPTURE_FINALIZE_GRACE_MS, o as VOICE_RECONNECT_GRACE_MS, s as isDiscordRealtimeVoiceMode, t as loadDiscordVoiceSdk, u as resolveDiscordVoiceMode, v as writeVoiceWavFile, x as formatVoiceLogPreview, y as formatVoiceIngressPrompt } from "./sdk-runtime-4geQHtXc.js";
3
+ import { a as normalizeDiscordSlug, b as formatDiscordUserTag, m as resolveDiscordOwnerAccess } from "./allow-list-DBTtR2dz.js";
4
+ import { a as formatMention } from "./send.outbound-DHuFUL6W.js";
5
+ import { n as resolveDiscordVoiceEnabled, r as authorizeDiscordVoiceIngress, t as resolveDiscordVoiceAccess } from "./owner-access-Cp8r6Ipf.js";
6
+ import { b as resolveFetchedDiscordThreadLikeChannelContext, m as buildDiscordGroupSystemPrompt } from "./provider-uKmmrDM4.js";
7
+ import { createRequire } from "node:module";
8
+ import fs from "node:fs/promises";
8
9
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
9
10
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
11
+ import { readByteStreamWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
10
12
  import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
11
13
  import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
12
14
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
13
- import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
15
+ import { createSubsystemLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
14
16
  import { escapeRegExp, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
15
17
  import { stripInlineDirectiveTagsForDisplay } from "openclaw/plugin-sdk/text-chunking";
16
- import { unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
18
+ import { resolveFfmpegBin, unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
19
+ import { resolvePreferredOpenClawTmpDir, tempWorkspace } from "openclaw/plugin-sdk/temp-path";
17
20
  import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
18
21
  import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
19
- import { Readable } from "node:stream";
20
- import { OpusError } from "libopus-wasm";
22
+ import { controlRealtimeVoiceAgentRun, resamplePcm, shouldAutoControlRealtimeVoiceAgentText } from "openclaw/plugin-sdk/realtime-voice";
23
+ import { spawn } from "node:child_process";
24
+ import { Readable, Transform } from "node:stream";
25
+ import { StringDecoder } from "node:string_decoder";
26
+ import { Application, OpusError, createDecoder, createEncoder } from "libopus-wasm";
21
27
  import { resolveRealtimeBootstrapContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
28
+ //#region extensions/discord/src/voice/sdk-runtime.ts
29
+ let cachedDiscordVoiceSdk = null;
30
+ function loadDiscordVoiceSdk() {
31
+ if (cachedDiscordVoiceSdk) return cachedDiscordVoiceSdk;
32
+ cachedDiscordVoiceSdk = createRequire(import.meta.url)("@discordjs/voice");
33
+ return cachedDiscordVoiceSdk;
34
+ }
35
+ //#endregion
36
+ //#region extensions/discord/src/voice/log-preview.ts
37
+ const DISCORD_VOICE_LOG_PREVIEW_CHARS = 500;
38
+ function formatVoiceLogPreview(text) {
39
+ const oneLine = text.replace(/\s+/g, " ").trim();
40
+ if (oneLine.length <= DISCORD_VOICE_LOG_PREVIEW_CHARS) return oneLine;
41
+ return `${truncateUtf16Safe(oneLine, DISCORD_VOICE_LOG_PREVIEW_CHARS)}...`;
42
+ }
43
+ //#endregion
44
+ //#region extensions/discord/src/voice/agent-control.ts
45
+ async function maybeControlDiscordVoiceAgentRun(params) {
46
+ if (!shouldAutoControlRealtimeVoiceAgentText(params.text)) return { handled: false };
47
+ const result = await controlRealtimeVoiceAgentRun({
48
+ sessionKey: params.entry.route.sessionKey,
49
+ text: params.text
50
+ });
51
+ if (!result.active) return {
52
+ handled: false,
53
+ result
54
+ };
55
+ return {
56
+ handled: true,
57
+ result,
58
+ ...result.speak && !result.suppress ? { speakText: result.message } : {}
59
+ };
60
+ }
61
+ //#endregion
62
+ //#region extensions/discord/src/voice/prompt.ts
63
+ const DISCORD_VOICE_SPOKEN_OUTPUT_CONTRACT = [
64
+ "You are OpenClaw's Discord voice interface in a live voice channel.",
65
+ "Discord voice reply requirements:",
66
+ "- Return only the concise text that should be spoken aloud in the voice channel.",
67
+ "- Treat the transcript as speech-to-text from a live conversation; repair obvious transcription artifacts and ignore repeated partial fragments caused by voice buffering.",
68
+ "- If the transcript is garbled, incomplete, or missing the user's intent, ask one brief clarifying question instead of guessing.",
69
+ "- If the request needs deeper reasoning, current information, or tools, use the available tools before answering.",
70
+ "- Do not call the tts tool; Discord voice will synthesize and play the returned text.",
71
+ "- Do not reply with NO_REPLY unless no spoken response is appropriate.",
72
+ "- Keep the response brief, natural, and conversational. Prefer one to three short sentences.",
73
+ "- Avoid markdown tables, code fences, citations, and visual formatting unless the user explicitly asks for something that cannot be spoken naturally."
74
+ ].join("\n");
75
+ function formatVoiceIngressPrompt(transcript, speakerLabel) {
76
+ const cleanedTranscript = transcript.trim();
77
+ const cleanedLabel = speakerLabel?.trim();
78
+ const voiceInput = cleanedLabel ? [`Voice transcript from speaker "${cleanedLabel}":`, cleanedTranscript].join("\n") : cleanedTranscript;
79
+ return [DISCORD_VOICE_SPOKEN_OUTPUT_CONTRACT, voiceInput].join("\n\n");
80
+ }
81
+ //#endregion
82
+ //#region extensions/discord/src/voice/audio.ts
83
+ const SAMPLE_RATE = 48e3;
84
+ const CHANNELS = 2;
85
+ const BIT_DEPTH = 16;
86
+ const FFMPEG_ERROR_OUTPUT_BYTES = 8192;
87
+ const DISCORD_OPUS_FRAME_SIZE = 960;
88
+ const DISCORD_OPUS_FRAME_BYTES = DISCORD_OPUS_FRAME_SIZE * CHANNELS * (BIT_DEPTH / 8);
89
+ const FFMPEG_PCM_ARGUMENTS = [
90
+ "-analyzeduration",
91
+ "0",
92
+ "-loglevel",
93
+ "error",
94
+ "-vn",
95
+ "-sn",
96
+ "-dn",
97
+ "-f",
98
+ "s16le",
99
+ "-ar",
100
+ String(SAMPLE_RATE),
101
+ "-ac",
102
+ String(CHANNELS)
103
+ ];
104
+ let warnedOpusMissing = false;
105
+ function buildWavBuffer(pcm) {
106
+ const blockAlign = 4;
107
+ const byteRate = SAMPLE_RATE * blockAlign;
108
+ const header = Buffer.alloc(44);
109
+ header.write("RIFF", 0);
110
+ header.writeUInt32LE(36 + pcm.length, 4);
111
+ header.write("WAVE", 8);
112
+ header.write("fmt ", 12);
113
+ header.writeUInt32LE(16, 16);
114
+ header.writeUInt16LE(1, 20);
115
+ header.writeUInt16LE(CHANNELS, 22);
116
+ header.writeUInt32LE(SAMPLE_RATE, 24);
117
+ header.writeUInt32LE(byteRate, 28);
118
+ header.writeUInt16LE(blockAlign, 32);
119
+ header.writeUInt16LE(BIT_DEPTH, 34);
120
+ header.write("data", 36);
121
+ header.writeUInt32LE(pcm.length, 40);
122
+ return Buffer.concat([header, pcm]);
123
+ }
124
+ function createDiscordOpusEncodeStream() {
125
+ return new DiscordOpusEncodeStream();
126
+ }
127
+ function createDiscordOpusPlaybackStream(input) {
128
+ const inputSource = typeof input === "string" ? input : "pipe:0";
129
+ const ffmpeg = spawn(resolveFfmpegBin(), [
130
+ "-i",
131
+ inputSource,
132
+ ...FFMPEG_PCM_ARGUMENTS,
133
+ "pipe:1"
134
+ ], {
135
+ stdio: [
136
+ "pipe",
137
+ "pipe",
138
+ "pipe"
139
+ ],
140
+ windowsHide: true
141
+ });
142
+ const opusStream = createDiscordOpusEncodeStream();
143
+ const stderr = Buffer.alloc(FFMPEG_ERROR_OUTPUT_BYTES);
144
+ let stderrBytes = 0;
145
+ let ffmpegClosed = false;
146
+ const killFfmpeg = (signal = "SIGTERM") => {
147
+ if (!ffmpegClosed && !ffmpeg.killed) ffmpeg.kill(signal);
148
+ };
149
+ ffmpeg.stderr.on("data", (chunk) => {
150
+ if (stderrBytes < FFMPEG_ERROR_OUTPUT_BYTES) stderrBytes += chunk.copy(stderr, stderrBytes, 0, FFMPEG_ERROR_OUTPUT_BYTES - stderrBytes);
151
+ });
152
+ ffmpeg.once("error", (err) => {
153
+ opusStream.destroy(err);
154
+ });
155
+ ffmpeg.once("close", (code, signal) => {
156
+ ffmpegClosed = true;
157
+ if (code && code !== 0) {
158
+ const stderrText = new StringDecoder("utf8").write(stderr.subarray(0, stderrBytes)).trim();
159
+ const suffix = stderrText ? `: ${stderrText}` : "";
160
+ opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with code ${code}${suffix}`));
161
+ return;
162
+ }
163
+ if (signal) opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with signal ${signal}`));
164
+ });
165
+ for (const readable of [ffmpeg.stdout, ffmpeg.stderr]) readable.on("error", (err) => {
166
+ killFfmpeg("SIGKILL");
167
+ opusStream.destroy(err);
168
+ });
169
+ ffmpeg.stdin.on("error", (err) => {
170
+ if (err.code !== "EPIPE") opusStream.destroy(err);
171
+ });
172
+ ffmpeg.stdout.pipe(opusStream);
173
+ opusStream.once("close", () => {
174
+ if (!opusStream.readableEnded) killFfmpeg();
175
+ });
176
+ if (typeof input !== "string") {
177
+ input.on("error", (err) => {
178
+ ffmpeg.stdin.destroy(err);
179
+ opusStream.destroy(err);
180
+ });
181
+ input.pipe(ffmpeg.stdin);
182
+ } else ffmpeg.stdin.end();
183
+ return opusStream;
184
+ }
185
+ var DiscordOpusEncodeStream = class extends Transform {
186
+ #buffer = Buffer.alloc(0);
187
+ #encoder;
188
+ #packetPcmBytes = /* @__PURE__ */ new WeakMap();
189
+ constructor() {
190
+ super({ readableObjectMode: true });
191
+ }
192
+ _construct(done) {
193
+ createEncoder({
194
+ application: Application.Audio,
195
+ channels: CHANNELS,
196
+ sampleRate: SAMPLE_RATE
197
+ }).then((encoder) => {
198
+ this.#encoder = encoder;
199
+ done();
200
+ }, (err) => done(err instanceof Error ? err : new Error(formatErrorMessage(err))));
201
+ }
202
+ _transform(chunk, _encoding, done) {
203
+ try {
204
+ this.#buffer = this.#buffer.length > 0 ? Buffer.concat([this.#buffer, chunk]) : Buffer.from(chunk);
205
+ while (this.#buffer.length >= DISCORD_OPUS_FRAME_BYTES) {
206
+ const frame = this.#buffer.subarray(0, DISCORD_OPUS_FRAME_BYTES);
207
+ this.#buffer = this.#buffer.subarray(DISCORD_OPUS_FRAME_BYTES);
208
+ this.#encodeFrame(frame);
209
+ }
210
+ done();
211
+ } catch (err) {
212
+ done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
213
+ }
214
+ }
215
+ _flush(done) {
216
+ try {
217
+ this.flushPartialFrame();
218
+ done();
219
+ } catch (err) {
220
+ done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
221
+ }
222
+ }
223
+ flushPartialFrame() {
224
+ if (this.destroyed || this.#buffer.length === 0) return false;
225
+ const pcmBytes = this.#buffer.length;
226
+ const frame = Buffer.alloc(DISCORD_OPUS_FRAME_BYTES);
227
+ this.#buffer.copy(frame);
228
+ this.#buffer = Buffer.alloc(0);
229
+ this.#encodeFrame(frame, pcmBytes);
230
+ return true;
231
+ }
232
+ takePcmBytes(packet) {
233
+ const bytes = this.#packetPcmBytes.get(packet) ?? 0;
234
+ this.#packetPcmBytes.delete(packet);
235
+ return bytes;
236
+ }
237
+ _destroy(err, done) {
238
+ this.#encoder?.free();
239
+ this.#buffer = Buffer.alloc(0);
240
+ done(err);
241
+ }
242
+ #encodeFrame(frame, pcmBytes = frame.length) {
243
+ const packet = Buffer.from(this.#encoder.encode(frame, { frameSize: DISCORD_OPUS_FRAME_SIZE }));
244
+ this.#packetPcmBytes.set(packet, pcmBytes);
245
+ this.push(packet);
246
+ }
247
+ };
248
+ function pcmInt16ToBuffer(pcm) {
249
+ return Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength);
250
+ }
251
+ async function decodeOpusStream(stream, params) {
252
+ return await readByteStreamWithLimit(decodeOpusFrames(stream, params), {
253
+ maxBytes: params.maxBytes,
254
+ onOverflow: () => /* @__PURE__ */ new Error(`Discord voice capture exceeds the transcription PCM limit (${params.maxBytes} bytes); speak a shorter segment.`)
255
+ });
256
+ }
257
+ async function decodeOpusStreamChunks(stream, params) {
258
+ try {
259
+ for await (const pcm of decodeOpusFrames(stream, params)) params.onChunk(pcm);
260
+ } catch (err) {
261
+ params.onError?.(err);
262
+ }
263
+ }
264
+ async function* decodeOpusFrames(stream, params) {
265
+ let decoder;
266
+ try {
267
+ decoder = await createDecoder({
268
+ channels: CHANNELS,
269
+ sampleRate: SAMPLE_RATE
270
+ });
271
+ } catch (err) {
272
+ if (!warnedOpusMissing) {
273
+ warnedOpusMissing = true;
274
+ params.onWarn(`discord voice: no usable opus decoder available (libopus-wasm: ${formatErrorMessage(err)}); cannot decode voice audio`);
275
+ }
276
+ return;
277
+ }
278
+ params.onVerbose("opus decoder: libopus-wasm");
279
+ try {
280
+ for await (const chunk of stream) {
281
+ if (!chunk || !(chunk instanceof Buffer) || chunk.length === 0) continue;
282
+ const decoded = decoder.decode(chunk, { maxFrameSize: DISCORD_OPUS_FRAME_SIZE });
283
+ if (decoded.length > 0) yield pcmInt16ToBuffer(decoded);
284
+ }
285
+ } catch (err) {
286
+ params.onError?.(err);
287
+ if (shouldLogVerbose()) logVerbose(`discord voice: opus decode failed: ${formatErrorMessage(err)}`);
288
+ } finally {
289
+ decoder.free();
290
+ }
291
+ }
292
+ function convertDiscordPcm48kStereoToRealtimePcm24kMono(pcm) {
293
+ const frameCount = Math.floor(pcm.length / 4);
294
+ if (frameCount === 0) return Buffer.alloc(0);
295
+ const mono48k = Buffer.alloc(frameCount * 2);
296
+ for (let frame = 0; frame < frameCount; frame += 1) {
297
+ const offset = frame * 4;
298
+ const left = pcm.readInt16LE(offset);
299
+ const right = pcm.readInt16LE(offset + 2);
300
+ mono48k.writeInt16LE(Math.round((left + right) / 2), frame * 2);
301
+ }
302
+ return resamplePcm(mono48k, SAMPLE_RATE, 24e3);
303
+ }
304
+ function convertRealtimePcm24kMonoToDiscordPcm48kStereo(pcm) {
305
+ const mono48k = resamplePcm(pcm, 24e3, SAMPLE_RATE);
306
+ const sampleCount = Math.floor(mono48k.length / 2);
307
+ if (sampleCount === 0) return Buffer.alloc(0);
308
+ const stereo = Buffer.alloc(sampleCount * 4);
309
+ for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
310
+ const sample = mono48k.readInt16LE(sampleIndex * 2);
311
+ const offset = sampleIndex * 4;
312
+ stereo.writeInt16LE(sample, offset);
313
+ stereo.writeInt16LE(sample, offset + 2);
314
+ }
315
+ return stereo;
316
+ }
317
+ function estimateDurationSeconds(pcm) {
318
+ const bytesPerSample = BIT_DEPTH / 8 * CHANNELS;
319
+ return pcm.length / (bytesPerSample * SAMPLE_RATE);
320
+ }
321
+ async function writeVoiceWavFile(pcm) {
322
+ const workspace = await tempWorkspace({
323
+ rootDir: resolvePreferredOpenClawTmpDir(),
324
+ prefix: "discord-voice-"
325
+ });
326
+ scheduleTempCleanup(workspace.dir);
327
+ const wav = buildWavBuffer(pcm);
328
+ return {
329
+ path: await workspace.write("segment.wav", wav),
330
+ durationSeconds: estimateDurationSeconds(pcm)
331
+ };
332
+ }
333
+ function scheduleTempCleanup(tempDir, delayMs = 18e5) {
334
+ setTimeout(() => {
335
+ fs.rm(tempDir, {
336
+ recursive: true,
337
+ force: true
338
+ }).catch((err) => {
339
+ if (shouldLogVerbose()) logVerbose(`discord voice: temp cleanup failed for ${tempDir}: ${formatErrorMessage(err)}`);
340
+ });
341
+ }, delayMs).unref();
342
+ }
343
+ const CAPTURE_FINALIZE_GRACE_MS = 2e3;
344
+ const VOICE_CONNECT_READY_TIMEOUT_MS = 3e4;
345
+ const VOICE_RECONNECT_GRACE_MS = 15e3;
346
+ const PLAYBACK_READY_TIMEOUT_MS = 6e4;
347
+ function resolveVoiceTimeoutMs(value, fallbackMs) {
348
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return fallbackMs;
349
+ return Math.floor(value);
350
+ }
351
+ function resolveDiscordVoiceMode(voice) {
352
+ const mode = voice?.mode;
353
+ if (mode === "stt-tts" || mode === "bidi") return mode;
354
+ return "agent-proxy";
355
+ }
356
+ function isDiscordRealtimeVoiceMode(mode) {
357
+ return mode === "agent-proxy" || mode === "bidi";
358
+ }
359
+ function logVoiceVerbose(message) {
360
+ logVerbose(`discord voice: ${message}`);
361
+ }
362
+ function isVoiceChannel(type) {
363
+ return type === discord_exports.ChannelType.GuildVoice || type === discord_exports.ChannelType.GuildStageVoice;
364
+ }
365
+ //#endregion
22
366
  //#region extensions/discord/src/voice/ingress.ts
23
367
  const DISCORD_VOICE_MESSAGE_PROVIDER = "discord-voice";
24
368
  const logger$7 = createSubsystemLogger("discord/voice");
@@ -471,7 +815,11 @@ var DiscordVoiceSpeakerContextResolver = class {
471
815
  label: identity.label,
472
816
  name: identity.name,
473
817
  tag: identity.tag,
474
- senderIsOwner: this.resolveIsOwner(identity)
818
+ senderIsOwner: resolveDiscordOwnerAccess({
819
+ allowFrom: this.params.ownerAllowFrom,
820
+ sender: identity,
821
+ allowNameMatching: false
822
+ }).ownerAllowed
475
823
  };
476
824
  this.setCachedContext(guildId, userId, context);
477
825
  return context;
@@ -507,17 +855,6 @@ var DiscordVoiceSpeakerContextResolver = class {
507
855
  }
508
856
  }
509
857
  }
510
- resolveIsOwner(identity) {
511
- return resolveDiscordOwnerAccess({
512
- allowFrom: this.params.ownerAllowFrom,
513
- sender: {
514
- id: identity.id,
515
- name: identity.name,
516
- tag: identity.tag
517
- },
518
- allowNameMatching: false
519
- }).ownerAllowed;
520
- }
521
858
  resolveCacheKey(guildId, userId) {
522
859
  return `${guildId}:${userId}`;
523
860
  }
@@ -1030,70 +1367,43 @@ var DiscordVoiceFollowing = class {
1030
1367
  //#endregion
1031
1368
  //#region extensions/discord/src/voice/capture-state.ts
1032
1369
  function createVoiceCaptureState() {
1033
- return {
1034
- activeSpeakers: /* @__PURE__ */ new Set(),
1035
- activeCaptureStreams: /* @__PURE__ */ new Map(),
1036
- captureFinalizeTimers: /* @__PURE__ */ new Map(),
1037
- captureGenerations: /* @__PURE__ */ new Map()
1038
- };
1370
+ return /* @__PURE__ */ new Map();
1039
1371
  }
1040
1372
  function stopVoiceCaptureState(state) {
1041
- for (const { timer } of state.captureFinalizeTimers.values()) clearTimeout(timer);
1042
- state.captureFinalizeTimers.clear();
1043
- for (const { stream } of state.activeCaptureStreams.values()) stream.destroy();
1044
- state.activeCaptureStreams.clear();
1045
- state.captureGenerations.clear();
1046
- state.activeSpeakers.clear();
1047
- }
1048
- function getActiveVoiceCapture(state, userId) {
1049
- return state.activeCaptureStreams.get(userId);
1050
- }
1051
- function isVoiceCaptureActive(state, userId) {
1052
- return state.activeSpeakers.has(userId);
1053
- }
1054
- function clearVoiceCaptureFinalizeTimer(state, userId, generation) {
1055
- const scheduled = state.captureFinalizeTimers.get(userId);
1056
- if (!scheduled || generation !== void 0 && scheduled.generation !== generation) return false;
1057
- clearTimeout(scheduled.timer);
1058
- state.captureFinalizeTimers.delete(userId);
1373
+ const captures = [...state.values()];
1374
+ state.clear();
1375
+ for (const capture of captures) {
1376
+ clearVoiceCaptureFinalizeTimer(capture);
1377
+ capture.stream?.destroy();
1378
+ }
1379
+ }
1380
+ function clearVoiceCaptureFinalizeTimer(capture) {
1381
+ if (!capture.finalizeTimer) return false;
1382
+ clearTimeout(capture.finalizeTimer);
1383
+ delete capture.finalizeTimer;
1059
1384
  return true;
1060
1385
  }
1061
1386
  function beginVoiceCapture(state, userId, stream) {
1062
- const generation = (state.captureGenerations.get(userId) ?? 0) + 1;
1063
- state.captureGenerations.set(userId, generation);
1064
- state.activeSpeakers.add(userId);
1065
- state.activeCaptureStreams.set(userId, {
1066
- generation,
1067
- stream
1068
- });
1069
- clearVoiceCaptureFinalizeTimer(state, userId, generation);
1070
- return generation;
1071
- }
1072
- function finishVoiceCapture(state, userId, generation) {
1073
- clearVoiceCaptureFinalizeTimer(state, userId, generation);
1074
- if (state.activeCaptureStreams.get(userId)?.generation !== generation) return false;
1075
- state.activeCaptureStreams.delete(userId);
1076
- state.activeSpeakers.delete(userId);
1387
+ const capture = { stream };
1388
+ state.set(userId, capture);
1389
+ return capture;
1390
+ }
1391
+ function finishVoiceCapture(state, userId, capture) {
1392
+ clearVoiceCaptureFinalizeTimer(capture);
1393
+ if (state.get(userId) !== capture) return false;
1394
+ state.delete(userId);
1077
1395
  return true;
1078
1396
  }
1079
1397
  function scheduleVoiceCaptureFinalize(params) {
1080
1398
  const { state, userId, delayMs, onFinalize } = params;
1081
- const capture = state.activeCaptureStreams.get(userId);
1399
+ const capture = state.get(userId);
1082
1400
  if (!capture) return false;
1083
- clearVoiceCaptureFinalizeTimer(state, userId, capture.generation);
1084
- const timer = setTimeout(() => {
1085
- const activeCapture = state.activeCaptureStreams.get(userId);
1086
- if (!activeCapture || activeCapture.generation !== capture.generation) return;
1087
- state.captureFinalizeTimers.delete(userId);
1088
- state.activeCaptureStreams.delete(userId);
1089
- state.activeSpeakers.delete(userId);
1090
- onFinalize?.(activeCapture);
1091
- activeCapture.stream.destroy();
1401
+ clearVoiceCaptureFinalizeTimer(capture);
1402
+ capture.finalizeTimer = setTimeout(() => {
1403
+ if (!finishVoiceCapture(state, userId, capture)) return;
1404
+ onFinalize?.(capture);
1405
+ capture.stream?.destroy();
1092
1406
  }, delayMs);
1093
- state.captureFinalizeTimers.set(userId, {
1094
- generation: capture.generation,
1095
- timer
1096
- });
1097
1407
  return true;
1098
1408
  }
1099
1409
  //#endregion
@@ -1115,12 +1425,13 @@ function sanitizeVoiceReplyTextForSpeech(text, speakerLabel) {
1115
1425
  //#endregion
1116
1426
  //#region extensions/discord/src/voice/tts.ts
1117
1427
  async function transcribeVoiceAudio(params) {
1118
- return normalizeOptionalString((await getDiscordRuntime().mediaUnderstanding.transcribeAudioFile({
1428
+ const result = await getDiscordRuntime().mediaUnderstanding.transcribeAudioFile({
1119
1429
  filePath: params.filePath,
1120
1430
  cfg: params.cfg,
1121
1431
  agentDir: resolveAgentDir(params.cfg, params.agentId),
1122
1432
  mime: "audio/wav"
1123
- })).text);
1433
+ });
1434
+ return normalizeOptionalString(result.text);
1124
1435
  }
1125
1436
  async function synthesizeVoiceReplyAudio(params) {
1126
1437
  const runtime = getDiscordRuntime();
@@ -1199,6 +1510,7 @@ async function processDiscordVoiceSegment(params) {
1199
1510
  logVoiceVerbose(`transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
1200
1511
  logVoiceVerbose(`transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceLogPreview(transcript)}`);
1201
1512
  if (params.transcripts) {
1513
+ if (entry.sessionLifecycle.status === "stopped" || entry.transcripts !== params.transcripts) return;
1202
1514
  await params.transcripts.onUtterance({
1203
1515
  sessionId: params.transcripts.sessionId,
1204
1516
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1335,38 +1647,22 @@ var DiscordVoiceReceive = class {
1335
1647
  const botUserId = this.params.botUserId();
1336
1648
  if (botUserId && userId === botUserId) return;
1337
1649
  this.params.membership.notePresent(entry, userId);
1338
- if (isVoiceCaptureActive(entry.capture, userId)) {
1339
- const activeCapture = getActiveVoiceCapture(entry.capture, userId);
1340
- const extended = activeCapture ? clearVoiceCaptureFinalizeTimer(entry.capture, userId, activeCapture.generation) : false;
1650
+ const activeCapture = entry.capture.get(userId);
1651
+ if (activeCapture) {
1652
+ const extended = clearVoiceCaptureFinalizeTimer(activeCapture);
1341
1653
  logVoiceVerbose(`capture start ignored (already active): guild ${entry.guildId} channel ${entry.channelId} user ${userId}${extended ? " (finalize canceled)" : ""}`);
1342
1654
  return;
1343
1655
  }
1344
1656
  logVoiceVerbose(`capture start: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1345
1657
  const voiceSdk = loadDiscordVoiceSdk();
1658
+ const transcripts = entry.transcripts;
1346
1659
  const voiceMode = resolveDiscordVoiceMode(this.params.discordConfig.voice);
1347
1660
  const realtime = entry.realtimeLifecycle.status === "active" && isDiscordRealtimeVoiceMode(voiceMode) ? entry.realtimeLifecycle.instance : void 0;
1348
1661
  if (entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && !realtime) {
1349
1662
  logVoiceVerbose(`capture ignored during playback: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1350
1663
  return;
1351
1664
  }
1352
- const realtimeIngress = realtime ? await this.resolveDiscordVoiceIngressContext(entry, userId) : void 0;
1353
- if (realtime && !realtimeIngress) {
1354
- logVoiceVerbose(`realtime capture unauthorized: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1355
- return;
1356
- }
1357
- if (!this.params.isEntryCurrent(entry)) return;
1358
- if (entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && realtime) {
1359
- if (!realtime.isBargeInEnabled()) {
1360
- logger$3.info(`discord voice: realtime capture ignored during playback (barge-in disabled): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1361
- return;
1362
- }
1363
- logVoiceVerbose(`realtime barge-in: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1364
- logger$3.info(`discord voice: realtime barge-in detected source=speaker-start guild=${entry.guildId} channel=${entry.channelId} user=${userId} playerStatus=${entry.player.state.status}`);
1365
- realtime.handleBargeIn("speaker-start");
1366
- }
1367
- this.enableDaveReceivePassthrough(entry, `speaker ${userId} start`, 15);
1368
- const stream = entry.connection.receiver.subscribe(userId, { end: { behavior: voiceSdk.EndBehaviorType.Manual } });
1369
- const generation = beginVoiceCapture(entry.capture, userId, stream);
1665
+ const capture = beginVoiceCapture(entry.capture, userId);
1370
1666
  let streamAborted = false;
1371
1667
  let receiveFailureHandled = false;
1372
1668
  let receiveStreamEndHandled = false;
@@ -1383,8 +1679,25 @@ var DiscordVoiceReceive = class {
1383
1679
  receiveFailureHandled = true;
1384
1680
  this.handleReceiveError(entry, err);
1385
1681
  };
1386
- stream.on("error", handleStreamError);
1387
1682
  try {
1683
+ const realtimeIngress = realtime ? await this.resolveDiscordVoiceIngressContext(entry, userId) : void 0;
1684
+ if (realtime && !realtimeIngress) {
1685
+ logVoiceVerbose(`realtime capture unauthorized: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1686
+ return;
1687
+ }
1688
+ if (!this.params.isEntryCurrent(entry) || entry.capture.get(userId) !== capture) return;
1689
+ if (entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && realtime) {
1690
+ if (!realtime.isBargeInEnabled()) {
1691
+ logger$3.info(`discord voice: realtime capture ignored during playback (barge-in disabled): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1692
+ return;
1693
+ }
1694
+ logVoiceVerbose(`realtime barge-in: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1695
+ logger$3.info(`discord voice: realtime barge-in detected source=speaker-start guild=${entry.guildId} channel=${entry.channelId} user=${userId} playerStatus=${entry.player.state.status}`);
1696
+ realtime.handleBargeIn("speaker-start");
1697
+ }
1698
+ this.enableDaveReceivePassthrough(entry, `speaker ${userId} start`, 15);
1699
+ const stream = capture.stream = entry.connection.receiver.subscribe(userId, { end: { behavior: voiceSdk.EndBehaviorType.Manual } });
1700
+ stream.on("error", handleStreamError);
1388
1701
  if (realtime && realtimeIngress) {
1389
1702
  const turn = realtime.beginSpeakerTurn(realtimeIngress, userId);
1390
1703
  try {
@@ -1399,7 +1712,12 @@ var DiscordVoiceReceive = class {
1399
1712
  }
1400
1713
  return;
1401
1714
  }
1715
+ if (!entry.audioInputBudget.enabled) {
1716
+ logger$3.warn("discord voice: capture skipped: audio understanding is disabled; enable tools.media.audio.enabled to transcribe voice.");
1717
+ return;
1718
+ }
1402
1719
  const pcm = await decodeOpusStream(stream, {
1720
+ maxBytes: Math.max(0, entry.audioInputBudget.maxBytes - 44),
1403
1721
  onError: handleStreamError,
1404
1722
  onVerbose: logVoiceVerbose,
1405
1723
  onWarn: (message) => logger$3.warn(message)
@@ -1420,6 +1738,7 @@ var DiscordVoiceReceive = class {
1420
1738
  logVoiceVerbose(`capture ready (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
1421
1739
  entry.processingQueue = entry.processingQueue.then(async () => {
1422
1740
  if (!this.params.isEntryCurrent(entry)) return;
1741
+ if (entry.transcripts !== transcripts) return;
1423
1742
  await this.processSegment({
1424
1743
  entry,
1425
1744
  wavPath,
@@ -1431,8 +1750,9 @@ var DiscordVoiceReceive = class {
1431
1750
  if (!receiveFailureHandled) this.handleReceiveError(entry, err);
1432
1751
  throw err;
1433
1752
  } finally {
1434
- stream.off?.("error", handleStreamError);
1435
- if (finishVoiceCapture(entry.capture, userId, generation) && !stream.destroyed) stream.destroy();
1753
+ const stream = capture.stream;
1754
+ stream?.off?.("error", handleStreamError);
1755
+ if (finishVoiceCapture(entry.capture, userId, capture) && stream && !stream.destroyed) stream.destroy();
1436
1756
  }
1437
1757
  }
1438
1758
  async processSegment(params) {
@@ -1613,6 +1933,17 @@ const REALTIME_PLAYBACK_MAX_MISSED_FRAMES = 100;
1613
1933
  function isVoiceSessionStopped(entry) {
1614
1934
  return entry.sessionLifecycle.status === "stopped";
1615
1935
  }
1936
+ function retireTranscripts(entry) {
1937
+ const transcripts = entry.transcripts;
1938
+ entry.transcripts = void 0;
1939
+ try {
1940
+ Promise.resolve(transcripts?.onStop?.()).catch((error) => {
1941
+ logger$2.warn(`discord voice: transcripts retirement failed: ${formatErrorMessage(error)}`);
1942
+ });
1943
+ } catch (error) {
1944
+ logger$2.warn(`discord voice: transcripts retirement failed: ${formatErrorMessage(error)}`);
1945
+ }
1946
+ }
1616
1947
  function isVoiceConnectionDestroyed(connection, voiceSdk) {
1617
1948
  return connection.state.status === voiceSdk.VoiceConnectionStatus.Destroyed;
1618
1949
  }
@@ -1699,7 +2030,10 @@ var DiscordVoiceSessions = class {
1699
2030
  if (existing && existing.channelId === channelId) {
1700
2031
  existing.autoJoinWhenOccupied = options?.autoJoinWhenOccupied === true;
1701
2032
  if (authority) existing.generation = authority.generation;
1702
- if (options?.transcripts) existing.transcripts = options.transcripts;
2033
+ if (options?.transcripts) {
2034
+ retireTranscripts(existing);
2035
+ existing.transcripts = options.transcripts;
2036
+ }
1703
2037
  if (!options?.transcripts && isDiscordRealtimeVoiceMode(voiceMode) && existing.realtimeLifecycle.status !== "active" && existing.realtimeLifecycle.status !== "starting") {
1704
2038
  const realtimeResult = await this.attachRealtimeSession(existing, voiceMode, {
1705
2039
  requireLiveEntry: true,
@@ -1751,6 +2085,8 @@ var DiscordVoiceSessions = class {
1751
2085
  ok: false,
1752
2086
  message: "Discord voice plugin is not available."
1753
2087
  };
2088
+ const audioInputBudget = await getDiscordRuntime().mediaUnderstanding.resolveAudioInputBudget({ cfg: this.params.cfg });
2089
+ if (authority && !authority.isCurrent()) return cancelledJoinResult();
1754
2090
  const adapterCreator = voicePlugin.getGatewayAdapterCreator(guildId);
1755
2091
  const daveEncryption = voiceConfig?.daveEncryption;
1756
2092
  const decryptionFailureTolerance = voiceConfig?.decryptionFailureTolerance;
@@ -1862,6 +2198,7 @@ var DiscordVoiceSessions = class {
1862
2198
  status: "stopped",
1863
2199
  reason: optionsLocal.reason
1864
2200
  };
2201
+ retireTranscripts(entry);
1865
2202
  if (this.params.sessions.get(guildId) === entry) this.params.sessions.delete(guildId);
1866
2203
  this.params.membership.deactivate(entry);
1867
2204
  connection.receiver.speaking.off("start", speakingHandler);
@@ -1900,6 +2237,7 @@ var DiscordVoiceSessions = class {
1900
2237
  player,
1901
2238
  playbackQueue: Promise.resolve(),
1902
2239
  processingQueue: Promise.resolve(),
2240
+ audioInputBudget,
1903
2241
  ttsStreamFallbackWarned: false,
1904
2242
  capture: createVoiceCaptureState(),
1905
2243
  transcripts: options?.transcripts,
@@ -2004,7 +2342,7 @@ var DiscordVoiceSessions = class {
2004
2342
  channelId: entry.channelId
2005
2343
  };
2006
2344
  if (entry.realtimeLifecycle.status === "active" || entry.realtimeLifecycle.status === "starting") {
2007
- entry.transcripts = void 0;
2345
+ retireTranscripts(entry);
2008
2346
  return {
2009
2347
  ok: true,
2010
2348
  message: `Stopped transcripts for ${formatMention({ channelId: entry.channelId })}.`,
@@ -2034,7 +2372,7 @@ var DiscordVoiceSessions = class {
2034
2372
  ok: false,
2035
2373
  message: "Discord realtime voice session stopped before startup completed."
2036
2374
  };
2037
- const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-CU8IQUS-.js");
2375
+ const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-CGYm1t_0.js");
2038
2376
  const realtime = new DiscordRealtimeVoiceSession({
2039
2377
  accountId: this.params.accountId,
2040
2378
  bootstrapContextInstructions,
@@ -2138,6 +2476,13 @@ var DiscordVoiceStateUpdateListener = class extends VoiceStateUpdateListener {
2138
2476
  };
2139
2477
  //#endregion
2140
2478
  //#region extensions/discord/src/voice/voice-runtime.ts
2479
+ var voice_runtime_exports = /* @__PURE__ */ __exportAll({
2480
+ DiscordVoiceGuildCreateListener: () => DiscordVoiceGuildCreateListener,
2481
+ DiscordVoiceManager: () => DiscordVoiceManager,
2482
+ DiscordVoiceReadyListener: () => DiscordVoiceReadyListener,
2483
+ DiscordVoiceResumedListener: () => DiscordVoiceResumedListener,
2484
+ DiscordVoiceStateUpdateListener: () => DiscordVoiceStateUpdateListener
2485
+ });
2141
2486
  const logger = createSubsystemLogger("discord/voice");
2142
2487
  const DISCORD_VOICE_FATAL_AUTOJOIN_ERROR_PATTERNS = [
2143
2488
  "api key missing",
@@ -2166,6 +2511,7 @@ var DiscordVoiceManager = class {
2166
2511
  this.joinTasks = /* @__PURE__ */ new Map();
2167
2512
  this.autoJoinTasks = /* @__PURE__ */ new Map();
2168
2513
  this.fatalAutoJoinFailures = /* @__PURE__ */ new Map();
2514
+ this.occupancyWatchers = /* @__PURE__ */ new Set();
2169
2515
  this.destroyed = false;
2170
2516
  this.client = params.client;
2171
2517
  this.botUserId = params.botUserId;
@@ -2245,8 +2591,42 @@ var DiscordVoiceManager = class {
2245
2591
  refreshGuildRoster(guildId) {
2246
2592
  this.voiceSessions.refreshGuildRoster(guildId);
2247
2593
  }
2594
+ watchChannelOccupancy(params, listener) {
2595
+ if (this.destroyed) return () => void 0;
2596
+ const guildId = params.guildId.trim();
2597
+ const channelId = params.channelId.trim();
2598
+ let wasOccupied = false;
2599
+ const watcher = {
2600
+ guildId,
2601
+ refresh: () => {
2602
+ const states = listDiscordVoiceParticipantStates({
2603
+ client: this.client,
2604
+ guildId,
2605
+ channelId
2606
+ });
2607
+ if (states === null) return;
2608
+ const occupied = countDiscordVoiceHumanParticipants({
2609
+ states,
2610
+ botUserId: this.botUserId
2611
+ }) > 0;
2612
+ if (occupied !== wasOccupied) {
2613
+ wasOccupied = occupied;
2614
+ listener({ occupied });
2615
+ }
2616
+ }
2617
+ };
2618
+ this.occupancyWatchers.add(watcher);
2619
+ watcher.refresh();
2620
+ return () => {
2621
+ this.occupancyWatchers.delete(watcher);
2622
+ };
2623
+ }
2624
+ reconcileChannelOccupancy(guildId) {
2625
+ for (const watcher of this.occupancyWatchers) if (!guildId || watcher.guildId === guildId) watcher.refresh();
2626
+ }
2248
2627
  async autoJoin() {
2249
2628
  if (!this.voiceEnabled || this.destroyed) return;
2629
+ this.reconcileChannelOccupancy();
2250
2630
  const entriesByGuild = /* @__PURE__ */ new Map();
2251
2631
  const duplicateGuilds = /* @__PURE__ */ new Set();
2252
2632
  for (const entry of this.autoJoinChannels) {
@@ -2262,6 +2642,7 @@ var DiscordVoiceManager = class {
2262
2642
  await this.following.startReconciliation();
2263
2643
  }
2264
2644
  async reconcileAutoJoinGuild(guildId) {
2645
+ this.reconcileChannelOccupancy(guildId);
2265
2646
  const entry = this.resolveAutoJoinTarget(guildId);
2266
2647
  if (!entry?.whenOccupied || !this.voiceEnabled || this.destroyed) return;
2267
2648
  await this.enqueueAutoJoin(entry);
@@ -2386,6 +2767,10 @@ var DiscordVoiceManager = class {
2386
2767
  guildId,
2387
2768
  channelId
2388
2769
  }));
2770
+ return {
2771
+ ...result,
2772
+ ...entry.channelName ? { channelName: entry.channelName } : {}
2773
+ };
2389
2774
  } else if (!result.ok && isCurrent()) this.guildLifecycles.set(guildId, {
2390
2775
  status: "inactive",
2391
2776
  generation
@@ -2453,6 +2838,7 @@ var DiscordVoiceManager = class {
2453
2838
  return;
2454
2839
  }
2455
2840
  this.membership.track(this.sessions.get(guildId), data, previousVoiceState);
2841
+ this.reconcileChannelOccupancy(guildId);
2456
2842
  if (this.following.isFollowedUser(userId)) await this.following.handleFollowedUserVoiceStateUpdate({
2457
2843
  guildId,
2458
2844
  channelId,
@@ -2463,6 +2849,7 @@ var DiscordVoiceManager = class {
2463
2849
  }
2464
2850
  async destroy() {
2465
2851
  this.destroyed = true;
2852
+ this.occupancyWatchers.clear();
2466
2853
  this.following.destroy();
2467
2854
  for (const entry of this.sessions.values()) entry.stop();
2468
2855
  for (const [guildId, lifecycle] of this.guildLifecycles) this.guildLifecycles.set(guildId, {
@@ -2538,4 +2925,4 @@ var DiscordVoiceManager = class {
2538
2925
  }
2539
2926
  };
2540
2927
  //#endregion
2541
- export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener };
2928
+ export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener, createDiscordOpusEncodeStream as a, formatVoiceLogPreview as c, convertRealtimePcm24kMonoToDiscordPcm48kStereo as i, loadDiscordVoiceSdk as l, logVoiceVerbose as n, formatVoiceIngressPrompt as o, convertDiscordPcm48kStereoToRealtimePcm24kMono as r, maybeControlDiscordVoiceAgentRun as s, voice_runtime_exports as t };