@openclaw/discord 2026.5.25-beta.1 → 2026.5.26-beta.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 (32) hide show
  1. package/dist/api.js +1 -1
  2. package/dist/{channel-CmaowH-7.js → channel-XYTSE8wN.js} +1 -1
  3. package/dist/channel-plugin-api.js +1 -1
  4. package/dist/index.js +3 -3
  5. package/dist/{manager.runtime-C5dZwVX1.js → manager.runtime-Bs0ngLSw.js} +145 -50
  6. package/dist/{message-handler-DUNd0JjP.js → message-handler-B9n5CtIS.js} +2 -2
  7. package/dist/{message-handler.preflight-DHZfoN6e.js → message-handler.preflight-Cu8cFBgW.js} +2 -2
  8. package/dist/{message-handler.process-C0xo2jq3.js → message-handler.process-DO17vBlT.js} +1 -1
  9. package/dist/{provider-D-XVxGT7.js → provider-D3RwHRhI.js} +593 -197
  10. package/dist/{provider-session.runtime-C_z8oLu7.js → provider-session.runtime-DbNYskMy.js} +1 -1
  11. package/dist/provider.runtime-rUg1sHKP.js +2 -0
  12. package/dist/runtime-api.js +3 -3
  13. package/dist/{runtime-api.monitor-DgBpOYpt.js → runtime-api.monitor-CUn-x5uG.js} +2 -2
  14. package/dist/runtime-api.monitor.js +3 -3
  15. package/dist/test-api.js +1 -1
  16. package/dist/transcripts-source-api.js +2 -0
  17. package/dist/{meeting-notes-source-DXpiff3y.js → transcripts-source-emawQzBc.js} +12 -12
  18. package/node_modules/libopus-wasm/CHANGELOG.md +28 -0
  19. package/node_modules/libopus-wasm/README.md +202 -41
  20. package/node_modules/libopus-wasm/dist/discordjs.d.ts +20 -0
  21. package/node_modules/libopus-wasm/dist/discordjs.d.ts.map +1 -0
  22. package/node_modules/libopus-wasm/dist/discordjs.js +108 -0
  23. package/node_modules/libopus-wasm/dist/generated/libopus.generated.mjs +0 -0
  24. package/node_modules/libopus-wasm/dist/index.d.ts +109 -15
  25. package/node_modules/libopus-wasm/dist/index.d.ts.map +1 -1
  26. package/node_modules/libopus-wasm/dist/index.js +476 -70
  27. package/node_modules/libopus-wasm/package.json +14 -3
  28. package/npm-shrinkwrap.json +7 -8
  29. package/openclaw.plugin.json +1 -1
  30. package/package.json +6 -8
  31. package/dist/meeting-notes-source-api.js +0 -2
  32. package/dist/provider.runtime-xwgFlddx.js +0 -2
package/dist/api.js CHANGED
@@ -6,7 +6,7 @@ import { n as fetchDiscord, r as requestDiscord, t as DiscordApiError } from "./
6
6
  import { i as parseDiscordSendTarget, n as resolveDiscordTarget } from "./target-resolver-CVgOsap6.js";
7
7
  import "./targets-CNDNKpqQ.js";
8
8
  import { a as getDiscordExecApprovalApprovers, c as shouldSuppressLocalDiscordExecApprovalPrompt, o as isDiscordExecApprovalApprover, s as isDiscordExecApprovalClientEnabled } from "./conversation-identity-Be5JQPP9.js";
9
- import { i as resolveDiscordGroupToolPolicy, n as collectDiscordStatusIssues, r as resolveDiscordGroupRequireMention, t as discordPlugin } from "./channel-CmaowH-7.js";
9
+ import { i as resolveDiscordGroupToolPolicy, n as collectDiscordStatusIssues, r as resolveDiscordGroupRequireMention, t as discordPlugin } from "./channel-XYTSE8wN.js";
10
10
  import { t as normalizeExplicitDiscordSessionKey } from "./session-key-normalization-wJgsKPNF.js";
11
11
  import { t as discordSetupPlugin } from "./channel.setup-JaozRxYz.js";
12
12
  import { n as handleDiscordSubagentEnded, r as handleDiscordSubagentSpawning, t as handleDiscordSubagentDeliveryTarget } from "./subagent-hooks-DHA_1pBI.js";
@@ -120,7 +120,7 @@ const loadDiscordResolveUsersModule = createLazyRuntimeModule(() => import("./re
120
120
  const loadDiscordThreadBindingsManagerModule = createLazyRuntimeModule(() => import("./thread-bindings.manager-UJ5FvZfO.js").then((n) => n.a));
121
121
  const loadDiscordTargetResolverModule = createLazyRuntimeModule(() => import("./target-resolver-CVgOsap6.js").then((n) => n.r));
122
122
  async function loadDiscordProviderRuntime() {
123
- discordProviderRuntimePromise ??= import("./provider.runtime-xwgFlddx.js");
123
+ discordProviderRuntimePromise ??= import("./provider.runtime-rUg1sHKP.js");
124
124
  return await discordProviderRuntimePromise;
125
125
  }
126
126
  async function loadDiscordProbeRuntime() {
@@ -1,2 +1,2 @@
1
- import { t as discordPlugin } from "./channel-CmaowH-7.js";
1
+ import { t as discordPlugin } from "./channel-XYTSE8wN.js";
2
2
  export { discordPlugin };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as discordVoiceMeetingNotesSourceProvider } from "./meeting-notes-source-DXpiff3y.js";
2
- import "./meeting-notes-source-api.js";
3
1
  import { registerDiscordSubagentHooks } from "./subagent-hooks-api.js";
2
+ import { t as discordVoiceTranscriptsSourceProvider } from "./transcripts-source-emawQzBc.js";
3
+ import "./transcripts-source-api.js";
4
4
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
5
5
  //#region extensions/discord/index.ts
6
6
  var discord_default = defineBundledChannelEntry({
@@ -22,7 +22,7 @@ var discord_default = defineBundledChannelEntry({
22
22
  },
23
23
  registerFull(api) {
24
24
  registerDiscordSubagentHooks(api);
25
- api.registerMeetingNotesSourceProvider(discordVoiceMeetingNotesSourceProvider);
25
+ api.registerTranscriptSourceProvider(discordVoiceTranscriptsSourceProvider);
26
26
  }
27
27
  });
28
28
  //#endregion
@@ -3,12 +3,13 @@ import { c as resolveDiscordAccountAllowFrom } from "./accounts-dXTfmnSZ.js";
3
3
  import { a as normalizeDiscordSlug, b as formatDiscordUserTag, m as resolveDiscordOwnerAccess } from "./allow-list-BnkWtVpA.js";
4
4
  import { i as formatMention } from "./send.outbound-BHQPWbwU.js";
5
5
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
6
- import { o as authorizeDiscordVoiceIngress, u as resolveDiscordVoiceEnabled } from "./provider-D-XVxGT7.js";
6
+ import { o as authorizeDiscordVoiceIngress, u as resolveDiscordVoiceEnabled } from "./provider-D3RwHRhI.js";
7
7
  import { t as buildDiscordGroupSystemPrompt } from "./inbound-context-B5EsqsSr.js";
8
8
  import { createRequire } from "node:module";
9
9
  import { asBoolean, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
10
10
  import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
11
11
  import path from "node:path";
12
+ import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-runtime";
12
13
  import { createSubsystemLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
13
14
  import { stripInlineDirectiveTagsForDisplay } from "openclaw/plugin-sdk/text-chunking";
14
15
  import fs from "node:fs/promises";
@@ -17,22 +18,26 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
17
18
  import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONTROL_TOOL, REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentConsultChatMessage, buildRealtimeVoiceAgentConsultPolicyInstructions, classifySkippableRealtimeVoiceConsultTranscript, controlRealtimeVoiceAgentRun, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceBridgeSession, createRealtimeVoiceForcedConsultCoordinator, createRealtimeVoiceOutputActivityTracker, createRealtimeVoiceTurnContextTracker, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, normalizeSupportedRealtimeVoiceActivationName, parseRealtimeVoiceAgentControlToolArgs, resamplePcm, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultToolPolicy, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, shouldAutoControlRealtimeVoiceAgentText, sortRealtimeVoiceActivationNames } from "openclaw/plugin-sdk/realtime-voice";
18
19
  import { agentCommandFromIngress, getTtsProvider, resolveAgentDir, resolveTtsConfig, resolveTtsPrefsPath } from "openclaw/plugin-sdk/agent-runtime";
19
20
  import { escapeRegExp } from "openclaw/plugin-sdk/text-utility-runtime";
21
+ import { spawn } from "node:child_process";
20
22
  import { PassThrough, Readable, Transform } from "node:stream";
21
23
  import { Application, createDecoder, createEncoder } from "libopus-wasm";
22
- import prism from "prism-media";
23
24
  import { resolveRealtimeBootstrapContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
24
25
  import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
25
26
  //#region extensions/discord/src/voice/audio.ts
26
27
  const SAMPLE_RATE = 48e3;
27
28
  const CHANNELS = 2;
28
29
  const BIT_DEPTH = 16;
30
+ const FFMPEG_ERROR_OUTPUT_BYTES = 8192;
29
31
  const DISCORD_OPUS_FRAME_SIZE = 960;
30
32
  const DISCORD_OPUS_FRAME_BYTES = DISCORD_OPUS_FRAME_SIZE * CHANNELS * (BIT_DEPTH / 8);
31
33
  const FFMPEG_PCM_ARGUMENTS = [
32
34
  "-analyzeduration",
33
35
  "0",
34
36
  "-loglevel",
35
- "0",
37
+ "error",
38
+ "-vn",
39
+ "-sn",
40
+ "-dn",
36
41
  "-f",
37
42
  "s16le",
38
43
  "-ar",
@@ -78,7 +83,7 @@ async function createOpusDecoder(params) {
78
83
  return {
79
84
  name: "libopus-wasm",
80
85
  decoder: {
81
- decode: (buffer) => pcmInt16ToBuffer(decoder.decodeFrame(buffer, DISCORD_OPUS_FRAME_SIZE)),
86
+ decode: (buffer) => pcmInt16ToBuffer(decoder.decode(buffer, { maxFrameSize: DISCORD_OPUS_FRAME_SIZE })),
82
87
  free: () => decoder.free()
83
88
  }
84
89
  };
@@ -87,19 +92,54 @@ function createDiscordOpusEncodeStream() {
87
92
  return new DiscordOpusEncodeStream();
88
93
  }
89
94
  function createDiscordOpusPlaybackStream(input) {
90
- const ffmpeg = new prism.FFmpeg({ args: [
95
+ const inputSource = typeof input === "string" ? input : "pipe:0";
96
+ const ffmpeg = spawn(resolveFfmpegBin(), [
91
97
  "-i",
92
- typeof input === "string" ? input : "-",
93
- ...FFMPEG_PCM_ARGUMENTS
94
- ] });
98
+ inputSource,
99
+ ...FFMPEG_PCM_ARGUMENTS,
100
+ "pipe:1"
101
+ ], {
102
+ stdio: [
103
+ "pipe",
104
+ "pipe",
105
+ "pipe"
106
+ ],
107
+ windowsHide: true
108
+ });
95
109
  const opusStream = createDiscordOpusEncodeStream();
96
- ffmpeg.on("error", (err) => opusStream.destroy(err));
97
- opusStream.on("close", () => ffmpeg.destroy());
110
+ let stderr = "";
111
+ let ffmpegClosed = false;
112
+ ffmpeg.stderr.setEncoding("utf8");
113
+ ffmpeg.stderr.on("data", (chunk) => {
114
+ if (stderr.length < FFMPEG_ERROR_OUTPUT_BYTES) stderr = `${stderr}${chunk}`.slice(0, FFMPEG_ERROR_OUTPUT_BYTES);
115
+ });
116
+ ffmpeg.once("error", (err) => {
117
+ opusStream.destroy(err);
118
+ });
119
+ ffmpeg.once("close", (code, signal) => {
120
+ ffmpegClosed = true;
121
+ if (code && code !== 0) {
122
+ const suffix = stderr.trim() ? `: ${stderr.trim()}` : "";
123
+ opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with code ${code}${suffix}`));
124
+ return;
125
+ }
126
+ if (signal) opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with signal ${signal}`));
127
+ });
128
+ ffmpeg.stdout.on("error", (err) => opusStream.destroy(err));
129
+ ffmpeg.stdin.on("error", (err) => {
130
+ if (err.code !== "EPIPE") opusStream.destroy(err);
131
+ });
132
+ ffmpeg.stdout.pipe(opusStream);
133
+ opusStream.once("close", () => {
134
+ if (!ffmpegClosed && !opusStream.readableEnded) ffmpeg.kill();
135
+ });
98
136
  if (typeof input !== "string") {
99
- input.on("error", (err) => ffmpeg.destroy(err));
100
- input.pipe(ffmpeg);
101
- }
102
- ffmpeg.pipe(opusStream);
137
+ input.on("error", (err) => {
138
+ ffmpeg.stdin.destroy(err);
139
+ opusStream.destroy(err);
140
+ });
141
+ input.pipe(ffmpeg.stdin);
142
+ } else ffmpeg.stdin.end();
103
143
  return opusStream;
104
144
  }
105
145
  var DiscordOpusEncodeStream = class extends Transform {
@@ -125,7 +165,7 @@ var DiscordOpusEncodeStream = class extends Transform {
125
165
  while (this.#buffer.length >= DISCORD_OPUS_FRAME_BYTES) {
126
166
  const frame = this.#buffer.subarray(0, DISCORD_OPUS_FRAME_BYTES);
127
167
  this.#buffer = this.#buffer.subarray(DISCORD_OPUS_FRAME_BYTES);
128
- this.push(Buffer.from(encoder.encodePcm16(frame, DISCORD_OPUS_FRAME_SIZE)));
168
+ this.push(Buffer.from(encoder.encode(frame, { frameSize: DISCORD_OPUS_FRAME_SIZE })));
129
169
  }
130
170
  done();
131
171
  } catch (err) {
@@ -139,7 +179,7 @@ var DiscordOpusEncodeStream = class extends Transform {
139
179
  const frame = Buffer.alloc(DISCORD_OPUS_FRAME_BYTES);
140
180
  this.#buffer.copy(frame);
141
181
  this.#buffer = Buffer.alloc(0);
142
- this.push(Buffer.from(encoder.encodePcm16(frame, DISCORD_OPUS_FRAME_SIZE)));
182
+ this.push(Buffer.from(encoder.encode(frame, { frameSize: DISCORD_OPUS_FRAME_SIZE })));
143
183
  }
144
184
  this.#freeEncoder();
145
185
  done();
@@ -487,6 +527,7 @@ const DISCORD_REALTIME_PENDING_SPEAKER_CONTEXT_LIMIT = 32;
487
527
  const DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_LIMIT = 16;
488
528
  const DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_TTL_MS = 15e3;
489
529
  const DISCORD_REALTIME_IGNORED_WAKE_NAME_CONTEXT_TTL_MS = 1e4;
530
+ const DISCORD_REALTIME_WAKE_NAME_FOLLOWUP_TTL_MS = 1e4;
490
531
  const DISCORD_REALTIME_LOG_PREVIEW_CHARS = 500;
491
532
  const DISCORD_REALTIME_DEFAULT_MIN_BARGE_IN_AUDIO_END_MS = 250;
492
533
  const DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS = 200;
@@ -786,6 +827,7 @@ var DiscordRealtimeVoiceSession = class {
786
827
  this.exactSpeechResponseActive = false;
787
828
  this.exactSpeechAudioStarted = false;
788
829
  this.resetPartialWakeNameTracking();
830
+ this.pendingWakeNameFollowup = void 0;
789
831
  this.clearOutputAudio("session-close");
790
832
  this.bridge?.close();
791
833
  this.bridge = null;
@@ -1208,17 +1250,31 @@ var DiscordRealtimeVoiceSession = class {
1208
1250
  const trimmed = text.trim();
1209
1251
  if (!trimmed) return;
1210
1252
  this.partialUserTranscript = "";
1211
- const meetingNotesTurn = this.peekPendingSpeakerTurn();
1212
- this.recordMeetingNotesUtterance(trimmed, meetingNotesTurn);
1253
+ const transcriptsTurn = this.peekPendingSpeakerTurn();
1254
+ let transcriptAttribution = this.transcriptAttributionFromTurn(transcriptsTurn);
1213
1255
  const wakeNameResult = this.resolveWakeNameTranscript(trimmed);
1256
+ let forcedSpeakerContext;
1214
1257
  if (!wakeNameResult.allowed) {
1215
- this.rememberIgnoredWakeNameSpeakerContext(this.consumePendingSpeakerContext());
1216
- logger$2.info(`discord voice: realtime wake-name gate ignored transcript chars=${trimmed.length} voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId} wakeNames=${this.wakeNames.join(",") || "none"}`);
1258
+ const pendingWakeNameFollowup = this.consumePendingWakeNameFollowup();
1259
+ transcriptAttribution ??= pendingWakeNameFollowup;
1260
+ if (!pendingWakeNameFollowup) {
1261
+ this.recordTranscriptUtterance(trimmed, transcriptAttribution);
1262
+ this.rememberIgnoredWakeNameSpeakerContext(this.consumePendingSpeakerContext());
1263
+ logger$2.info(`discord voice: realtime wake-name gate ignored transcript chars=${trimmed.length} voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId} wakeNames=${this.wakeNames.join(",") || "none"}`);
1264
+ return;
1265
+ }
1266
+ forcedSpeakerContext = pendingWakeNameFollowup.context;
1267
+ logger$2.info(`discord voice: realtime wake-name follow-up accepted chars=${trimmed.length} speaker=${forcedSpeakerContext.speakerLabel} voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId}`);
1268
+ }
1269
+ this.recordTranscriptUtterance(trimmed, transcriptAttribution);
1270
+ const acceptedText = wakeNameResult.allowed ? wakeNameResult.text || trimmed : trimmed;
1271
+ if (wakeNameResult.allowed && !wakeNameResult.text.trim()) {
1272
+ this.armWakeNameFollowup();
1217
1273
  return;
1218
1274
  }
1219
- const acceptedText = wakeNameResult.text || trimmed;
1275
+ if (wakeNameResult.allowed) this.pendingWakeNameFollowup = void 0;
1220
1276
  const usesAgentProxy = isDiscordAgentProxyVoiceMode(this.params.mode);
1221
- const pendingForcedConsult = usesAgentProxy && params.usesRealtimeAgentHandoff ? this.prepareForcedAgentProxyConsult(acceptedText) : void 0;
1277
+ const pendingForcedConsult = usesAgentProxy && params.usesRealtimeAgentHandoff ? this.prepareForcedAgentProxyConsult(acceptedText, forcedSpeakerContext) : void 0;
1222
1278
  const control = await maybeControlDiscordVoiceAgentRun({
1223
1279
  entry: this.params.entry,
1224
1280
  text: acceptedText
@@ -1236,7 +1292,7 @@ var DiscordRealtimeVoiceSession = class {
1236
1292
  if (pendingForcedConsult) this.schedulePreparedForcedAgentProxyConsult(pendingForcedConsult);
1237
1293
  return;
1238
1294
  }
1239
- this.talkback.enqueue(acceptedText, this.consumePendingSpeakerContext());
1295
+ this.talkback.enqueue(acceptedText, forcedSpeakerContext ?? this.consumePendingSpeakerContext());
1240
1296
  }
1241
1297
  handlePartialUserTranscript(text) {
1242
1298
  if (!this.requireWakeName || this.wakeNameAckedForTurn) return;
@@ -1269,13 +1325,19 @@ var DiscordRealtimeVoiceSession = class {
1269
1325
  text
1270
1326
  };
1271
1327
  }
1272
- recordMeetingNotesUtterance(text, turn) {
1273
- const meetingNotes = this.params.entry.meetingNotes;
1274
- if (!meetingNotes || !turn) return;
1275
- const context = turn.context;
1328
+ transcriptAttributionFromTurn(turn) {
1329
+ return turn ? {
1330
+ context: turn.context,
1331
+ startedAt: turn.startedAt
1332
+ } : void 0;
1333
+ }
1334
+ recordTranscriptUtterance(text, attribution) {
1335
+ const transcripts = this.params.entry.transcripts;
1336
+ if (!transcripts || !attribution) return;
1337
+ const context = attribution.context;
1276
1338
  const utterance = {
1277
- sessionId: meetingNotes.sessionId,
1278
- startedAt: new Date(turn.startedAt).toISOString(),
1339
+ sessionId: transcripts.sessionId,
1340
+ startedAt: new Date(attribution.startedAt).toISOString(),
1279
1341
  final: true,
1280
1342
  speaker: {
1281
1343
  id: context.userId,
@@ -1289,14 +1351,14 @@ var DiscordRealtimeVoiceSession = class {
1289
1351
  voiceSessionKey: this.params.entry.voiceSessionKey
1290
1352
  }
1291
1353
  };
1292
- Promise.resolve().then(() => meetingNotes.onUtterance(utterance)).catch((error) => {
1293
- logger$2.warn(`discord voice: realtime meeting notes utterance failed: ${formatErrorMessage(error)}`);
1354
+ Promise.resolve().then(() => transcripts.onUtterance(utterance)).catch((error) => {
1355
+ logger$2.warn(`discord voice: realtime transcripts utterance failed: ${formatErrorMessage(error)}`);
1294
1356
  });
1295
1357
  }
1296
1358
  logAgentControlResult(result) {
1297
1359
  logger$2.info(`discord voice: realtime active-run control handled mode=${result.mode} ok=${result.ok} active=${result.active} reason=${result.reason ?? "none"} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId}`);
1298
1360
  }
1299
- prepareForcedAgentProxyConsult(transcript) {
1361
+ prepareForcedAgentProxyConsult(transcript, speakerContext) {
1300
1362
  if (this.consultPolicy !== "always" && !this.requireWakeName) return;
1301
1363
  const question = transcript.trim();
1302
1364
  if (!question) return;
@@ -1306,7 +1368,7 @@ var DiscordRealtimeVoiceSession = class {
1306
1368
  logger$2.info(`discord voice: realtime forced agent consult skipped reason=${skipReason} chars=${question.length} speaker=${context?.speakerLabel ?? "unknown"} transcript=${formatRealtimeLogPreview(question)}`);
1307
1369
  return;
1308
1370
  }
1309
- let context = this.consumePendingSpeakerContext();
1371
+ let context = speakerContext ?? this.consumePendingSpeakerContext();
1310
1372
  if (!context) context = this.consumeRecentIgnoredWakeNameSpeakerContext();
1311
1373
  if (!context) {
1312
1374
  const recent = this.findRecentAgentProxyConsultContext(question);
@@ -1357,6 +1419,32 @@ var DiscordRealtimeVoiceSession = class {
1357
1419
  consumePendingSpeakerContext() {
1358
1420
  return this.speakerTurns.consumeAudioContext();
1359
1421
  }
1422
+ armWakeNameFollowup() {
1423
+ const turn = this.peekPendingSpeakerTurn();
1424
+ const context = this.consumePendingSpeakerContext();
1425
+ if (!context) {
1426
+ logger$2.warn(`discord voice: realtime wake-name follow-up has no speaker context voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId}`);
1427
+ return;
1428
+ }
1429
+ this.pendingWakeNameFollowup = {
1430
+ context,
1431
+ startedAt: turn?.startedAt ?? Date.now(),
1432
+ expiresAt: Date.now() + DISCORD_REALTIME_WAKE_NAME_FOLLOWUP_TTL_MS
1433
+ };
1434
+ logger$2.info(`discord voice: realtime wake-name follow-up armed speaker=${context.speakerLabel} voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId}`);
1435
+ }
1436
+ consumePendingWakeNameFollowup() {
1437
+ const pending = this.pendingWakeNameFollowup;
1438
+ this.pendingWakeNameFollowup = void 0;
1439
+ if (!pending || Date.now() > pending.expiresAt) return;
1440
+ const currentTurn = this.peekPendingSpeakerTurn();
1441
+ if (currentTurn && currentTurn.context.userId !== pending.context.userId) return;
1442
+ if (currentTurn) this.consumePendingSpeakerContext();
1443
+ return {
1444
+ context: pending.context,
1445
+ startedAt: pending.startedAt
1446
+ };
1447
+ }
1360
1448
  rememberIgnoredWakeNameSpeakerContext(context) {
1361
1449
  this.speakerTurns.rememberIgnoredContext(context);
1362
1450
  }
@@ -1503,7 +1591,7 @@ function isAbortLikeReceiveError(err) {
1503
1591
  if (!err || typeof err !== "object") return false;
1504
1592
  const name = "name" in err && typeof err.name === "string" ? err.name : "";
1505
1593
  const message = "message" in err && typeof err.message === "string" ? err.message : "";
1506
- return name === "AbortError" || message.includes("The operation was aborted") || message.includes("aborted");
1594
+ return name === "AbortError" || message === "Premature close" || message.includes("The operation was aborted") || message.includes("aborted");
1507
1595
  }
1508
1596
  function analyzeVoiceReceiveError(err) {
1509
1597
  const message = formatErrorMessage(err);
@@ -1701,9 +1789,9 @@ async function processDiscordVoiceSegment(params) {
1701
1789
  }
1702
1790
  logVoiceVerbose(`transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
1703
1791
  logVoiceVerbose(`transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceTranscriptLogPreview(transcript)}`);
1704
- if (params.meetingNotes) {
1705
- await params.meetingNotes.onUtterance({
1706
- sessionId: params.meetingNotes.sessionId,
1792
+ if (params.transcripts) {
1793
+ await params.transcripts.onUtterance({
1794
+ sessionId: params.transcripts.sessionId,
1707
1795
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1708
1796
  final: true,
1709
1797
  speaker: {
@@ -1898,6 +1986,13 @@ const DISCORD_VOICE_FATAL_AUTOJOIN_ERROR_PATTERNS = [
1898
1986
  "permission denied",
1899
1987
  "forbidden"
1900
1988
  ];
1989
+ function logFollowUserReconcileVerbose(reason, message) {
1990
+ if (reason === "interval") {
1991
+ logger.trace(`discord voice: ${message}`);
1992
+ return;
1993
+ }
1994
+ logVoiceVerbose(message);
1995
+ }
1901
1996
  function formatVoiceLogPreview(text) {
1902
1997
  const oneLine = text.replace(/\s+/g, " ").trim();
1903
1998
  if (oneLine.length <= VOICE_LOG_PREVIEW_CHARS) return oneLine;
@@ -2165,8 +2260,8 @@ var DiscordVoiceManager$1 = class {
2165
2260
  const voiceMode = resolveDiscordVoiceMode(voiceConfig);
2166
2261
  const existing = this.sessions.get(guildId);
2167
2262
  if (existing && existing.channelId === channelId) {
2168
- if (options?.meetingNotes) existing.meetingNotes = options.meetingNotes;
2169
- if (!options?.meetingNotes && isDiscordRealtimeVoiceMode(voiceMode) && !existing.realtime) {
2263
+ if (options?.transcripts) existing.transcripts = options.transcripts;
2264
+ if (!options?.transcripts && isDiscordRealtimeVoiceMode(voiceMode) && !existing.realtime) {
2170
2265
  const realtimeResult = await this.attachRealtimeSession(existing, voiceMode, { requireLiveEntry: true });
2171
2266
  if (!realtimeResult.ok) return {
2172
2267
  ok: false,
@@ -2342,7 +2437,7 @@ var DiscordVoiceManager$1 = class {
2342
2437
  playbackQueue: Promise.resolve(),
2343
2438
  processingQueue: Promise.resolve(),
2344
2439
  capture: createVoiceCaptureState(),
2345
- meetingNotes: options?.meetingNotes,
2440
+ transcripts: options?.transcripts,
2346
2441
  receiveRecovery: createVoiceReceiveRecoveryState(),
2347
2442
  isStopped: () => stopped,
2348
2443
  stop: () => {
@@ -2352,7 +2447,7 @@ var DiscordVoiceManager$1 = class {
2352
2447
  });
2353
2448
  }
2354
2449
  };
2355
- if (!options?.meetingNotes && isDiscordRealtimeVoiceMode(voiceMode)) {
2450
+ if (!options?.transcripts && isDiscordRealtimeVoiceMode(voiceMode)) {
2356
2451
  const realtimeResult = await this.attachRealtimeSession(entry, voiceMode);
2357
2452
  if (!realtimeResult.ok) {
2358
2453
  destroyVoiceConnectionSafely({
@@ -2489,18 +2584,18 @@ var DiscordVoiceManager$1 = class {
2489
2584
  ok: false,
2490
2585
  message: "Not connected to that voice channel."
2491
2586
  };
2492
- if (options?.meetingNotesSessionId) {
2493
- if (!entry.meetingNotes || entry.meetingNotes.sessionId !== options.meetingNotesSessionId) return {
2587
+ if (options?.transcriptsSessionId) {
2588
+ if (!entry.transcripts || entry.transcripts.sessionId !== options.transcriptsSessionId) return {
2494
2589
  ok: false,
2495
- message: "Meeting notes session is not active in this voice channel.",
2590
+ message: "Transcripts session is not active in this voice channel.",
2496
2591
  guildId,
2497
2592
  channelId: entry.channelId
2498
2593
  };
2499
2594
  if (entry.realtime || entry.pendingRealtime) {
2500
- entry.meetingNotes = void 0;
2595
+ entry.transcripts = void 0;
2501
2596
  return {
2502
2597
  ok: true,
2503
- message: `Stopped meeting notes for ${formatMention({ channelId: entry.channelId })}.`,
2598
+ message: `Stopped transcripts for ${formatMention({ channelId: entry.channelId })}.`,
2504
2599
  guildId,
2505
2600
  channelId: entry.channelId
2506
2601
  };
@@ -2673,7 +2768,7 @@ var DiscordVoiceManager$1 = class {
2673
2768
  logVoiceVerbose(`follow user reconcile skipped reason=${reason}: no Discord guild ids are configured`);
2674
2769
  return;
2675
2770
  }
2676
- logVoiceVerbose(`follow user reconcile reason=${reason}: ${this.followUserIds.size} users across ${guildIds.length} guilds`);
2771
+ logFollowUserReconcileVerbose(reason, `follow user reconcile reason=${reason}: ${this.followUserIds.size} users across ${guildIds.length} guilds`);
2677
2772
  const plans = this.selectFollowUserReconcilePlans(guildIds, reason);
2678
2773
  for (const plan of plans) {
2679
2774
  for (const userId of plan.userIds) {
@@ -2682,7 +2777,7 @@ var DiscordVoiceManager$1 = class {
2682
2777
  logger.warn(`discord voice: follow user reconcile skipped transient voice state error guild=${plan.guildId} user=${userId} reason=${reason}: ${formatErrorMessage(err)}`);
2683
2778
  return "transient-error";
2684
2779
  }
2685
- logVoiceVerbose(`follow user reconcile reason=${reason}: no voice state guild ${plan.guildId} user ${userId}: ${formatErrorMessage(err)}`);
2780
+ logFollowUserReconcileVerbose(reason, `follow user reconcile reason=${reason}: no voice state guild ${plan.guildId} user ${userId}: ${formatErrorMessage(err)}`);
2686
2781
  });
2687
2782
  if (this.destroyed) return;
2688
2783
  if (voiceState === "transient-error") continue;
@@ -2816,7 +2911,7 @@ var DiscordVoiceManager$1 = class {
2816
2911
  logger.warn(`discord voice: follow reconcile skipped transient bot voice state error guild=${guildId} reason=${reason}: ${formatErrorMessage(err)}`);
2817
2912
  return "transient-error";
2818
2913
  }
2819
- logVoiceVerbose(`follow user reconcile reason=${reason}: no bot voice state guild ${guildId}: ${formatErrorMessage(err)}`);
2914
+ logFollowUserReconcileVerbose(reason, `follow user reconcile reason=${reason}: no bot voice state guild ${guildId}: ${formatErrorMessage(err)}`);
2820
2915
  });
2821
2916
  if (this.destroyed || botVoiceState === "transient-error") return;
2822
2917
  const botChannelId = botVoiceState?.channel_id?.trim();
@@ -3024,7 +3119,7 @@ var DiscordVoiceManager$1 = class {
3024
3119
  ownerAllowFrom: this.ownerAllowFrom,
3025
3120
  runtime: this.params.runtime,
3026
3121
  speakerContext: this.speakerContext,
3027
- meetingNotes: params.entry.meetingNotes,
3122
+ transcripts: params.entry.transcripts,
3028
3123
  fetchGuildName: async (guildId) => {
3029
3124
  const guild = await this.params.client.fetchGuild(guildId).catch(() => null);
3030
3125
  return guild && typeof guild.name === "string" && guild.name.trim() ? guild.name : void 0;
@@ -121,7 +121,7 @@ function applyImplicitReplyBatchGate(ctx, replyToMode, isBatched) {
121
121
  //#region extensions/discord/src/monitor/message-run-queue.ts
122
122
  let messageProcessRuntimePromise;
123
123
  async function loadMessageProcessRuntime() {
124
- messageProcessRuntimePromise ??= import("./message-handler.process-C0xo2jq3.js");
124
+ messageProcessRuntimePromise ??= import("./message-handler.process-DO17vBlT.js");
125
125
  return await messageProcessRuntimePromise;
126
126
  }
127
127
  async function processDiscordQueuedMessage(params) {
@@ -173,7 +173,7 @@ function createDiscordMessageRunQueue(params) {
173
173
  //#region extensions/discord/src/monitor/message-handler.ts
174
174
  let messagePreflightRuntimePromise;
175
175
  async function loadMessagePreflightRuntime() {
176
- messagePreflightRuntimePromise ??= import("./message-handler.preflight-DHZfoN6e.js");
176
+ messagePreflightRuntimePromise ??= import("./message-handler.preflight-Cu8cFBgW.js");
177
177
  return await messagePreflightRuntimePromise;
178
178
  }
179
179
  function isNonEmptyString(value) {
@@ -5,7 +5,7 @@ import { t as resolveDiscordConversationIdentity } from "./conversation-identity
5
5
  import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-BsOnj5NX.js";
6
6
  import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-xCfun-pQ.js";
7
7
  import "./thread-bindings-Dw4wcHWn.js";
8
- import { C as resolveDiscordDmCommandAccess, f as buildDiscordRoutePeer, g as handleDiscordDmCommandDecision, h as shouldIgnoreStaleDiscordRouteBinding, m as resolveDiscordEffectiveRoute, p as resolveDiscordConversationRoute, w as resolveDiscordTextCommandAccess } from "./provider-D-XVxGT7.js";
8
+ import { C as resolveDiscordDmCommandAccess, f as buildDiscordRoutePeer, g as handleDiscordDmCommandDecision, h as shouldIgnoreStaleDiscordRouteBinding, m as resolveDiscordEffectiveRoute, p as resolveDiscordConversationRoute, w as resolveDiscordTextCommandAccess } from "./provider-D3RwHRhI.js";
9
9
  import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-DxHZcpPf.js";
10
10
  import { n as resolveDiscordWebhookId, t as resolveDiscordSenderIdentity } from "./sender-identity-BFp5w0F8.js";
11
11
  import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -436,7 +436,7 @@ async function loadSystemEventsRuntime() {
436
436
  return await systemEventsRuntimePromise;
437
437
  }
438
438
  async function loadDiscordThreadingRuntime() {
439
- discordThreadingRuntimePromise ??= import("./provider-D-XVxGT7.js").then((n) => n.v);
439
+ discordThreadingRuntimePromise ??= import("./provider-D3RwHRhI.js").then((n) => n.v);
440
440
  return await discordThreadingRuntimePromise;
441
441
  }
442
442
  function isPreflightAborted(abortSignal) {
@@ -9,7 +9,7 @@ import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-even
9
9
  import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-C8lzfSt6.js";
10
10
  import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-CQ7PsV9J.js";
11
11
  import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-l_PsHQvX.js";
12
- import { a as resolveReplyContext, i as buildGuildLabel, n as deliverDiscordReply, r as buildDirectLabel, x as resolveDiscordThreadStarter, y as resolveDiscordAutoThreadReplyPlan } from "./provider-D-XVxGT7.js";
12
+ import { a as resolveReplyContext, i as buildGuildLabel, n as deliverDiscordReply, r as buildDirectLabel, x as resolveDiscordThreadStarter, y as resolveDiscordAutoThreadReplyPlan } from "./provider-D3RwHRhI.js";
13
13
  import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-DxHZcpPf.js";
14
14
  import { t as sendTyping } from "./typing-BhIpRSfR.js";
15
15
  import { n as buildDiscordInboundAccessContext, r as createDiscordSupplementalContextAccessChecker } from "./inbound-context-B5EsqsSr.js";