@openclaw/discord 2026.9.1-beta.1 → 2026.9.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/dist/account-inspect-BY-ve3yH.js +45 -0
- package/dist/account-inspect-api.js +1 -1
- package/dist/{accounts-CfQ-vSyf.js → accounts-CwJQoLjM.js} +89 -45
- package/dist/action-runtime-api.js +1 -1
- package/dist/{activities-api-q-9PaaHH.js → activities-api-E-segZn6.js} +30 -17
- package/dist/activities-api.js +1 -1
- package/dist/{allow-list-BDMimASD.js → allow-list-C2JuH-R0.js} +10 -6
- package/dist/{api-DNVbJyEy.js → api-BfmEP6wp.js} +6 -5
- package/dist/api.js +18 -18
- package/dist/{approval-handler.runtime-B7qhK3fR.js → approval-handler.runtime-CW5k3mSa.js} +19 -12
- package/dist/{audit-CBEv_ERu.js → audit-B9rydv5a.js} +8 -7
- package/dist/{channel-Dx1tMTfx.js → channel-D93LnbTE.js} +29 -23
- package/dist/{channel-actions.runtime-CLWWu9nC.js → channel-actions.runtime-DMFv4doE.js} +16 -13
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-DOzXS2Pk.js → channel.setup-DTL7kPOM.js} +18 -18
- package/dist/{components-B9V5o361.js → components-DmXYIQ26.js} +111 -149
- package/dist/{config-schema-BR13IAx6.js → config-schema-B797-Spt.js} +19 -12
- package/dist/{conversation-binding-route-DJYrY2TQ.js → conversation-binding-route-24afmvEC.js} +1 -1
- package/dist/{directory-config-BK6eqekJ.js → directory-config-BD4YL7wT.js} +2 -2
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-DSAJSrJk.js → directory-live-BrFVc1Ds.js} +4 -4
- package/dist/{doctor-Cm7Y1pJ2.js → doctor-BUB41_lr.js} +5 -5
- package/dist/{doctor-contract-DsWvKIXF.js → doctor-contract-Co5vRCZ6.js} +9 -6
- package/dist/doctor-contract-api.js +3 -2
- package/dist/{handle-action.guild-admin-BdwCRHmm.js → handle-action.guild-admin-B0egstYq.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/ingress-identity-BCzfPPYM.js +60 -0
- package/dist/{message-handler-BXpWZyni.js → message-handler-Qdw9c0j4.js} +14 -10
- package/dist/{message-handler.preflight-Ba5LAY_X.js → message-handler.preflight-Dxi2yBfR.js} +41 -101
- package/dist/{message-handler.process-3HEzmemS.js → message-handler.process-BYDjtxZJ.js} +83 -109
- package/dist/{message-text-aiwO5R2F.js → message-text-BRVqYm-t.js} +11 -6
- package/dist/{outbound-adapter-BE4jSq2d.js → outbound-adapter-DEhthWTk.js} +52 -67
- package/dist/{outbound-components-oLpg8Qzb.js → outbound-components-DehyxvA4.js} +5 -4
- package/dist/{outbound-session-route-Cg15ZFJd.js → outbound-session-route-BFGRF9fE.js} +55 -12
- package/dist/{owner-access-UlYi9t0M.js → owner-access-yc1sX7Gx.js} +7 -6
- package/dist/{pluralkit-Tiyl70dx.js → pluralkit-XmmAHoV6.js} +2 -2
- package/dist/{probe-n6ZXxK8V.js → probe-3ovFAp3b.js} +3 -3
- package/dist/{probe.runtime-CN4bdPfZ.js → probe.runtime-BckHn3cJ.js} +1 -1
- package/dist/{provider-B11xwilq.js → provider-DuDtKbF8.js} +294 -131
- package/dist/{provider-session.runtime-B9xe-Qaf.js → provider-session.runtime-BA2BPyZ4.js} +3 -3
- package/dist/provider.runtime-Db8YwZBv.js +2 -0
- package/dist/{realtime-session.runtime-CU8IQUS-.js → realtime-session.runtime-DgSbX6ay.js} +103 -163
- package/dist/{resolve-allowlist-common-BLoYaxEG.js → resolve-allowlist-common-BJ5UZNNT.js} +3 -3
- package/dist/{resolve-channels-C2sT_n67.js → resolve-channels-CYseD5Uw.js} +4 -4
- package/dist/{resolve-users-CzpjLwjy.js → resolve-users-DrmhDrqm.js} +5 -4
- package/dist/{retry-Cv4jCU5Z.js → retry-BNCxiyyR.js} +120 -32
- package/dist/{retry-after-DdLPmPoa.js → retry-after-BrLjLUJi.js} +2 -1
- package/dist/{runtime-BHrTlFkf.js → runtime-BbWNtfyq.js} +127 -93
- package/dist/{runtime-CY4PRMF-.js → runtime-DB7Fb6AU.js} +2 -2
- package/dist/runtime-api.js +19 -19
- package/dist/{secret-config-contract-B3i6WxV5.js → secret-config-contract-DFu9hfU8.js} +14 -6
- package/dist/secret-contract-api.js +1 -1
- package/dist/{security-audit-BfJhAXZp.js → security-audit-DKfK-iPY.js} +10 -11
- package/dist/security-audit-contract-api.js +1 -1
- package/dist/{security-audit.runtime-BVKF9VtY.js → security-audit.runtime-sKZyKgpx.js} +1 -1
- package/dist/{send-C49MFF9j.js → send-BnTEIeLE.js} +132 -100
- package/dist/{send.components-DKKAw5VX.js → send.components-BWVEcjRP.js} +21 -15
- package/dist/{send.outbound-zam0TrZx.js → send.outbound-b5xwHTlL.js} +35 -22
- package/dist/{send.shared-JjljcV4O.js → send.shared-5vxBTagO.js} +25 -18
- package/dist/{sender-identity-BJtfyise.js → sender-identity-Dt0imZyY.js} +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{shared-_5EcP4jW.js → shared-BSJWHEMA.js} +20 -14
- package/dist/{subagent-hooks-DQzvOptn.js → subagent-hooks-IzQKX4RQ.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{system-events-C7yxaHWP.js → system-events-BOiDS8b3.js} +2 -2
- package/dist/{target-resolver-vxIh6O3-.js → target-resolver-BVI8xSSC.js} +6 -5
- package/dist/targets-DbxVbq8k.js +3 -0
- package/dist/{thread-bindings-DW5F9vat.js → thread-bindings-D7mcaXQC.js} +6 -5
- package/dist/{thread-bindings.manager-ByOtwjo_.js → thread-bindings.manager-B1oySiVc.js} +37 -28
- package/dist/{thread-bindings.session-updates-CJb-BpPL.js → thread-bindings.session-updates-CC7HoQvt.js} +2 -1
- package/dist/{threading-agH1T_6s.js → threading-OQnL-0Zx.js} +8 -6
- package/dist/{timeouts-B9cy6RvQ.js → timeouts-D86uWLt_.js} +1 -1
- package/dist/{transcripts-source-BR11iHA4.js → transcripts-source-D8lH7AkN.js} +9 -4
- package/dist/transcripts-source-api.js +1 -1
- package/dist/{typing-Dc_YMY2e.js → typing-C9q0AQMb.js} +2 -2
- package/dist/{voice-runtime-DIeaa8fJ.js → voice-runtime-Dfhjh1tV.js} +433 -93
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/README.md +1 -1
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/conforms.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/package.json +3 -3
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubArray.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubObject.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubString.d.ts +2 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +14 -6
- package/package.json +6 -5
- package/skills/discord/SKILL.md +1 -1
- package/dist/account-inspect-BDfKyuvL.js +0 -89
- package/dist/provider.runtime-DiYaHvOz.js +0 -2
- package/dist/sdk-runtime-4geQHtXc.js +0 -338
- package/dist/targets-CtTKvntJ.js +0 -3
|
@@ -1,24 +1,356 @@
|
|
|
1
|
-
import {
|
|
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-BNCxiyyR.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-
|
|
4
|
-
import { a as formatMention } from "./send.outbound-
|
|
5
|
-
import { n as resolveDiscordVoiceEnabled, r as authorizeDiscordVoiceIngress, t as resolveDiscordVoiceAccess } from "./owner-access-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { a as normalizeDiscordSlug, h as resolveDiscordOwnerAccess, x as formatDiscordUserTag } from "./allow-list-C2JuH-R0.js";
|
|
4
|
+
import { a as formatMention } from "./send.outbound-b5xwHTlL.js";
|
|
5
|
+
import { n as resolveDiscordVoiceEnabled, r as authorizeDiscordVoiceIngress, t as resolveDiscordVoiceAccess } from "./owner-access-yc1sX7Gx.js";
|
|
6
|
+
import { b as resolveFetchedDiscordThreadLikeChannelContext, m as buildDiscordGroupSystemPrompt } from "./provider-DuDtKbF8.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 {
|
|
20
|
-
import {
|
|
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/log-preview.ts
|
|
29
|
+
const DISCORD_VOICE_LOG_PREVIEW_CHARS = 500;
|
|
30
|
+
function formatVoiceLogPreview(text) {
|
|
31
|
+
const oneLine = text.replace(/\s+/g, " ").trim();
|
|
32
|
+
if (oneLine.length <= DISCORD_VOICE_LOG_PREVIEW_CHARS) return oneLine;
|
|
33
|
+
return `${truncateUtf16Safe(oneLine, DISCORD_VOICE_LOG_PREVIEW_CHARS)}...`;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region extensions/discord/src/voice/agent-control.ts
|
|
37
|
+
async function maybeControlDiscordVoiceAgentRun(params) {
|
|
38
|
+
if (!shouldAutoControlRealtimeVoiceAgentText(params.text)) return { handled: false };
|
|
39
|
+
const result = await controlRealtimeVoiceAgentRun({
|
|
40
|
+
sessionKey: params.entry.route.sessionKey,
|
|
41
|
+
text: params.text
|
|
42
|
+
});
|
|
43
|
+
if (!result.active) return {
|
|
44
|
+
handled: false,
|
|
45
|
+
result
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
handled: true,
|
|
49
|
+
result,
|
|
50
|
+
...result.speak && !result.suppress ? { speakText: result.message } : {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region extensions/discord/src/voice/prompt.ts
|
|
55
|
+
const DISCORD_VOICE_SPOKEN_OUTPUT_CONTRACT = [
|
|
56
|
+
"You are OpenClaw's Discord voice interface in a live voice channel.",
|
|
57
|
+
"Discord voice reply requirements:",
|
|
58
|
+
"- Return only the concise text that should be spoken aloud in the voice channel.",
|
|
59
|
+
"- Treat the transcript as speech-to-text from a live conversation; repair obvious transcription artifacts and ignore repeated partial fragments caused by voice buffering.",
|
|
60
|
+
"- If the transcript is garbled, incomplete, or missing the user's intent, ask one brief clarifying question instead of guessing.",
|
|
61
|
+
"- If the request needs deeper reasoning, current information, or tools, use the available tools before answering.",
|
|
62
|
+
"- Do not call the tts tool; Discord voice will synthesize and play the returned text.",
|
|
63
|
+
"- Do not reply with NO_REPLY unless no spoken response is appropriate.",
|
|
64
|
+
"- Keep the response brief, natural, and conversational. Prefer one to three short sentences.",
|
|
65
|
+
"- Avoid markdown tables, code fences, citations, and visual formatting unless the user explicitly asks for something that cannot be spoken naturally."
|
|
66
|
+
].join("\n");
|
|
67
|
+
function formatVoiceIngressPrompt(transcript, speakerLabel) {
|
|
68
|
+
const cleanedTranscript = transcript.trim();
|
|
69
|
+
const cleanedLabel = speakerLabel?.trim();
|
|
70
|
+
const voiceInput = cleanedLabel ? [`Voice transcript from speaker "${cleanedLabel}":`, cleanedTranscript].join("\n") : cleanedTranscript;
|
|
71
|
+
return [DISCORD_VOICE_SPOKEN_OUTPUT_CONTRACT, voiceInput].join("\n\n");
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region extensions/discord/src/voice/audio.ts
|
|
75
|
+
const SAMPLE_RATE = 48e3;
|
|
76
|
+
const CHANNELS = 2;
|
|
77
|
+
const BIT_DEPTH = 16;
|
|
78
|
+
const FFMPEG_ERROR_OUTPUT_BYTES = 8192;
|
|
79
|
+
const DISCORD_OPUS_FRAME_SIZE = 960;
|
|
80
|
+
const DISCORD_OPUS_FRAME_BYTES = DISCORD_OPUS_FRAME_SIZE * CHANNELS * (BIT_DEPTH / 8);
|
|
81
|
+
const FFMPEG_PCM_ARGUMENTS = [
|
|
82
|
+
"-analyzeduration",
|
|
83
|
+
"0",
|
|
84
|
+
"-loglevel",
|
|
85
|
+
"error",
|
|
86
|
+
"-vn",
|
|
87
|
+
"-sn",
|
|
88
|
+
"-dn",
|
|
89
|
+
"-f",
|
|
90
|
+
"s16le",
|
|
91
|
+
"-ar",
|
|
92
|
+
String(SAMPLE_RATE),
|
|
93
|
+
"-ac",
|
|
94
|
+
String(CHANNELS)
|
|
95
|
+
];
|
|
96
|
+
let warnedOpusMissing = false;
|
|
97
|
+
function buildWavBuffer(pcm) {
|
|
98
|
+
const blockAlign = 4;
|
|
99
|
+
const byteRate = SAMPLE_RATE * blockAlign;
|
|
100
|
+
const header = Buffer.alloc(44);
|
|
101
|
+
header.write("RIFF", 0);
|
|
102
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
103
|
+
header.write("WAVE", 8);
|
|
104
|
+
header.write("fmt ", 12);
|
|
105
|
+
header.writeUInt32LE(16, 16);
|
|
106
|
+
header.writeUInt16LE(1, 20);
|
|
107
|
+
header.writeUInt16LE(CHANNELS, 22);
|
|
108
|
+
header.writeUInt32LE(SAMPLE_RATE, 24);
|
|
109
|
+
header.writeUInt32LE(byteRate, 28);
|
|
110
|
+
header.writeUInt16LE(blockAlign, 32);
|
|
111
|
+
header.writeUInt16LE(BIT_DEPTH, 34);
|
|
112
|
+
header.write("data", 36);
|
|
113
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
114
|
+
return Buffer.concat([header, pcm]);
|
|
115
|
+
}
|
|
116
|
+
function createDiscordOpusEncodeStream() {
|
|
117
|
+
return new DiscordOpusEncodeStream();
|
|
118
|
+
}
|
|
119
|
+
function createDiscordOpusPlaybackStream(input) {
|
|
120
|
+
const inputSource = typeof input === "string" ? input : "pipe:0";
|
|
121
|
+
const ffmpeg = spawn(resolveFfmpegBin(), [
|
|
122
|
+
"-i",
|
|
123
|
+
inputSource,
|
|
124
|
+
...FFMPEG_PCM_ARGUMENTS,
|
|
125
|
+
"pipe:1"
|
|
126
|
+
], {
|
|
127
|
+
stdio: [
|
|
128
|
+
"pipe",
|
|
129
|
+
"pipe",
|
|
130
|
+
"pipe"
|
|
131
|
+
],
|
|
132
|
+
windowsHide: true
|
|
133
|
+
});
|
|
134
|
+
const opusStream = createDiscordOpusEncodeStream();
|
|
135
|
+
const stderr = Buffer.alloc(FFMPEG_ERROR_OUTPUT_BYTES);
|
|
136
|
+
let stderrBytes = 0;
|
|
137
|
+
let ffmpegClosed = false;
|
|
138
|
+
const killFfmpeg = (signal = "SIGTERM") => {
|
|
139
|
+
if (!ffmpegClosed && !ffmpeg.killed) ffmpeg.kill(signal);
|
|
140
|
+
};
|
|
141
|
+
ffmpeg.stderr.on("data", (chunk) => {
|
|
142
|
+
if (stderrBytes < FFMPEG_ERROR_OUTPUT_BYTES) stderrBytes += chunk.copy(stderr, stderrBytes, 0, FFMPEG_ERROR_OUTPUT_BYTES - stderrBytes);
|
|
143
|
+
});
|
|
144
|
+
ffmpeg.once("error", (err) => {
|
|
145
|
+
opusStream.destroy(err);
|
|
146
|
+
});
|
|
147
|
+
ffmpeg.once("close", (code, signal) => {
|
|
148
|
+
ffmpegClosed = true;
|
|
149
|
+
if (code && code !== 0) {
|
|
150
|
+
const stderrText = new StringDecoder("utf8").write(stderr.subarray(0, stderrBytes)).trim();
|
|
151
|
+
const suffix = stderrText ? `: ${stderrText}` : "";
|
|
152
|
+
opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with code ${code}${suffix}`));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (signal) opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with signal ${signal}`));
|
|
156
|
+
});
|
|
157
|
+
for (const readable of [ffmpeg.stdout, ffmpeg.stderr]) readable.on("error", (err) => {
|
|
158
|
+
killFfmpeg("SIGKILL");
|
|
159
|
+
opusStream.destroy(err);
|
|
160
|
+
});
|
|
161
|
+
ffmpeg.stdin.on("error", (err) => {
|
|
162
|
+
if (err.code !== "EPIPE") opusStream.destroy(err);
|
|
163
|
+
});
|
|
164
|
+
ffmpeg.stdout.pipe(opusStream);
|
|
165
|
+
opusStream.once("close", () => {
|
|
166
|
+
if (!opusStream.readableEnded) killFfmpeg();
|
|
167
|
+
});
|
|
168
|
+
if (typeof input !== "string") {
|
|
169
|
+
input.on("error", (err) => {
|
|
170
|
+
ffmpeg.stdin.destroy(err);
|
|
171
|
+
opusStream.destroy(err);
|
|
172
|
+
});
|
|
173
|
+
input.pipe(ffmpeg.stdin);
|
|
174
|
+
} else ffmpeg.stdin.end();
|
|
175
|
+
return opusStream;
|
|
176
|
+
}
|
|
177
|
+
var DiscordOpusEncodeStream = class extends Transform {
|
|
178
|
+
#buffer = Buffer.alloc(0);
|
|
179
|
+
#encoder;
|
|
180
|
+
constructor() {
|
|
181
|
+
super({ readableObjectMode: true });
|
|
182
|
+
}
|
|
183
|
+
_construct(done) {
|
|
184
|
+
createEncoder({
|
|
185
|
+
application: Application.Audio,
|
|
186
|
+
channels: CHANNELS,
|
|
187
|
+
sampleRate: SAMPLE_RATE
|
|
188
|
+
}).then((encoder) => {
|
|
189
|
+
this.#encoder = encoder;
|
|
190
|
+
done();
|
|
191
|
+
}, (err) => done(err instanceof Error ? err : new Error(formatErrorMessage(err))));
|
|
192
|
+
}
|
|
193
|
+
_transform(chunk, _encoding, done) {
|
|
194
|
+
try {
|
|
195
|
+
this.#buffer = this.#buffer.length > 0 ? Buffer.concat([this.#buffer, chunk]) : Buffer.from(chunk);
|
|
196
|
+
while (this.#buffer.length >= DISCORD_OPUS_FRAME_BYTES) {
|
|
197
|
+
const frame = this.#buffer.subarray(0, DISCORD_OPUS_FRAME_BYTES);
|
|
198
|
+
this.#buffer = this.#buffer.subarray(DISCORD_OPUS_FRAME_BYTES);
|
|
199
|
+
this.#encodeFrame(frame);
|
|
200
|
+
}
|
|
201
|
+
done();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
_flush(done) {
|
|
207
|
+
try {
|
|
208
|
+
if (this.#buffer.length > 0) {
|
|
209
|
+
const frame = Buffer.alloc(DISCORD_OPUS_FRAME_BYTES);
|
|
210
|
+
this.#buffer.copy(frame);
|
|
211
|
+
this.#buffer = Buffer.alloc(0);
|
|
212
|
+
this.#encodeFrame(frame);
|
|
213
|
+
}
|
|
214
|
+
done();
|
|
215
|
+
} catch (err) {
|
|
216
|
+
done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
_destroy(err, done) {
|
|
220
|
+
this.#encoder?.free();
|
|
221
|
+
this.#buffer = Buffer.alloc(0);
|
|
222
|
+
done(err);
|
|
223
|
+
}
|
|
224
|
+
#encodeFrame(frame) {
|
|
225
|
+
this.push(Buffer.from(this.#encoder.encode(frame, { frameSize: DISCORD_OPUS_FRAME_SIZE })));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
function pcmInt16ToBuffer(pcm) {
|
|
229
|
+
return Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength);
|
|
230
|
+
}
|
|
231
|
+
async function decodeOpusStream(stream, params) {
|
|
232
|
+
return await readByteStreamWithLimit(decodeOpusFrames(stream, params), {
|
|
233
|
+
maxBytes: params.maxBytes,
|
|
234
|
+
onOverflow: () => /* @__PURE__ */ new Error(`Discord voice capture exceeds the transcription PCM limit (${params.maxBytes} bytes); speak a shorter segment.`)
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async function decodeOpusStreamChunks(stream, params) {
|
|
238
|
+
try {
|
|
239
|
+
for await (const pcm of decodeOpusFrames(stream, params)) params.onChunk(pcm);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
params.onError?.(err);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function* decodeOpusFrames(stream, params) {
|
|
245
|
+
let decoder;
|
|
246
|
+
try {
|
|
247
|
+
decoder = await createDecoder({
|
|
248
|
+
channels: CHANNELS,
|
|
249
|
+
sampleRate: SAMPLE_RATE
|
|
250
|
+
});
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (!warnedOpusMissing) {
|
|
253
|
+
warnedOpusMissing = true;
|
|
254
|
+
params.onWarn(`discord voice: no usable opus decoder available (libopus-wasm: ${formatErrorMessage(err)}); cannot decode voice audio`);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
params.onVerbose("opus decoder: libopus-wasm");
|
|
259
|
+
try {
|
|
260
|
+
for await (const chunk of stream) {
|
|
261
|
+
if (!chunk || !(chunk instanceof Buffer) || chunk.length === 0) continue;
|
|
262
|
+
const decoded = decoder.decode(chunk, { maxFrameSize: DISCORD_OPUS_FRAME_SIZE });
|
|
263
|
+
if (decoded.length > 0) yield pcmInt16ToBuffer(decoded);
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
params.onError?.(err);
|
|
267
|
+
if (shouldLogVerbose()) logVerbose(`discord voice: opus decode failed: ${formatErrorMessage(err)}`);
|
|
268
|
+
} finally {
|
|
269
|
+
decoder.free();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function convertDiscordPcm48kStereoToRealtimePcm24kMono(pcm) {
|
|
273
|
+
const frameCount = Math.floor(pcm.length / 4);
|
|
274
|
+
if (frameCount === 0) return Buffer.alloc(0);
|
|
275
|
+
const mono48k = Buffer.alloc(frameCount * 2);
|
|
276
|
+
for (let frame = 0; frame < frameCount; frame += 1) {
|
|
277
|
+
const offset = frame * 4;
|
|
278
|
+
const left = pcm.readInt16LE(offset);
|
|
279
|
+
const right = pcm.readInt16LE(offset + 2);
|
|
280
|
+
mono48k.writeInt16LE(Math.round((left + right) / 2), frame * 2);
|
|
281
|
+
}
|
|
282
|
+
return resamplePcm(mono48k, SAMPLE_RATE, 24e3);
|
|
283
|
+
}
|
|
284
|
+
function convertRealtimePcm24kMonoToDiscordPcm48kStereo(pcm) {
|
|
285
|
+
const mono48k = resamplePcm(pcm, 24e3, SAMPLE_RATE);
|
|
286
|
+
const sampleCount = Math.floor(mono48k.length / 2);
|
|
287
|
+
if (sampleCount === 0) return Buffer.alloc(0);
|
|
288
|
+
const stereo = Buffer.alloc(sampleCount * 4);
|
|
289
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
290
|
+
const sample = mono48k.readInt16LE(sampleIndex * 2);
|
|
291
|
+
const offset = sampleIndex * 4;
|
|
292
|
+
stereo.writeInt16LE(sample, offset);
|
|
293
|
+
stereo.writeInt16LE(sample, offset + 2);
|
|
294
|
+
}
|
|
295
|
+
return stereo;
|
|
296
|
+
}
|
|
297
|
+
function estimateDurationSeconds(pcm) {
|
|
298
|
+
const bytesPerSample = BIT_DEPTH / 8 * CHANNELS;
|
|
299
|
+
return pcm.length / (bytesPerSample * SAMPLE_RATE);
|
|
300
|
+
}
|
|
301
|
+
async function writeVoiceWavFile(pcm) {
|
|
302
|
+
const workspace = await tempWorkspace({
|
|
303
|
+
rootDir: resolvePreferredOpenClawTmpDir(),
|
|
304
|
+
prefix: "discord-voice-"
|
|
305
|
+
});
|
|
306
|
+
scheduleTempCleanup(workspace.dir);
|
|
307
|
+
const wav = buildWavBuffer(pcm);
|
|
308
|
+
return {
|
|
309
|
+
path: await workspace.write("segment.wav", wav),
|
|
310
|
+
durationSeconds: estimateDurationSeconds(pcm)
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function scheduleTempCleanup(tempDir, delayMs = 18e5) {
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
fs.rm(tempDir, {
|
|
316
|
+
recursive: true,
|
|
317
|
+
force: true
|
|
318
|
+
}).catch((err) => {
|
|
319
|
+
if (shouldLogVerbose()) logVerbose(`discord voice: temp cleanup failed for ${tempDir}: ${formatErrorMessage(err)}`);
|
|
320
|
+
});
|
|
321
|
+
}, delayMs).unref();
|
|
322
|
+
}
|
|
323
|
+
const CAPTURE_FINALIZE_GRACE_MS = 2e3;
|
|
324
|
+
const VOICE_CONNECT_READY_TIMEOUT_MS = 3e4;
|
|
325
|
+
const VOICE_RECONNECT_GRACE_MS = 15e3;
|
|
326
|
+
const PLAYBACK_READY_TIMEOUT_MS = 6e4;
|
|
327
|
+
function resolveVoiceTimeoutMs(value, fallbackMs) {
|
|
328
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return fallbackMs;
|
|
329
|
+
return Math.floor(value);
|
|
330
|
+
}
|
|
331
|
+
function resolveDiscordVoiceMode(voice) {
|
|
332
|
+
const mode = voice?.mode;
|
|
333
|
+
if (mode === "stt-tts" || mode === "bidi") return mode;
|
|
334
|
+
return "agent-proxy";
|
|
335
|
+
}
|
|
336
|
+
function isDiscordRealtimeVoiceMode(mode) {
|
|
337
|
+
return mode === "agent-proxy" || mode === "bidi";
|
|
338
|
+
}
|
|
339
|
+
function logVoiceVerbose(message) {
|
|
340
|
+
logVerbose(`discord voice: ${message}`);
|
|
341
|
+
}
|
|
342
|
+
function isVoiceChannel(type) {
|
|
343
|
+
return type === discord_exports.ChannelType.GuildVoice || type === discord_exports.ChannelType.GuildStageVoice;
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region extensions/discord/src/voice/sdk-runtime.ts
|
|
347
|
+
let cachedDiscordVoiceSdk = null;
|
|
348
|
+
function loadDiscordVoiceSdk() {
|
|
349
|
+
if (cachedDiscordVoiceSdk) return cachedDiscordVoiceSdk;
|
|
350
|
+
cachedDiscordVoiceSdk = createRequire(import.meta.url)("@discordjs/voice");
|
|
351
|
+
return cachedDiscordVoiceSdk;
|
|
352
|
+
}
|
|
353
|
+
//#endregion
|
|
22
354
|
//#region extensions/discord/src/voice/ingress.ts
|
|
23
355
|
const DISCORD_VOICE_MESSAGE_PROVIDER = "discord-voice";
|
|
24
356
|
const logger$7 = createSubsystemLogger("discord/voice");
|
|
@@ -1030,70 +1362,43 @@ var DiscordVoiceFollowing = class {
|
|
|
1030
1362
|
//#endregion
|
|
1031
1363
|
//#region extensions/discord/src/voice/capture-state.ts
|
|
1032
1364
|
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
|
-
};
|
|
1365
|
+
return /* @__PURE__ */ new Map();
|
|
1039
1366
|
}
|
|
1040
1367
|
function stopVoiceCaptureState(state) {
|
|
1041
|
-
|
|
1042
|
-
state.
|
|
1043
|
-
for (const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
function
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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);
|
|
1368
|
+
const captures = [...state.values()];
|
|
1369
|
+
state.clear();
|
|
1370
|
+
for (const capture of captures) {
|
|
1371
|
+
clearVoiceCaptureFinalizeTimer(capture);
|
|
1372
|
+
capture.stream?.destroy();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function clearVoiceCaptureFinalizeTimer(capture) {
|
|
1376
|
+
if (!capture.finalizeTimer) return false;
|
|
1377
|
+
clearTimeout(capture.finalizeTimer);
|
|
1378
|
+
delete capture.finalizeTimer;
|
|
1059
1379
|
return true;
|
|
1060
1380
|
}
|
|
1061
1381
|
function beginVoiceCapture(state, userId, stream) {
|
|
1062
|
-
const
|
|
1063
|
-
state.
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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);
|
|
1382
|
+
const capture = { stream };
|
|
1383
|
+
state.set(userId, capture);
|
|
1384
|
+
return capture;
|
|
1385
|
+
}
|
|
1386
|
+
function finishVoiceCapture(state, userId, capture) {
|
|
1387
|
+
clearVoiceCaptureFinalizeTimer(capture);
|
|
1388
|
+
if (state.get(userId) !== capture) return false;
|
|
1389
|
+
state.delete(userId);
|
|
1077
1390
|
return true;
|
|
1078
1391
|
}
|
|
1079
1392
|
function scheduleVoiceCaptureFinalize(params) {
|
|
1080
1393
|
const { state, userId, delayMs, onFinalize } = params;
|
|
1081
|
-
const capture = state.
|
|
1394
|
+
const capture = state.get(userId);
|
|
1082
1395
|
if (!capture) return false;
|
|
1083
|
-
clearVoiceCaptureFinalizeTimer(
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
state.activeCaptureStreams.delete(userId);
|
|
1089
|
-
state.activeSpeakers.delete(userId);
|
|
1090
|
-
onFinalize?.(activeCapture);
|
|
1091
|
-
activeCapture.stream.destroy();
|
|
1396
|
+
clearVoiceCaptureFinalizeTimer(capture);
|
|
1397
|
+
capture.finalizeTimer = setTimeout(() => {
|
|
1398
|
+
if (!finishVoiceCapture(state, userId, capture)) return;
|
|
1399
|
+
onFinalize?.(capture);
|
|
1400
|
+
capture.stream?.destroy();
|
|
1092
1401
|
}, delayMs);
|
|
1093
|
-
state.captureFinalizeTimers.set(userId, {
|
|
1094
|
-
generation: capture.generation,
|
|
1095
|
-
timer
|
|
1096
|
-
});
|
|
1097
1402
|
return true;
|
|
1098
1403
|
}
|
|
1099
1404
|
//#endregion
|
|
@@ -1115,12 +1420,13 @@ function sanitizeVoiceReplyTextForSpeech(text, speakerLabel) {
|
|
|
1115
1420
|
//#endregion
|
|
1116
1421
|
//#region extensions/discord/src/voice/tts.ts
|
|
1117
1422
|
async function transcribeVoiceAudio(params) {
|
|
1118
|
-
|
|
1423
|
+
const result = await getDiscordRuntime().mediaUnderstanding.transcribeAudioFile({
|
|
1119
1424
|
filePath: params.filePath,
|
|
1120
1425
|
cfg: params.cfg,
|
|
1121
1426
|
agentDir: resolveAgentDir(params.cfg, params.agentId),
|
|
1122
1427
|
mime: "audio/wav"
|
|
1123
|
-
})
|
|
1428
|
+
});
|
|
1429
|
+
return normalizeOptionalString(result.text);
|
|
1124
1430
|
}
|
|
1125
1431
|
async function synthesizeVoiceReplyAudio(params) {
|
|
1126
1432
|
const runtime = getDiscordRuntime();
|
|
@@ -1199,6 +1505,7 @@ async function processDiscordVoiceSegment(params) {
|
|
|
1199
1505
|
logVoiceVerbose(`transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
|
|
1200
1506
|
logVoiceVerbose(`transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceLogPreview(transcript)}`);
|
|
1201
1507
|
if (params.transcripts) {
|
|
1508
|
+
if (entry.sessionLifecycle.status === "stopped" || entry.transcripts !== params.transcripts) return;
|
|
1202
1509
|
await params.transcripts.onUtterance({
|
|
1203
1510
|
sessionId: params.transcripts.sessionId,
|
|
1204
1511
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1335,38 +1642,22 @@ var DiscordVoiceReceive = class {
|
|
|
1335
1642
|
const botUserId = this.params.botUserId();
|
|
1336
1643
|
if (botUserId && userId === botUserId) return;
|
|
1337
1644
|
this.params.membership.notePresent(entry, userId);
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
const extended =
|
|
1645
|
+
const activeCapture = entry.capture.get(userId);
|
|
1646
|
+
if (activeCapture) {
|
|
1647
|
+
const extended = clearVoiceCaptureFinalizeTimer(activeCapture);
|
|
1341
1648
|
logVoiceVerbose(`capture start ignored (already active): guild ${entry.guildId} channel ${entry.channelId} user ${userId}${extended ? " (finalize canceled)" : ""}`);
|
|
1342
1649
|
return;
|
|
1343
1650
|
}
|
|
1344
1651
|
logVoiceVerbose(`capture start: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1345
1652
|
const voiceSdk = loadDiscordVoiceSdk();
|
|
1653
|
+
const transcripts = entry.transcripts;
|
|
1346
1654
|
const voiceMode = resolveDiscordVoiceMode(this.params.discordConfig.voice);
|
|
1347
1655
|
const realtime = entry.realtimeLifecycle.status === "active" && isDiscordRealtimeVoiceMode(voiceMode) ? entry.realtimeLifecycle.instance : void 0;
|
|
1348
1656
|
if (entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && !realtime) {
|
|
1349
1657
|
logVoiceVerbose(`capture ignored during playback: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1350
1658
|
return;
|
|
1351
1659
|
}
|
|
1352
|
-
const
|
|
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);
|
|
1660
|
+
const capture = beginVoiceCapture(entry.capture, userId);
|
|
1370
1661
|
let streamAborted = false;
|
|
1371
1662
|
let receiveFailureHandled = false;
|
|
1372
1663
|
let receiveStreamEndHandled = false;
|
|
@@ -1383,8 +1674,25 @@ var DiscordVoiceReceive = class {
|
|
|
1383
1674
|
receiveFailureHandled = true;
|
|
1384
1675
|
this.handleReceiveError(entry, err);
|
|
1385
1676
|
};
|
|
1386
|
-
stream.on("error", handleStreamError);
|
|
1387
1677
|
try {
|
|
1678
|
+
const realtimeIngress = realtime ? await this.resolveDiscordVoiceIngressContext(entry, userId) : void 0;
|
|
1679
|
+
if (realtime && !realtimeIngress) {
|
|
1680
|
+
logVoiceVerbose(`realtime capture unauthorized: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (!this.params.isEntryCurrent(entry) || entry.capture.get(userId) !== capture) return;
|
|
1684
|
+
if (entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && realtime) {
|
|
1685
|
+
if (!realtime.isBargeInEnabled()) {
|
|
1686
|
+
logger$3.info(`discord voice: realtime capture ignored during playback (barge-in disabled): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
logVoiceVerbose(`realtime barge-in: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1690
|
+
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}`);
|
|
1691
|
+
realtime.handleBargeIn("speaker-start");
|
|
1692
|
+
}
|
|
1693
|
+
this.enableDaveReceivePassthrough(entry, `speaker ${userId} start`, 15);
|
|
1694
|
+
const stream = capture.stream = entry.connection.receiver.subscribe(userId, { end: { behavior: voiceSdk.EndBehaviorType.Manual } });
|
|
1695
|
+
stream.on("error", handleStreamError);
|
|
1388
1696
|
if (realtime && realtimeIngress) {
|
|
1389
1697
|
const turn = realtime.beginSpeakerTurn(realtimeIngress, userId);
|
|
1390
1698
|
try {
|
|
@@ -1399,7 +1707,12 @@ var DiscordVoiceReceive = class {
|
|
|
1399
1707
|
}
|
|
1400
1708
|
return;
|
|
1401
1709
|
}
|
|
1710
|
+
if (!entry.audioInputBudget.enabled) {
|
|
1711
|
+
logger$3.warn("discord voice: capture skipped: audio understanding is disabled; enable tools.media.audio.enabled to transcribe voice.");
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1402
1714
|
const pcm = await decodeOpusStream(stream, {
|
|
1715
|
+
maxBytes: Math.max(0, entry.audioInputBudget.maxBytes - 44),
|
|
1403
1716
|
onError: handleStreamError,
|
|
1404
1717
|
onVerbose: logVoiceVerbose,
|
|
1405
1718
|
onWarn: (message) => logger$3.warn(message)
|
|
@@ -1420,6 +1733,7 @@ var DiscordVoiceReceive = class {
|
|
|
1420
1733
|
logVoiceVerbose(`capture ready (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
|
|
1421
1734
|
entry.processingQueue = entry.processingQueue.then(async () => {
|
|
1422
1735
|
if (!this.params.isEntryCurrent(entry)) return;
|
|
1736
|
+
if (entry.transcripts !== transcripts) return;
|
|
1423
1737
|
await this.processSegment({
|
|
1424
1738
|
entry,
|
|
1425
1739
|
wavPath,
|
|
@@ -1431,8 +1745,9 @@ var DiscordVoiceReceive = class {
|
|
|
1431
1745
|
if (!receiveFailureHandled) this.handleReceiveError(entry, err);
|
|
1432
1746
|
throw err;
|
|
1433
1747
|
} finally {
|
|
1434
|
-
stream.
|
|
1435
|
-
|
|
1748
|
+
const stream = capture.stream;
|
|
1749
|
+
stream?.off?.("error", handleStreamError);
|
|
1750
|
+
if (finishVoiceCapture(entry.capture, userId, capture) && stream && !stream.destroyed) stream.destroy();
|
|
1436
1751
|
}
|
|
1437
1752
|
}
|
|
1438
1753
|
async processSegment(params) {
|
|
@@ -1613,6 +1928,17 @@ const REALTIME_PLAYBACK_MAX_MISSED_FRAMES = 100;
|
|
|
1613
1928
|
function isVoiceSessionStopped(entry) {
|
|
1614
1929
|
return entry.sessionLifecycle.status === "stopped";
|
|
1615
1930
|
}
|
|
1931
|
+
function retireTranscripts(entry) {
|
|
1932
|
+
const transcripts = entry.transcripts;
|
|
1933
|
+
entry.transcripts = void 0;
|
|
1934
|
+
try {
|
|
1935
|
+
Promise.resolve(transcripts?.onStop?.()).catch((error) => {
|
|
1936
|
+
logger$2.warn(`discord voice: transcripts retirement failed: ${formatErrorMessage(error)}`);
|
|
1937
|
+
});
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
logger$2.warn(`discord voice: transcripts retirement failed: ${formatErrorMessage(error)}`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1616
1942
|
function isVoiceConnectionDestroyed(connection, voiceSdk) {
|
|
1617
1943
|
return connection.state.status === voiceSdk.VoiceConnectionStatus.Destroyed;
|
|
1618
1944
|
}
|
|
@@ -1699,7 +2025,10 @@ var DiscordVoiceSessions = class {
|
|
|
1699
2025
|
if (existing && existing.channelId === channelId) {
|
|
1700
2026
|
existing.autoJoinWhenOccupied = options?.autoJoinWhenOccupied === true;
|
|
1701
2027
|
if (authority) existing.generation = authority.generation;
|
|
1702
|
-
if (options?.transcripts)
|
|
2028
|
+
if (options?.transcripts) {
|
|
2029
|
+
retireTranscripts(existing);
|
|
2030
|
+
existing.transcripts = options.transcripts;
|
|
2031
|
+
}
|
|
1703
2032
|
if (!options?.transcripts && isDiscordRealtimeVoiceMode(voiceMode) && existing.realtimeLifecycle.status !== "active" && existing.realtimeLifecycle.status !== "starting") {
|
|
1704
2033
|
const realtimeResult = await this.attachRealtimeSession(existing, voiceMode, {
|
|
1705
2034
|
requireLiveEntry: true,
|
|
@@ -1751,6 +2080,8 @@ var DiscordVoiceSessions = class {
|
|
|
1751
2080
|
ok: false,
|
|
1752
2081
|
message: "Discord voice plugin is not available."
|
|
1753
2082
|
};
|
|
2083
|
+
const audioInputBudget = await getDiscordRuntime().mediaUnderstanding.resolveAudioInputBudget({ cfg: this.params.cfg });
|
|
2084
|
+
if (authority && !authority.isCurrent()) return cancelledJoinResult();
|
|
1754
2085
|
const adapterCreator = voicePlugin.getGatewayAdapterCreator(guildId);
|
|
1755
2086
|
const daveEncryption = voiceConfig?.daveEncryption;
|
|
1756
2087
|
const decryptionFailureTolerance = voiceConfig?.decryptionFailureTolerance;
|
|
@@ -1862,6 +2193,7 @@ var DiscordVoiceSessions = class {
|
|
|
1862
2193
|
status: "stopped",
|
|
1863
2194
|
reason: optionsLocal.reason
|
|
1864
2195
|
};
|
|
2196
|
+
retireTranscripts(entry);
|
|
1865
2197
|
if (this.params.sessions.get(guildId) === entry) this.params.sessions.delete(guildId);
|
|
1866
2198
|
this.params.membership.deactivate(entry);
|
|
1867
2199
|
connection.receiver.speaking.off("start", speakingHandler);
|
|
@@ -1900,6 +2232,7 @@ var DiscordVoiceSessions = class {
|
|
|
1900
2232
|
player,
|
|
1901
2233
|
playbackQueue: Promise.resolve(),
|
|
1902
2234
|
processingQueue: Promise.resolve(),
|
|
2235
|
+
audioInputBudget,
|
|
1903
2236
|
ttsStreamFallbackWarned: false,
|
|
1904
2237
|
capture: createVoiceCaptureState(),
|
|
1905
2238
|
transcripts: options?.transcripts,
|
|
@@ -2004,7 +2337,7 @@ var DiscordVoiceSessions = class {
|
|
|
2004
2337
|
channelId: entry.channelId
|
|
2005
2338
|
};
|
|
2006
2339
|
if (entry.realtimeLifecycle.status === "active" || entry.realtimeLifecycle.status === "starting") {
|
|
2007
|
-
entry
|
|
2340
|
+
retireTranscripts(entry);
|
|
2008
2341
|
return {
|
|
2009
2342
|
ok: true,
|
|
2010
2343
|
message: `Stopped transcripts for ${formatMention({ channelId: entry.channelId })}.`,
|
|
@@ -2034,7 +2367,7 @@ var DiscordVoiceSessions = class {
|
|
|
2034
2367
|
ok: false,
|
|
2035
2368
|
message: "Discord realtime voice session stopped before startup completed."
|
|
2036
2369
|
};
|
|
2037
|
-
const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-
|
|
2370
|
+
const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-DgSbX6ay.js");
|
|
2038
2371
|
const realtime = new DiscordRealtimeVoiceSession({
|
|
2039
2372
|
accountId: this.params.accountId,
|
|
2040
2373
|
bootstrapContextInstructions,
|
|
@@ -2138,6 +2471,13 @@ var DiscordVoiceStateUpdateListener = class extends VoiceStateUpdateListener {
|
|
|
2138
2471
|
};
|
|
2139
2472
|
//#endregion
|
|
2140
2473
|
//#region extensions/discord/src/voice/voice-runtime.ts
|
|
2474
|
+
var voice_runtime_exports = /* @__PURE__ */ __exportAll({
|
|
2475
|
+
DiscordVoiceGuildCreateListener: () => DiscordVoiceGuildCreateListener,
|
|
2476
|
+
DiscordVoiceManager: () => DiscordVoiceManager,
|
|
2477
|
+
DiscordVoiceReadyListener: () => DiscordVoiceReadyListener,
|
|
2478
|
+
DiscordVoiceResumedListener: () => DiscordVoiceResumedListener,
|
|
2479
|
+
DiscordVoiceStateUpdateListener: () => DiscordVoiceStateUpdateListener
|
|
2480
|
+
});
|
|
2141
2481
|
const logger = createSubsystemLogger("discord/voice");
|
|
2142
2482
|
const DISCORD_VOICE_FATAL_AUTOJOIN_ERROR_PATTERNS = [
|
|
2143
2483
|
"api key missing",
|
|
@@ -2538,4 +2878,4 @@ var DiscordVoiceManager = class {
|
|
|
2538
2878
|
}
|
|
2539
2879
|
};
|
|
2540
2880
|
//#endregion
|
|
2541
|
-
export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener };
|
|
2881
|
+
export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener, convertRealtimePcm24kMonoToDiscordPcm48kStereo as a, maybeControlDiscordVoiceAgentRun as c, convertDiscordPcm48kStereoToRealtimePcm24kMono as i, formatVoiceLogPreview as l, loadDiscordVoiceSdk as n, createDiscordOpusEncodeStream as o, logVoiceVerbose as r, formatVoiceIngressPrompt as s, voice_runtime_exports as t };
|