@openclaw/discord 2026.7.2-beta.2 → 2026.7.2-beta.3

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 (85) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{activities-api-D0D_87n3.js → activities-api-DS1L7KRY.js} +15 -42
  3. package/dist/activities-api.js +1 -1
  4. package/dist/{allow-list-B7qOH5xu.js → allow-list-DzKrtfah.js} +18 -7
  5. package/dist/{api-B88T4rFN.js → api-C43z8vUm.js} +3 -1
  6. package/dist/api.js +25 -16
  7. package/dist/{approval-handler.runtime-B7E1-x5q.js → approval-handler.runtime-BbkfPoGc.js} +5 -6
  8. package/dist/{conversation-identity-BPcbhCAH.js → approval-message-safety-DrLlgIPz.js} +48 -2
  9. package/dist/{audit-CjpnY1yb.js → audit-BC6bgPMX.js} +3 -3
  10. package/dist/{channel-BDfKr14H.js → channel-5MfaC2IE.js} +18 -18
  11. package/dist/{channel-actions-BQct-o4T.js → channel-actions-DicWTbYH.js} +2 -2
  12. package/dist/{channel-actions.runtime-CC4QwYVO.js → channel-actions.runtime-D9Qadn-l.js} +8 -8
  13. package/dist/channel-config-api.js +1 -1
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/{channel.setup-BS4gd8sc.js → channel.setup-CJXfFclP.js} +3 -3
  16. package/dist/{chunk-CHpY_lsO.js → chunk-CWzmeWf_.js} +9 -49
  17. package/dist/{config-schema-CoDck1GW.js → config-schema-BxrgfSMD.js} +4 -0
  18. package/dist/{directory-config-G1QwBIdi.js → directory-config-CmukHSms.js} +1 -1
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-nfvekMil.js → directory-live-BYlCRO__.js} +3 -3
  21. package/dist/{doctor-Bq4pJAQk.js → doctor-Ci7USQAW.js} +1 -1
  22. package/dist/{handle-action.guild-admin-LYY75_Oa.js → handle-action.guild-admin-Bsk2YBPs.js} +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/{manager.runtime-w6VVYQMl.js → manager.runtime-Cc8vS-M7.js} +98 -145
  25. package/dist/message-handler-CWJ-mQvH.js +590 -0
  26. package/dist/{message-handler.preflight-DnAH04Ss.js → message-handler.preflight-DK46_qbS.js} +16 -15
  27. package/dist/{message-handler.process-ClgKz8I5.js → message-handler.process-BK0mjFJH.js} +37 -61
  28. package/dist/{message-utils-BU3gc01v.js → message-utils-Cfe6CZ6N.js} +1 -1
  29. package/dist/{outbound-adapter-ByoXsR-V.js → outbound-adapter-B2_oa-zV.js} +36 -5
  30. package/dist/{outbound-components-7wAolLuS.js → outbound-components-BGhk6HI3.js} +3 -3
  31. package/dist/{outbound-session-route-D4QqePZN.js → outbound-session-route-M2bEY9tI.js} +36 -6
  32. package/dist/{pluralkit-r4wiI2yy.js → pluralkit-CT0VRsZd.js} +1 -1
  33. package/dist/{probe-WKeTYyJX.js → probe-C3SCKyYU.js} +1 -1
  34. package/dist/{probe.runtime-CRY-qUke.js → probe.runtime-D55QqC9u.js} +1 -1
  35. package/dist/{provider-PRlF_lZm.js → provider-HW0G-2l6.js} +868 -794
  36. package/dist/{provider-session.runtime-BZPUZRjn.js → provider-session.runtime-cCfGMsF_.js} +3 -3
  37. package/dist/provider.runtime-BWCRsVoT.js +2 -0
  38. package/dist/{resolve-allowlist-common-QN7VMZaC.js → resolve-allowlist-common-CJlqw_nC.js} +2 -2
  39. package/dist/{resolve-channels-BO9NUPlS.js → resolve-channels-9KcG3mLg.js} +4 -4
  40. package/dist/{resolve-users-CU3oG5-m.js → resolve-users-GCGf3oV0.js} +3 -3
  41. package/dist/{runtime-CCf5nkV3.js → runtime-CD5MRZkD.js} +1 -2
  42. package/dist/{runtime-BsefAQ9q.js → runtime-Z2qkKakn.js} +9 -8
  43. package/dist/runtime-api.actions.js +2 -2
  44. package/dist/runtime-api.js +23 -22
  45. package/dist/runtime-api.lookup.js +5 -5
  46. package/dist/runtime-api.monitor-C3EDYJqk.js +6 -0
  47. package/dist/runtime-api.monitor.js +8 -8
  48. package/dist/runtime-api.send.js +6 -5
  49. package/dist/runtime-api.threads.js +3 -3
  50. package/dist/{secret-config-contract-BDNrRWvd.js → secret-config-contract-B1akrlyY.js} +1 -0
  51. package/dist/secret-contract-api.js +1 -1
  52. package/dist/security-audit-contract-api.js +1 -1
  53. package/dist/{security-audit.runtime-K0nkzjzJ.js → security-audit.runtime-DEEywczv.js} +1 -1
  54. package/dist/{send-JJzVo_sK.js → send-CSRxNNgq.js} +7 -86
  55. package/dist/{send.components-CCRW54gK.js → send.components-DLqzOEPJ.js} +5 -4
  56. package/dist/{send.outbound-Cvulb-k_.js → send.outbound-C1SfX20w.js} +4 -3
  57. package/dist/send.reactions-Cj4ugX3F.js +85 -0
  58. package/dist/send.receipt-CuwRrpFz.js +48 -0
  59. package/dist/{send.shared-XuOTO1dW.js → send.shared-Dber6X57.js} +2 -2
  60. package/dist/{sender-identity-C62lBe18.js → sender-identity-mG_zsdox.js} +1 -1
  61. package/dist/session-binding-contract-api.js +1 -1
  62. package/dist/setup-plugin-api.js +1 -1
  63. package/dist/{shared-Bngr0I-v.js → shared-BEhmAdMY.js} +5 -5
  64. package/dist/{subagent-hooks-mSxRv4AE.js → subagent-hooks-Wrk61DwY.js} +2 -2
  65. package/dist/subagent-hooks-api.js +10 -1
  66. package/dist/subagent-progress-B2fpHe3m.js +662 -0
  67. package/dist/{system-events-BANBRAe3.js → system-events-AhfcKRRb.js} +2 -2
  68. package/dist/{target-resolver-BaEmTxeK.js → target-resolver-vqLA450R.js} +2 -2
  69. package/dist/targets-BjEMIe8t.js +3 -0
  70. package/dist/{thread-bindings-4MV-u8vU.js → thread-bindings-CJQoXuWY.js} +4 -4
  71. package/dist/{thread-bindings.discord-api-DXfXsHGZ.js → thread-bindings.discord-api-mNCWlgvM.js} +4 -4
  72. package/dist/{thread-bindings.manager-DJ4cO5Eu.js → thread-bindings.manager-D1z8mK4b.js} +3 -3
  73. package/dist/{timeouts-D5lWili0.js → timeouts-B9cy6RvQ.js} +1 -6
  74. package/dist/{transcripts-source-TDZcMriu.js → transcripts-source-DjDbt14x.js} +1 -1
  75. package/dist/transcripts-source-api.js +1 -1
  76. package/dist/{typing-CKd2NMWG.js → typing-wxtm1NlF.js} +2 -2
  77. package/npm-shrinkwrap.json +3 -3
  78. package/openclaw.plugin.json +10 -0
  79. package/package.json +4 -4
  80. package/dist/approval-message-safety-C-8BL6Kv.js +0 -47
  81. package/dist/message-handler-DExaghWK.js +0 -331
  82. package/dist/provider.runtime-BX8GcCYx.js +0 -2
  83. package/dist/runtime-api.monitor-DCS_ETft.js +0 -6
  84. package/dist/targets-fjJt3PYG.js +0 -3
  85. package/dist/{security-audit-aLbWmUjl.js → security-audit-DtT0SIns.js} +1 -1
@@ -1,6 +1,6 @@
1
- import { c as readDiscordChannelMoveParams, n as isDiscordModerationAction, o as readDiscordChannelCreateParams, r as readDiscordModerationCommand, s as readDiscordChannelEditParams, t as handleDiscordAction } from "./runtime-BsefAQ9q.js";
1
+ import { c as readDiscordChannelMoveParams, n as isDiscordModerationAction, o as readDiscordChannelCreateParams, r as readDiscordModerationCommand, s as readDiscordChannelEditParams, t as handleDiscordAction } from "./runtime-Z2qkKakn.js";
2
2
  import "./action-runtime-api.js";
3
- import { n as isTrustedRequesterGuildAdminAction } from "./channel-actions-BQct-o4T.js";
3
+ import { n as isTrustedRequesterGuildAdminAction } from "./channel-actions-DicWTbYH.js";
4
4
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { readNonNegativeIntegerParam, readPositiveIntegerParam, readStringArrayParam, readStringParam } from "openclaw/plugin-sdk/agent-runtime";
6
6
  //#region extensions/discord/src/actions/handle-action.guild-admin.ts
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as registerDiscordActivities } from "./activities-api-D0D_87n3.js";
1
+ import { t as registerDiscordActivities } from "./activities-api-DS1L7KRY.js";
2
2
  import { registerDiscordSubagentHooks } from "./subagent-hooks-api.js";
3
- import { t as discordVoiceTranscriptsSourceProvider } from "./transcripts-source-TDZcMriu.js";
3
+ import { t as discordVoiceTranscriptsSourceProvider } from "./transcripts-source-DjDbt14x.js";
4
4
  import "./transcripts-source-api.js";
5
5
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
6
6
  //#region extensions/discord/index.ts
@@ -1,8 +1,8 @@
1
- import { A as isUnknownDiscordVoiceStateError, Bt as getGuildVoiceState, S as ResumedListener, d as parseDiscordTarget, p as discord_exports, w as VoiceStateUpdateListener, x as ReadyListener } from "./chunk-CHpY_lsO.js";
2
- import { a as normalizeDiscordSlug, h as resolveDiscordOwnerAccess, x as formatDiscordUserTag } from "./allow-list-B7qOH5xu.js";
1
+ import { O as isUnknownDiscordVoiceStateError, Rt as getGuildVoiceState, S as VoiceStateUpdateListener, b as ResumedListener, d as discord_exports, l as parseDiscordTarget, y as ReadyListener } from "./chunk-CWzmeWf_.js";
3
2
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
4
- import { a as formatMention } from "./send.outbound-Cvulb-k_.js";
5
- import { c as resolveDiscordVoiceAccess, h as buildDiscordGroupSystemPrompt, l as authorizeDiscordVoiceIngress, p as resolveDiscordVoiceEnabled } from "./provider-PRlF_lZm.js";
3
+ import { a as normalizeDiscordSlug, h as resolveDiscordOwnerAccess, x as formatDiscordUserTag } from "./allow-list-DzKrtfah.js";
4
+ import { a as formatMention } from "./send.outbound-C1SfX20w.js";
5
+ import { c as resolveDiscordVoiceAccess, h as buildDiscordGroupSystemPrompt, l as authorizeDiscordVoiceIngress, p as resolveDiscordVoiceEnabled } from "./provider-HW0G-2l6.js";
6
6
  import { createRequire } from "node:module";
7
7
  import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
8
8
  import { asBoolean, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -16,15 +16,14 @@ import { stripInlineDirectiveTagsForDisplay } from "openclaw/plugin-sdk/text-chu
16
16
  import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-runtime";
17
17
  import path from "node:path";
18
18
  import { resolvePreferredOpenClawTmpDir, tempWorkspace } from "openclaw/plugin-sdk/temp-path";
19
- 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";
20
- import { agentCommandFromIngress, getTtsProvider, resolveAgentDir, resolveTtsConfig, resolveTtsPrefsPath } from "openclaw/plugin-sdk/agent-runtime";
19
+ 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, createRealtimeVoiceSessionHarness, createRealtimeVoiceTurnContextTracker, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, normalizeSupportedRealtimeVoiceActivationName, parseRealtimeVoiceAgentControlToolArgs, resamplePcm, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultToolPolicy, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow, shouldAutoControlRealtimeVoiceAgentText, sortRealtimeVoiceActivationNames } from "openclaw/plugin-sdk/realtime-voice";
20
+ import { agentCommandFromIngress, resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
21
21
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
22
22
  import { spawn } from "node:child_process";
23
23
  import { PassThrough, Readable, Transform, pipeline } from "node:stream";
24
24
  import { StringDecoder } from "node:string_decoder";
25
25
  import { Application, OpusError, createDecoder, createEncoder } from "libopus-wasm";
26
26
  import { resolveRealtimeBootstrapContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
27
- import { parseTtsDirectives } from "openclaw/plugin-sdk/speech";
28
27
  //#region extensions/discord/src/voice/audio.ts
29
28
  const SAMPLE_RATE = 48e3;
30
29
  const CHANNELS = 2;
@@ -926,6 +925,7 @@ const DISCORD_REALTIME_WAKE_ACKS = [
926
925
  "Got it.",
927
926
  "One sec."
928
927
  ];
928
+ const discordRealtimeTalkPayload = () => ({});
929
929
  const REALTIME_PCM16_BYTES_PER_SAMPLE = 2;
930
930
  const DISCORD_RAW_PCM_FRAME_BYTES = 3840;
931
931
  const DISCORD_REALTIME_OUTPUT_PREROLL_FRAMES = 25;
@@ -1041,9 +1041,6 @@ function extractDiscordExactSpeechConsultText(args) {
1041
1041
  function normalizeControlSpeechText(text) {
1042
1042
  return text.toLowerCase().replace(/\s+/g, " ").trim();
1043
1043
  }
1044
- function matchesPendingAgentProxyQuestion(consultMessage, question) {
1045
- return matchRealtimeVoiceConsultQuestions(consultMessage, question);
1046
- }
1047
1044
  var DiscordRealtimeVoiceSession = class {
1048
1045
  constructor(params) {
1049
1046
  this.params = params;
@@ -1054,17 +1051,11 @@ var DiscordRealtimeVoiceSession = class {
1054
1051
  this.consultPolicy = "auto";
1055
1052
  this.wakeNamePolicy = "never";
1056
1053
  this.wakeNames = [];
1057
- this.forcedConsults = createRealtimeVoiceForcedConsultCoordinator({
1058
- limit: DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_LIMIT,
1059
- nativeDedupeMs: DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_TTL_MS,
1060
- questionsMatch: matchesPendingAgentProxyQuestion
1061
- });
1062
1054
  this.speakerTurns = createRealtimeVoiceTurnContextTracker({
1063
1055
  limit: DISCORD_REALTIME_PENDING_SPEAKER_CONTEXT_LIMIT,
1064
1056
  ignoredContextTtlMs: DISCORD_REALTIME_IGNORED_WAKE_NAME_CONTEXT_TTL_MS,
1065
1057
  deferUntilAudio: true
1066
1058
  });
1067
- this.outputActivity = createRealtimeVoiceOutputActivityTracker();
1068
1059
  this.outputPacedBuffer = Buffer.alloc(0);
1069
1060
  this.queuedExactSpeechMessages = [];
1070
1061
  this.exactSpeechResponseActive = false;
@@ -1077,21 +1068,41 @@ var DiscordRealtimeVoiceSession = class {
1077
1068
  this.resetOutputStream("player-idle");
1078
1069
  if (hadOutputAudio) this.completeExactSpeechResponse("player-idle");
1079
1070
  };
1080
- this.talkback = createRealtimeVoiceAgentTalkbackQueue({
1081
- debounceMs: this.realtimeConfig?.debounceMs ?? DISCORD_REALTIME_TALKBACK_DEBOUNCE_MS,
1082
- isStopped: () => this.stopped,
1083
- logger: logger$3,
1084
- logPrefix: "[discord] realtime agent",
1085
- responseStyle: "Brief, natural spoken answer for a Discord voice channel.",
1086
- fallbackText: DISCORD_REALTIME_FALLBACK_TEXT,
1087
- consult: async ({ question, responseStyle, metadata }) => {
1088
- const context = isDiscordRealtimeSpeakerContext(metadata) ? metadata : void 0;
1089
- return { text: await this.runAgentTurn({
1090
- context,
1091
- message: formatVoiceIngressPrompt([question, responseStyle ? `Spoken style: ${responseStyle}` : void 0].filter(Boolean).join("\n\n"), context?.speakerLabel ?? "Discord voice speaker")
1092
- }) };
1071
+ this.harness = createRealtimeVoiceSessionHarness({
1072
+ talk: {
1073
+ sessionId: `discord:${this.params.entry.voiceSessionKey}:realtime`,
1074
+ mode: "realtime",
1075
+ transport: "gateway-relay",
1076
+ brain: "agent-consult"
1077
+ },
1078
+ talkPayloads: {
1079
+ turnStarted: discordRealtimeTalkPayload,
1080
+ turnEnded: discordRealtimeTalkPayload,
1081
+ inputAudioDelta: discordRealtimeTalkPayload,
1082
+ outputAudioStarted: discordRealtimeTalkPayload,
1083
+ outputAudioDelta: discordRealtimeTalkPayload,
1084
+ outputAudioDone: discordRealtimeTalkPayload
1085
+ },
1086
+ talkback: {
1087
+ debounceMs: this.realtimeConfig?.debounceMs ?? DISCORD_REALTIME_TALKBACK_DEBOUNCE_MS,
1088
+ logger: logger$3,
1089
+ logPrefix: "[discord] realtime agent",
1090
+ responseStyle: "Brief, natural spoken answer for a Discord voice channel.",
1091
+ fallbackText: DISCORD_REALTIME_FALLBACK_TEXT,
1092
+ consult: async ({ question, responseStyle, metadata }) => {
1093
+ const context = isDiscordRealtimeSpeakerContext(metadata) ? metadata : void 0;
1094
+ return { text: await this.runAgentTurn({
1095
+ context,
1096
+ message: formatVoiceIngressPrompt([question, responseStyle ? `Spoken style: ${responseStyle}` : void 0].filter(Boolean).join("\n\n"), context?.speakerLabel ?? "Discord voice speaker")
1097
+ }) };
1098
+ },
1099
+ deliver: (text) => this.enqueueExactSpeechMessage(text)
1093
1100
  },
1094
- deliver: (text) => this.enqueueExactSpeechMessage(text)
1101
+ forcedConsults: {
1102
+ limit: DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_LIMIT,
1103
+ nativeDedupeMs: DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_TTL_MS,
1104
+ questionsMatch: matchRealtimeVoiceConsultQuestions
1105
+ }
1095
1106
  });
1096
1107
  }
1097
1108
  async connect() {
@@ -1134,7 +1145,7 @@ var DiscordRealtimeVoiceSession = class {
1134
1145
  toolPolicy,
1135
1146
  consultPolicy
1136
1147
  });
1137
- this.bridge = createRealtimeVoiceBridgeSession({
1148
+ this.bridge = this.harness.createBridge({
1138
1149
  provider: resolved.provider,
1139
1150
  cfg: this.params.cfg,
1140
1151
  providerConfig: resolved.providerConfig,
@@ -1147,7 +1158,7 @@ var DiscordRealtimeVoiceSession = class {
1147
1158
  audioSink: {
1148
1159
  isOpen: () => !this.stopped,
1149
1160
  sendAudio: (audio) => this.sendOutputAudio(audio),
1150
- clearAudio: () => this.clearOutputAudio("provider-clear-audio")
1161
+ clearAudio: () => this.harness.flushOutput(() => this.clearOutputAudio("provider-clear-audio"))
1151
1162
  },
1152
1163
  onTranscript: (role, text, isFinal) => {
1153
1164
  if (isFinal && text.trim()) logger$3.info(`discord voice: realtime ${role} transcript (${text.length} chars): ${formatVoiceLogPreview(text)}`);
@@ -1194,8 +1205,7 @@ var DiscordRealtimeVoiceSession = class {
1194
1205
  close() {
1195
1206
  this.stopped = true;
1196
1207
  this.flushSuppressedRealtimeErrors();
1197
- this.talkback.close();
1198
- this.forcedConsults.clear();
1208
+ this.harness.close();
1199
1209
  this.speakerTurns.clear();
1200
1210
  this.queuedExactSpeechMessages = [];
1201
1211
  this.exactSpeechResponseActive = false;
@@ -1278,11 +1288,11 @@ var DiscordRealtimeVoiceSession = class {
1278
1288
  return;
1279
1289
  }
1280
1290
  if (!this.hasInterruptibleOutputAudio()) {
1281
- logger$3.info(`discord voice: realtime barge-in ignored reason=${reason} outputActive=false guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} playbackChunks=${this.outputAudioChunks()}`);
1291
+ logger$3.info(`discord voice: realtime barge-in ignored reason=${reason} outputActive=false guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} playbackChunks=${this.harness.outputActivity.snapshot().chunks}`);
1282
1292
  return;
1283
1293
  }
1284
- logger$3.info(`discord voice: realtime barge-in requested reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.outputAudioMs()} outputActive=${this.isOutputAudioActive()} playbackChunks=${this.outputAudioChunks()}`);
1285
- this.bridge?.handleBargeIn({ audioPlaybackActive: true });
1294
+ logger$3.info(`discord voice: realtime barge-in requested reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.outputAudioMs()} outputActive=${this.isOutputAudioActive()} playbackChunks=${this.harness.outputActivity.snapshot().chunks}`);
1295
+ this.harness.handleBargeIn({ audioPlaybackActive: true }, () => {});
1286
1296
  }
1287
1297
  isBargeInEnabled() {
1288
1298
  if (this.isWakeNameRequired()) return false;
@@ -1293,8 +1303,9 @@ var DiscordRealtimeVoiceSession = class {
1293
1303
  });
1294
1304
  }
1295
1305
  hasInterruptibleOutputAudio() {
1296
- this.syncOutputAudioTimestamp();
1297
- return this.outputActivity.isInterruptible(this.isOutputStreamActive());
1306
+ this.bridge?.setMediaTimestamp(this.outputAudioMs());
1307
+ const streamActive = Boolean(this.outputStream && !this.outputStream.destroyed);
1308
+ return this.harness.outputActivity.isInterruptible(streamActive);
1298
1309
  }
1299
1310
  get realtimeConfig() {
1300
1311
  return this.params.discordConfig.voice?.realtime;
@@ -1308,14 +1319,14 @@ var DiscordRealtimeVoiceSession = class {
1308
1319
  sendOutputAudio(realtimePcm24kMono) {
1309
1320
  const discordPcm = convertRealtimePcm24kMonoToDiscordPcm48kStereo(realtimePcm24kMono);
1310
1321
  if (discordPcm.length === 0) return;
1311
- this.syncOutputAudioTimestamp();
1312
- if (this.outputActivity.snapshot().streamEnding) {
1322
+ this.bridge?.setMediaTimestamp(this.outputAudioMs());
1323
+ if (this.harness.outputActivity.snapshot().streamEnding) {
1313
1324
  logVoiceVerbose(`realtime output audio ignored after stream ending: guild ${this.params.entry.guildId} channel ${this.params.entry.channelId}`);
1314
1325
  return;
1315
1326
  }
1316
1327
  const stream = this.ensureOutputStream();
1317
1328
  if (this.exactSpeechResponseActive) this.exactSpeechAudioStarted = true;
1318
- this.outputActivity.markAudio({
1329
+ this.harness.outputActivity.markAudio({
1319
1330
  audioMs: pcm16MonoDurationMs(realtimePcm24kMono, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ.sampleRateHz),
1320
1331
  sourceAudioBytes: realtimePcm24kMono.length,
1321
1332
  sinkAudioBytes: discordPcm.length
@@ -1327,9 +1338,9 @@ var DiscordRealtimeVoiceSession = class {
1327
1338
  const stream = new PassThrough({ highWaterMark: DISCORD_RAW_PCM_FRAME_BYTES * 128 });
1328
1339
  this.outputStream = stream;
1329
1340
  this.outputPacedBuffer = Buffer.alloc(0);
1330
- this.outputActivity.markStreamOpened();
1341
+ this.harness.outputActivity.markStreamOpened();
1331
1342
  stream.once("close", () => {
1332
- if (this.outputActivity.snapshot().playbackStarted) return;
1343
+ if (this.harness.outputActivity.snapshot().playbackStarted) return;
1333
1344
  this.handleOutputStreamClosed(stream, "stream-close");
1334
1345
  });
1335
1346
  return stream;
@@ -1339,11 +1350,12 @@ var DiscordRealtimeVoiceSession = class {
1339
1350
  this.logOutputAudioStopped(reason);
1340
1351
  this.clearOutputPlaybackWatchdog();
1341
1352
  this.outputStream = null;
1342
- this.resetOutputAudioStats();
1353
+ this.outputPacedBuffer = Buffer.alloc(0);
1354
+ this.harness.outputActivity.reset();
1343
1355
  this.completeExactSpeechResponse(reason);
1344
1356
  }
1345
1357
  queueOutputAudio(stream, discordPcm) {
1346
- if (this.outputActivity.snapshot().playbackStarted) {
1358
+ if (this.harness.outputActivity.snapshot().playbackStarted) {
1347
1359
  stream.write(discordPcm);
1348
1360
  return;
1349
1361
  }
@@ -1351,7 +1363,7 @@ var DiscordRealtimeVoiceSession = class {
1351
1363
  if (this.outputPacedBuffer.length >= DISCORD_RAW_PCM_FRAME_BYTES * DISCORD_REALTIME_OUTPUT_PREROLL_FRAMES) this.startOutputPlayback(stream);
1352
1364
  }
1353
1365
  startOutputPlayback(stream) {
1354
- if (this.outputActivity.snapshot().playbackStarted || stream.destroyed) return;
1366
+ if (this.harness.outputActivity.snapshot().playbackStarted || stream.destroyed) return;
1355
1367
  const voiceSdk = loadDiscordVoiceSdk();
1356
1368
  const opusStream = createDiscordOpusEncodeStream();
1357
1369
  opusStream.on("error", (err) => {
@@ -1370,7 +1382,7 @@ var DiscordRealtimeVoiceSession = class {
1370
1382
  }
1371
1383
  const resource = voiceSdk.createAudioResource(opusStream, { inputType: voiceSdk.StreamType.Opus });
1372
1384
  this.params.entry.player.play(resource);
1373
- this.outputActivity.markPlaybackStarted();
1385
+ this.harness.outputActivity.markPlaybackStarted();
1374
1386
  const realtimeConfig = this.realtimeConfig;
1375
1387
  logger$3.info(`discord voice: realtime audio playback started guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} mode=${this.params.mode} model=${realtimeConfig?.model ?? "provider-default"} voice=${realtimeConfig?.voice ?? "provider-default"}`);
1376
1388
  }
@@ -1384,15 +1396,15 @@ var DiscordRealtimeVoiceSession = class {
1384
1396
  this.logOutputAudioStopped(reason);
1385
1397
  this.outputStream = null;
1386
1398
  this.outputPacedBuffer = Buffer.alloc(0);
1387
- this.resetOutputAudioStats();
1399
+ this.harness.outputActivity.reset();
1388
1400
  stream?.end();
1389
1401
  stream?.destroy();
1390
1402
  }
1391
1403
  finishOutputAudioStream(reason, { playBuffered = true } = {}) {
1392
1404
  const stream = this.outputStream;
1393
- if (!stream || stream.destroyed || this.outputActivity.snapshot().streamEnding) return;
1394
- this.outputActivity.markStreamEnding();
1395
- logger$3.info(`discord voice: realtime audio playback finishing reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} audioMs=${this.outputAudioMs()} chunks=${this.outputAudioChunks()}`);
1405
+ if (!stream || stream.destroyed || this.harness.outputActivity.snapshot().streamEnding) return;
1406
+ this.harness.outputActivity.markStreamEnding();
1407
+ logger$3.info(`discord voice: realtime audio playback finishing reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} audioMs=${this.outputAudioMs()} chunks=${this.harness.outputActivity.snapshot().chunks}`);
1396
1408
  if (playBuffered) {
1397
1409
  this.startOutputPlayback(stream);
1398
1410
  this.scheduleOutputPlaybackWatchdog(reason, stream);
@@ -1406,7 +1418,7 @@ var DiscordRealtimeVoiceSession = class {
1406
1418
  }
1407
1419
  scheduleOutputPlaybackWatchdog(reason, stream) {
1408
1420
  this.clearOutputPlaybackWatchdog();
1409
- const timeoutMs = this.outputActivity.playbackWatchdogDelayMs({ marginMs: DISCORD_REALTIME_OUTPUT_PLAYBACK_WATCHDOG_MARGIN_MS });
1421
+ const timeoutMs = this.harness.outputActivity.playbackWatchdogDelayMs({ marginMs: DISCORD_REALTIME_OUTPUT_PLAYBACK_WATCHDOG_MARGIN_MS });
1410
1422
  if (timeoutMs === void 0) return;
1411
1423
  this.outputPlaybackWatchdog = setTimeout(() => {
1412
1424
  this.outputPlaybackWatchdog = void 0;
@@ -1415,7 +1427,7 @@ var DiscordRealtimeVoiceSession = class {
1415
1427
  this.completeExactSpeechResponse("playback-watchdog");
1416
1428
  return;
1417
1429
  }
1418
- logger$3.warn(`discord voice: realtime audio playback watchdog fired reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} audioMs=${this.outputAudioMs()} elapsedMs=${this.outputActivity.elapsedPlaybackMs()}`);
1430
+ logger$3.warn(`discord voice: realtime audio playback watchdog fired reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} audioMs=${this.outputAudioMs()} elapsedMs=${this.harness.outputActivity.elapsedPlaybackMs()}`);
1419
1431
  this.clearOutputAudio("playback-watchdog");
1420
1432
  this.completeExactSpeechResponse("playback-watchdog");
1421
1433
  }, timeoutMs);
@@ -1456,11 +1468,10 @@ var DiscordRealtimeVoiceSession = class {
1456
1468
  if (this.stopped || !trimmed) return;
1457
1469
  this.queuedExactSpeechMessages = [];
1458
1470
  this.completeExactSpeechResponse("active-run-control", { drain: false });
1459
- this.bridge?.handleBargeIn?.({
1471
+ this.harness.handleBargeIn({
1460
1472
  audioPlaybackActive: true,
1461
1473
  force: true
1462
- });
1463
- this.clearOutputAudio("active-run-control");
1474
+ }, () => this.clearOutputAudio("active-run-control"));
1464
1475
  this.lastControlSpeech = {
1465
1476
  normalizedText: normalizeControlSpeechText(trimmed),
1466
1477
  sentAt: Date.now(),
@@ -1479,11 +1490,10 @@ var DiscordRealtimeVoiceSession = class {
1479
1490
  recent.assistantTranscriptCount += 1;
1480
1491
  if (recent.assistantTranscriptCount <= 1) return;
1481
1492
  logger$3.info(`discord voice: realtime duplicate active-run control speech suppressed guild=${this.params.entry.guildId} channel=${this.params.entry.channelId}`);
1482
- this.bridge?.handleBargeIn?.({
1493
+ this.harness.handleBargeIn({
1483
1494
  audioPlaybackActive: true,
1484
1495
  force: true
1485
- });
1486
- this.clearOutputAudio("duplicate-active-run-control");
1496
+ }, () => this.clearOutputAudio("duplicate-active-run-control"));
1487
1497
  }
1488
1498
  completeExactSpeechResponse(reason, options) {
1489
1499
  if (!this.exactSpeechResponseActive && this.queuedExactSpeechMessages.length === 0) return;
@@ -1500,32 +1510,19 @@ var DiscordRealtimeVoiceSession = class {
1500
1510
  this.sendExactSpeechMessage(next);
1501
1511
  }
1502
1512
  logOutputAudioStopped(reason) {
1503
- const activity = this.outputActivity.snapshot();
1513
+ const activity = this.harness.outputActivity.snapshot();
1504
1514
  const audioMs = Math.floor(activity.audioMs);
1505
1515
  const chunks = activity.chunks;
1506
1516
  const discordBytes = activity.sinkAudioBytes;
1507
1517
  const realtimeBytes = activity.sourceAudioBytes;
1508
- const elapsedMs = this.outputActivity.elapsedPlaybackMs();
1518
+ const elapsedMs = this.harness.outputActivity.elapsedPlaybackMs();
1509
1519
  if (this.outputStream || chunks > 0 || audioMs > 0) logger$3.info(`discord voice: realtime audio playback stopped reason=${reason} guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} audioMs=${audioMs} elapsedMs=${elapsedMs} chunks=${chunks} discordBytes=${discordBytes} realtimeBytes=${realtimeBytes}`);
1510
1520
  }
1511
- resetOutputAudioStats() {
1512
- this.outputPacedBuffer = Buffer.alloc(0);
1513
- this.outputActivity.reset();
1514
- }
1515
- syncOutputAudioTimestamp() {
1516
- this.bridge?.setMediaTimestamp(this.outputAudioMs());
1517
- }
1518
1521
  outputAudioMs() {
1519
- return Math.floor(this.outputActivity.snapshot().audioMs);
1520
- }
1521
- outputAudioChunks() {
1522
- return this.outputActivity.snapshot().chunks;
1523
- }
1524
- isOutputStreamActive() {
1525
- return Boolean(this.outputStream && !this.outputStream.destroyed);
1522
+ return Math.floor(this.harness.outputActivity.snapshot().audioMs);
1526
1523
  }
1527
1524
  isOutputAudioActive() {
1528
- return this.outputActivity.isActive(this.isOutputStreamActive());
1525
+ return this.harness.outputActivity.isActive(Boolean(this.outputStream && !this.outputStream.destroyed));
1529
1526
  }
1530
1527
  logSpeakerTurnClosed(turn) {
1531
1528
  if (turn.closed || !turn.hasAudio) return;
@@ -1573,8 +1570,8 @@ var DiscordRealtimeVoiceSession = class {
1573
1570
  return;
1574
1571
  }
1575
1572
  logger$3.info(`discord voice: realtime consult requested call=${callId || "unknown"} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} question=${formatVoiceLogPreview(consultMessage)}`);
1576
- const nativeConsult = this.forcedConsults.recordNativeConsult(event.args, callId);
1577
- if (nativeConsult.kind === "already_delivered" && this.forcedConsults.isCancelled(nativeConsult.handle)) {
1573
+ const nativeConsult = this.harness.forcedConsults.recordNativeConsult(event.args, callId);
1574
+ if (nativeConsult.kind === "already_delivered" && this.harness.forcedConsults.isCancelled(nativeConsult.handle)) {
1578
1575
  await this.submitTerminalRealtimeToolResult(callId, session, {
1579
1576
  status: "cancelled",
1580
1577
  message: "OpenClaw cancelled this consult before completion. Do not restart it."
@@ -1582,7 +1579,7 @@ var DiscordRealtimeVoiceSession = class {
1582
1579
  return;
1583
1580
  }
1584
1581
  const pendingConsult = nativeConsult.kind === "pending" ? nativeConsult.handle : void 0;
1585
- if (pendingConsult) this.forcedConsults.rememberQuestion(pendingConsult, consultMessage);
1582
+ if (pendingConsult) this.harness.forcedConsults.rememberQuestion(pendingConsult, consultMessage);
1586
1583
  let context = pendingConsult?.context?.speaker;
1587
1584
  let recent = pendingConsult;
1588
1585
  if (!context) {
@@ -1690,7 +1687,7 @@ var DiscordRealtimeVoiceSession = class {
1690
1687
  logger$3.warn(`discord voice: realtime active-run control failed; falling back to normal transcript handling: ${formatErrorMessage(error)}`);
1691
1688
  });
1692
1689
  if (control?.handled) {
1693
- if (pendingForcedConsult) this.forcedConsults.remove(pendingForcedConsult);
1690
+ if (pendingForcedConsult) this.harness.forcedConsults.remove(pendingForcedConsult);
1694
1691
  this.logAgentControlResult(control.result);
1695
1692
  if (control.speakText) this.speakControlResult(control.speakText);
1696
1693
  return;
@@ -1700,7 +1697,7 @@ var DiscordRealtimeVoiceSession = class {
1700
1697
  if (pendingForcedConsult) this.schedulePreparedForcedAgentProxyConsult(pendingForcedConsult);
1701
1698
  return;
1702
1699
  }
1703
- this.talkback.enqueue(acceptedText, forcedSpeakerContext ?? this.consumePendingSpeakerContext());
1700
+ this.harness.talkback?.enqueue(acceptedText, forcedSpeakerContext ?? this.consumePendingSpeakerContext());
1704
1701
  }
1705
1702
  handlePartialUserTranscript(text) {
1706
1703
  if (!this.isWakeNameRequired() || this.wakeNameAckedForTurn) return;
@@ -1787,28 +1784,28 @@ var DiscordRealtimeVoiceSession = class {
1787
1784
  logger$3.warn("discord voice: realtime forced agent consult has no speaker context");
1788
1785
  return;
1789
1786
  }
1790
- return this.forcedConsults.prepare(question, { context: { speaker: context } });
1787
+ return this.harness.forcedConsults.prepare(question, { context: { speaker: context } });
1791
1788
  }
1792
1789
  schedulePreparedForcedAgentProxyConsult(pending) {
1793
- this.forcedConsults.schedule(pending, DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS, (handle) => void this.runForcedAgentProxyConsult(handle));
1790
+ this.harness.forcedConsults.schedule(pending, DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS, (handle) => void this.runForcedAgentProxyConsult(handle));
1794
1791
  }
1795
1792
  async runForcedAgentProxyConsult(pending) {
1796
- this.forcedConsults.markStarted(pending);
1793
+ this.harness.forcedConsults.markStarted(pending);
1797
1794
  const state = pending.context;
1798
1795
  if (!state) {
1799
- this.forcedConsults.markCancelled(pending);
1796
+ this.harness.forcedConsults.markCancelled(pending);
1800
1797
  return;
1801
1798
  }
1802
1799
  const context = state.speaker;
1803
1800
  const { question } = pending;
1804
1801
  if (this.stopped) {
1805
- this.forcedConsults.markCancelled(pending);
1802
+ this.harness.forcedConsults.markCancelled(pending);
1806
1803
  return;
1807
1804
  }
1808
1805
  const startedAt = Date.now();
1809
1806
  logger$3.info(`discord voice: realtime forced agent consult starting chars=${question.length} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel} owner=${context.senderIsOwner}`);
1810
1807
  logger$3.debug(`discord voice: realtime forced agent consult reason=${DISCORD_REALTIME_FORCED_CONSULT_REASON} consultPolicy=${this.consultPolicy} wakeNamePolicy=${this.wakeNamePolicy} requireWakeName=${this.isWakeNameRequired()} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel}`);
1811
- if (this.hasInterruptibleOutputAudio()) logger$3.info(`discord voice: realtime forced agent consult preserving active playback guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.outputAudioMs()} outputActive=${this.isOutputAudioActive()} playbackChunks=${this.outputAudioChunks()}`);
1808
+ if (this.hasInterruptibleOutputAudio()) logger$3.info(`discord voice: realtime forced agent consult preserving active playback guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.outputAudioMs()} outputActive=${this.isOutputAudioActive()} playbackChunks=${this.harness.outputActivity.snapshot().chunks}`);
1812
1809
  state.handledByForcedPlayback = true;
1813
1810
  try {
1814
1811
  const promise = this.runAgentTurn({
@@ -1872,35 +1869,35 @@ var DiscordRealtimeVoiceSession = class {
1872
1869
  return this.speakerTurns.hasAudioContext();
1873
1870
  }
1874
1871
  rememberRecentAgentProxyConsultContext(question, context, options = {}) {
1875
- const handle = this.forcedConsults.prepare(question, {
1872
+ const handle = this.harness.forcedConsults.prepare(question, {
1876
1873
  context: { speaker: context },
1877
1874
  ...options.id ? { id: options.id } : {}
1878
1875
  });
1879
1876
  if (!handle) throw new Error("Discord realtime consult context requires a non-empty question");
1880
- if (options.started) this.forcedConsults.markStarted(handle);
1877
+ if (options.started) this.harness.forcedConsults.markStarted(handle);
1881
1878
  return handle;
1882
1879
  }
1883
1880
  setRecentAgentProxyConsultPromise(recent, promise) {
1884
1881
  const state = recent.context;
1885
1882
  if (!state) return;
1886
- this.forcedConsults.markStarted(recent);
1883
+ this.harness.forcedConsults.markStarted(recent);
1887
1884
  state.promise = promise;
1888
1885
  promise.then((text) => {
1889
1886
  state.result = {
1890
1887
  status: "fulfilled",
1891
1888
  text
1892
1889
  };
1893
- this.forcedConsults.markDelivered(recent);
1890
+ this.harness.forcedConsults.markDelivered(recent);
1894
1891
  }).catch((error) => {
1895
1892
  state.result = {
1896
1893
  status: "rejected",
1897
1894
  error: formatErrorMessage(error)
1898
1895
  };
1899
- this.forcedConsults.markDelivered(recent);
1896
+ this.harness.forcedConsults.markDelivered(recent);
1900
1897
  });
1901
1898
  }
1902
1899
  findRecentAgentProxyConsultContext(consultMessage) {
1903
- return this.forcedConsults.findRecent(consultMessage);
1900
+ return this.harness.forcedConsults.findRecent(consultMessage);
1904
1901
  }
1905
1902
  async submitTerminalRealtimeToolResult(callId, session, result) {
1906
1903
  if (session.bridge.supportsToolResultSuppression === false) {
@@ -2118,47 +2115,6 @@ function sanitizeVoiceReplyTextForSpeech(text, speakerLabel) {
2118
2115
  }
2119
2116
  //#endregion
2120
2117
  //#region extensions/discord/src/voice/tts.ts
2121
- function mergeTtsConfig(base, override) {
2122
- if (!override) return base;
2123
- const baseProviders = base.providers ?? {};
2124
- const overrideProviders = override.providers ?? {};
2125
- const mergedProviders = Object.fromEntries(uniqueStrings([...Object.keys(baseProviders), ...Object.keys(overrideProviders)]).map((providerId) => {
2126
- const baseProvider = baseProviders[providerId] ?? {};
2127
- const overrideProvider = overrideProviders[providerId] ?? {};
2128
- return [providerId, {
2129
- ...baseProvider,
2130
- ...overrideProvider
2131
- }];
2132
- }));
2133
- return {
2134
- ...base,
2135
- ...override,
2136
- modelOverrides: {
2137
- ...base.modelOverrides,
2138
- ...override.modelOverrides
2139
- },
2140
- ...Object.keys(mergedProviders).length === 0 ? {} : { providers: mergedProviders }
2141
- };
2142
- }
2143
- function resolveVoiceTtsConfig(params) {
2144
- if (!params.override) return {
2145
- cfg: params.cfg,
2146
- resolved: resolveTtsConfig(params.cfg)
2147
- };
2148
- const merged = mergeTtsConfig(params.cfg.messages?.tts ?? {}, params.override);
2149
- const messages = params.cfg.messages ?? {};
2150
- const cfg = {
2151
- ...params.cfg,
2152
- messages: {
2153
- ...messages,
2154
- tts: merged
2155
- }
2156
- };
2157
- return {
2158
- cfg,
2159
- resolved: resolveTtsConfig(cfg)
2160
- };
2161
- }
2162
2118
  async function transcribeVoiceAudio(params) {
2163
2119
  return normalizeOptionalString((await getDiscordRuntime().mediaUnderstanding.transcribeAudioFile({
2164
2120
  filePath: params.filePath,
@@ -2168,21 +2124,18 @@ async function transcribeVoiceAudio(params) {
2168
2124
  })).text);
2169
2125
  }
2170
2126
  async function synthesizeVoiceReplyAudio(params) {
2171
- const { cfg: ttsCfg, resolved: ttsConfig } = resolveVoiceTtsConfig({
2127
+ const runtime = getDiscordRuntime();
2128
+ const prepared = await runtime.tts.prepareTtsRequest({
2172
2129
  cfg: params.cfg,
2173
- override: params.override
2174
- });
2175
- const directive = parseTtsDirectives(params.replyText, ttsConfig.modelOverrides, {
2176
- cfg: ttsCfg,
2177
- providerConfigs: ttsConfig.providerConfigs,
2178
- preferredProviderId: getTtsProvider(ttsConfig, resolveTtsPrefsPath(ttsConfig))
2130
+ override: params.override,
2131
+ text: params.replyText
2179
2132
  });
2133
+ const directive = prepared.directives;
2180
2134
  const speakText = sanitizeVoiceReplyTextForSpeech(directive.overrides.ttsText ?? directive.cleanedText.trim(), params.speakerLabel);
2181
2135
  if (!speakText) return { status: "empty" };
2182
- const runtime = getDiscordRuntime();
2183
2136
  const streamResult = await runtime.tts.textToSpeechStream?.({
2184
2137
  text: speakText,
2185
- cfg: ttsCfg,
2138
+ cfg: prepared.cfg,
2186
2139
  channel: "discord",
2187
2140
  overrides: directive.overrides,
2188
2141
  disableFallback: true
@@ -2196,7 +2149,7 @@ async function synthesizeVoiceReplyAudio(params) {
2196
2149
  };
2197
2150
  const result = await runtime.tts.textToSpeech({
2198
2151
  text: speakText,
2199
- cfg: ttsCfg,
2152
+ cfg: prepared.cfg,
2200
2153
  channel: "discord",
2201
2154
  overrides: directive.overrides
2202
2155
  });