@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.
Files changed (93) hide show
  1. package/dist/account-inspect-BY-ve3yH.js +45 -0
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CfQ-vSyf.js → accounts-CwJQoLjM.js} +89 -45
  4. package/dist/action-runtime-api.js +1 -1
  5. package/dist/{activities-api-q-9PaaHH.js → activities-api-E-segZn6.js} +30 -17
  6. package/dist/activities-api.js +1 -1
  7. package/dist/{allow-list-BDMimASD.js → allow-list-C2JuH-R0.js} +10 -6
  8. package/dist/{api-DNVbJyEy.js → api-BfmEP6wp.js} +6 -5
  9. package/dist/api.js +18 -18
  10. package/dist/{approval-handler.runtime-B7qhK3fR.js → approval-handler.runtime-CW5k3mSa.js} +19 -12
  11. package/dist/{audit-CBEv_ERu.js → audit-B9rydv5a.js} +8 -7
  12. package/dist/{channel-Dx1tMTfx.js → channel-D93LnbTE.js} +29 -23
  13. package/dist/{channel-actions.runtime-CLWWu9nC.js → channel-actions.runtime-DMFv4doE.js} +16 -13
  14. package/dist/channel-config-api.js +1 -1
  15. package/dist/channel-plugin-api.js +1 -1
  16. package/dist/{channel.setup-DOzXS2Pk.js → channel.setup-DTL7kPOM.js} +18 -18
  17. package/dist/{components-B9V5o361.js → components-DmXYIQ26.js} +111 -149
  18. package/dist/{config-schema-BR13IAx6.js → config-schema-B797-Spt.js} +19 -12
  19. package/dist/{conversation-binding-route-DJYrY2TQ.js → conversation-binding-route-24afmvEC.js} +1 -1
  20. package/dist/{directory-config-BK6eqekJ.js → directory-config-BD4YL7wT.js} +2 -2
  21. package/dist/directory-contract-api.js +1 -1
  22. package/dist/{directory-live-DSAJSrJk.js → directory-live-BrFVc1Ds.js} +4 -4
  23. package/dist/{doctor-Cm7Y1pJ2.js → doctor-BUB41_lr.js} +5 -5
  24. package/dist/{doctor-contract-DsWvKIXF.js → doctor-contract-Co5vRCZ6.js} +9 -6
  25. package/dist/doctor-contract-api.js +3 -2
  26. package/dist/{handle-action.guild-admin-BdwCRHmm.js → handle-action.guild-admin-B0egstYq.js} +2 -2
  27. package/dist/index.js +1 -1
  28. package/dist/ingress-identity-BCzfPPYM.js +60 -0
  29. package/dist/{message-handler-BXpWZyni.js → message-handler-Qdw9c0j4.js} +14 -10
  30. package/dist/{message-handler.preflight-Ba5LAY_X.js → message-handler.preflight-Dxi2yBfR.js} +41 -101
  31. package/dist/{message-handler.process-3HEzmemS.js → message-handler.process-BYDjtxZJ.js} +83 -109
  32. package/dist/{message-text-aiwO5R2F.js → message-text-BRVqYm-t.js} +11 -6
  33. package/dist/{outbound-adapter-BE4jSq2d.js → outbound-adapter-DEhthWTk.js} +52 -67
  34. package/dist/{outbound-components-oLpg8Qzb.js → outbound-components-DehyxvA4.js} +5 -4
  35. package/dist/{outbound-session-route-Cg15ZFJd.js → outbound-session-route-BFGRF9fE.js} +55 -12
  36. package/dist/{owner-access-UlYi9t0M.js → owner-access-yc1sX7Gx.js} +7 -6
  37. package/dist/{pluralkit-Tiyl70dx.js → pluralkit-XmmAHoV6.js} +2 -2
  38. package/dist/{probe-n6ZXxK8V.js → probe-3ovFAp3b.js} +3 -3
  39. package/dist/{probe.runtime-CN4bdPfZ.js → probe.runtime-BckHn3cJ.js} +1 -1
  40. package/dist/{provider-B11xwilq.js → provider-DuDtKbF8.js} +294 -131
  41. package/dist/{provider-session.runtime-B9xe-Qaf.js → provider-session.runtime-BA2BPyZ4.js} +3 -3
  42. package/dist/provider.runtime-Db8YwZBv.js +2 -0
  43. package/dist/{realtime-session.runtime-CU8IQUS-.js → realtime-session.runtime-DgSbX6ay.js} +103 -163
  44. package/dist/{resolve-allowlist-common-BLoYaxEG.js → resolve-allowlist-common-BJ5UZNNT.js} +3 -3
  45. package/dist/{resolve-channels-C2sT_n67.js → resolve-channels-CYseD5Uw.js} +4 -4
  46. package/dist/{resolve-users-CzpjLwjy.js → resolve-users-DrmhDrqm.js} +5 -4
  47. package/dist/{retry-Cv4jCU5Z.js → retry-BNCxiyyR.js} +120 -32
  48. package/dist/{retry-after-DdLPmPoa.js → retry-after-BrLjLUJi.js} +2 -1
  49. package/dist/{runtime-BHrTlFkf.js → runtime-BbWNtfyq.js} +127 -93
  50. package/dist/{runtime-CY4PRMF-.js → runtime-DB7Fb6AU.js} +2 -2
  51. package/dist/runtime-api.js +19 -19
  52. package/dist/{secret-config-contract-B3i6WxV5.js → secret-config-contract-DFu9hfU8.js} +14 -6
  53. package/dist/secret-contract-api.js +1 -1
  54. package/dist/{security-audit-BfJhAXZp.js → security-audit-DKfK-iPY.js} +10 -11
  55. package/dist/security-audit-contract-api.js +1 -1
  56. package/dist/{security-audit.runtime-BVKF9VtY.js → security-audit.runtime-sKZyKgpx.js} +1 -1
  57. package/dist/{send-C49MFF9j.js → send-BnTEIeLE.js} +132 -100
  58. package/dist/{send.components-DKKAw5VX.js → send.components-BWVEcjRP.js} +21 -15
  59. package/dist/{send.outbound-zam0TrZx.js → send.outbound-b5xwHTlL.js} +35 -22
  60. package/dist/{send.shared-JjljcV4O.js → send.shared-5vxBTagO.js} +25 -18
  61. package/dist/{sender-identity-BJtfyise.js → sender-identity-Dt0imZyY.js} +1 -1
  62. package/dist/setup-plugin-api.js +1 -1
  63. package/dist/{shared-_5EcP4jW.js → shared-BSJWHEMA.js} +20 -14
  64. package/dist/{subagent-hooks-DQzvOptn.js → subagent-hooks-IzQKX4RQ.js} +2 -2
  65. package/dist/subagent-hooks-api.js +1 -1
  66. package/dist/{system-events-C7yxaHWP.js → system-events-BOiDS8b3.js} +2 -2
  67. package/dist/{target-resolver-vxIh6O3-.js → target-resolver-BVI8xSSC.js} +6 -5
  68. package/dist/targets-DbxVbq8k.js +3 -0
  69. package/dist/{thread-bindings-DW5F9vat.js → thread-bindings-D7mcaXQC.js} +6 -5
  70. package/dist/{thread-bindings.manager-ByOtwjo_.js → thread-bindings.manager-B1oySiVc.js} +37 -28
  71. package/dist/{thread-bindings.session-updates-CJb-BpPL.js → thread-bindings.session-updates-CC7HoQvt.js} +2 -1
  72. package/dist/{threading-agH1T_6s.js → threading-OQnL-0Zx.js} +8 -6
  73. package/dist/{timeouts-B9cy6RvQ.js → timeouts-D86uWLt_.js} +1 -1
  74. package/dist/{transcripts-source-BR11iHA4.js → transcripts-source-D8lH7AkN.js} +9 -4
  75. package/dist/transcripts-source-api.js +1 -1
  76. package/dist/{typing-Dc_YMY2e.js → typing-C9q0AQMb.js} +2 -2
  77. package/dist/{voice-runtime-DIeaa8fJ.js → voice-runtime-Dfhjh1tV.js} +433 -93
  78. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/README.md +1 -1
  79. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/conforms.d.ts +2 -0
  80. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/package.json +3 -3
  81. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubArray.d.ts +2 -0
  82. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubObject.d.ts +2 -0
  83. package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubString.d.ts +2 -0
  84. package/node_modules/typebox/build/format/email.mjs +1 -1
  85. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  86. package/node_modules/typebox/package.json +1 -1
  87. package/openclaw.plugin.json +14 -6
  88. package/package.json +6 -5
  89. package/skills/discord/SKILL.md +1 -1
  90. package/dist/account-inspect-BDfKyuvL.js +0 -89
  91. package/dist/provider.runtime-DiYaHvOz.js +0 -2
  92. package/dist/sdk-runtime-4geQHtXc.js +0 -338
  93. package/dist/targets-CtTKvntJ.js +0 -3
@@ -1,6 +1,6 @@
1
- import { n as createThreadBindingManager, t as createNoopThreadBindingManager } from "./thread-bindings.manager-ByOtwjo_.js";
2
- import { a as reconcileAcpThreadBindingsOnStartup } from "./thread-bindings-DW5F9vat.js";
3
- import { t as createDiscordMessageHandler } from "./message-handler-BXpWZyni.js";
1
+ import { n as createThreadBindingManager, t as createNoopThreadBindingManager } from "./thread-bindings.manager-B1oySiVc.js";
2
+ import { a as reconcileAcpThreadBindingsOnStartup } from "./thread-bindings-D7mcaXQC.js";
3
+ import { t as createDiscordMessageHandler } from "./message-handler-Qdw9c0j4.js";
4
4
  import { resolveThreadBindingIdleTimeoutMs, resolveThreadBindingMaxAgeMs, resolveThreadBindingsEnabled } from "openclaw/plugin-sdk/conversation-runtime";
5
5
  import { getAcpSessionManager, isAcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
6
6
  export { createDiscordMessageHandler, createNoopThreadBindingManager, createThreadBindingManager, getAcpSessionManager, isAcpRuntimeError, reconcileAcpThreadBindingsOnStartup, resolveThreadBindingIdleTimeoutMs, resolveThreadBindingMaxAgeMs, resolveThreadBindingsEnabled };
@@ -0,0 +1,2 @@
1
+ import { t as monitorDiscordProvider } from "./provider-DuDtKbF8.js";
2
+ export { monitorDiscordProvider };
@@ -1,17 +1,19 @@
1
- import { n as discordRealtimeVoiceSecretOwnerId } from "./secret-config-contract-B3i6WxV5.js";
2
- import { b as maybeControlDiscordVoiceAgentRun, f as convertDiscordPcm48kStereoToRealtimePcm24kMono, l as logVoiceVerbose, m as createDiscordOpusEncodeStream, p as convertRealtimePcm24kMonoToDiscordPcm48kStereo, t as loadDiscordVoiceSdk, x as formatVoiceLogPreview, y as formatVoiceIngressPrompt } from "./sdk-runtime-4geQHtXc.js";
1
+ import { n as discordRealtimeVoiceSecretOwnerId } from "./secret-config-contract-DFu9hfU8.js";
2
+ import { a as convertRealtimePcm24kMonoToDiscordPcm48kStereo, c as maybeControlDiscordVoiceAgentRun, i as convertDiscordPcm48kStereoToRealtimePcm24kMono, l as formatVoiceLogPreview, n as loadDiscordVoiceSdk, o as createDiscordOpusEncodeStream, r as logVoiceVerbose, s as formatVoiceIngressPrompt } from "./voice-runtime-Dfhjh1tV.js";
3
3
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
5
5
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
6
6
  import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
7
+ import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
7
8
  import { assertSecretOwnerAvailable, isSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
8
- import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONTROL_TOOL, REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceSessionInstructions, buildRealtimeVoiceSpeakExactMessage, canonicalizeRealtimeVoiceProviderId, classifyRealtimeVoiceConsultToolCall, classifySkippableRealtimeVoiceConsultTranscript, controlRealtimeVoiceAgentRun, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceSessionHarness, createRealtimeVoiceTurnContextTracker, isRealtimeVoiceWakeNameRequired, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, parseRealtimeVoiceAgentControlToolArgs, realtimeVoiceAudioDurationMs, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceBargeIn, resolveRealtimeVoiceInterruptResponseOnInputAudio, resolveRealtimeVoiceMinBargeInAudioEndMs, resolveRealtimeVoiceSessionPolicy } from "openclaw/plugin-sdk/realtime-voice";
9
+ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONTROL_TOOL, REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentErrorProviderResult, buildRealtimeVoiceSessionInstructions, buildRealtimeVoiceSpeakExactMessage, canonicalizeRealtimeVoiceProviderId, classifyRealtimeVoiceConsultToolCall, classifySkippableRealtimeVoiceConsultTranscript, controlRealtimeVoiceAgentRun, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceSessionHarness, createRealtimeVoiceTurnContextTracker, isRealtimeVoiceWakeNameRequired, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, parseRealtimeVoiceAgentControlToolArgs, realtimeVoiceAudioDurationMs, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceBargeIn, resolveRealtimeVoiceInterruptResponseOnInputAudio, resolveRealtimeVoiceMinBargeInAudioEndMs, resolveRealtimeVoiceSessionPolicy } from "openclaw/plugin-sdk/realtime-voice";
9
10
  import { PassThrough, pipeline } from "node:stream";
10
11
  //#region extensions/discord/src/voice/realtime-transcript.ts
11
12
  function mergeRealtimePartialTranscript(previous, next) {
12
13
  const trimmed = next.trim();
13
14
  if (!trimmed) return previous;
14
- return sliceUtf16Safe(trimmed.startsWith(previous) ? trimmed : `${previous}${next}`, -240);
15
+ const merged = trimmed.startsWith(previous) ? trimmed : `${previous}${next}`;
16
+ return sliceUtf16Safe(merged, -240);
15
17
  }
16
18
  //#endregion
17
19
  //#region extensions/discord/src/voice/realtime-turns.ts
@@ -39,6 +41,7 @@ var DiscordRealtimeTurns = class {
39
41
  ...context,
40
42
  userId
41
43
  }, {
44
+ transcripts: this.params.entry.transcripts,
42
45
  inputDiscordBytes: 0,
43
46
  inputRealtimeBytes: 0,
44
47
  inputChunks: 0,
@@ -185,12 +188,13 @@ var DiscordRealtimeTurns = class {
185
188
  transcriptAttributionFromTurn(turn) {
186
189
  return turn ? {
187
190
  context: turn.context,
188
- startedAt: turn.startedAt
191
+ startedAt: turn.startedAt,
192
+ transcripts: turn.transcripts
189
193
  } : void 0;
190
194
  }
191
195
  recordTranscriptUtterance(text, attribution, providerEpoch) {
192
- const transcripts = this.params.entry.transcripts;
193
- if (!transcripts || !attribution) return;
196
+ const transcripts = attribution?.transcripts;
197
+ if (!transcripts || !attribution || this.params.entry.transcripts !== transcripts) return;
194
198
  const context = attribution.context;
195
199
  const utterance = {
196
200
  sessionId: transcripts.sessionId,
@@ -209,7 +213,7 @@ var DiscordRealtimeTurns = class {
209
213
  }
210
214
  };
211
215
  Promise.resolve().then(() => {
212
- if (providerEpoch !== this.params.providerEpoch()) return;
216
+ if (providerEpoch !== this.params.providerEpoch() || this.params.entry.transcripts !== transcripts) return;
213
217
  return transcripts.onUtterance(utterance);
214
218
  }).catch((error) => {
215
219
  logger$3.warn(`discord voice: realtime transcripts utterance failed: ${formatErrorMessage(error)}`);
@@ -226,6 +230,7 @@ var DiscordRealtimeTurns = class {
226
230
  if (expiresAt === void 0) return;
227
231
  this.pendingWakeNameFollowup = {
228
232
  context,
233
+ transcripts: turn?.transcripts,
229
234
  startedAt: turn?.startedAt ?? Date.now(),
230
235
  expiresAt
231
236
  };
@@ -242,7 +247,8 @@ var DiscordRealtimeTurns = class {
242
247
  if (currentTurn) this.consumePendingSpeakerContext();
243
248
  return {
244
249
  context: pending.context,
245
- startedAt: pending.startedAt
250
+ startedAt: pending.startedAt,
251
+ transcripts: pending.transcripts
246
252
  };
247
253
  }
248
254
  rememberIgnoredWakeNameSpeakerContext(context) {
@@ -259,9 +265,14 @@ const DISCORD_REALTIME_TALKBACK_DEBOUNCE_MS = 350;
259
265
  const DISCORD_REALTIME_FALLBACK_TEXT = "I hit an error while checking that. Please try again.";
260
266
  const DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS = 200;
261
267
  const DISCORD_REALTIME_FORCED_CONSULT_REASON = "provider_final_transcript_without_openclaw_agent_consult";
268
+ const CANCELLED_CONSULT_RESULT = {
269
+ status: "cancelled",
270
+ message: "OpenClaw cancelled this consult before completion. Do not restart it."
271
+ };
262
272
  var DiscordRealtimeConsults = class {
263
273
  constructor(params) {
264
274
  this.params = params;
275
+ this.providerDeliveries = /* @__PURE__ */ new Map();
265
276
  this.talkback = this.createTalkbackQueue();
266
277
  }
267
278
  close() {
@@ -298,16 +309,12 @@ var DiscordRealtimeConsults = class {
298
309
  logger$2.warn(`discord voice: realtime consult rejected malformed args call=${callId || "unknown"}: ${outcome.error}`);
299
310
  await session.submitToolResult(callId, { error: outcome.error });
300
311
  return;
301
- case "consult": break;
302
312
  }
303
313
  const consultMessage = outcome.message;
304
314
  logger$2.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)}`);
305
315
  const nativeConsult = this.params.harness.forcedConsults.recordNativeConsult(event.args, callId);
306
316
  if (nativeConsult.kind === "already_delivered" && this.params.harness.forcedConsults.isCancelled(nativeConsult.handle)) {
307
- await this.submitTerminalRealtimeToolResult(callId, session, {
308
- status: "cancelled",
309
- message: "OpenClaw cancelled this consult before completion. Do not restart it."
310
- });
317
+ await this.submitTerminalRealtimeToolResult(callId, session, CANCELLED_CONSULT_RESULT);
311
318
  return;
312
319
  }
313
320
  const pendingConsult = nativeConsult.kind === "pending" ? nativeConsult.handle : void 0;
@@ -338,24 +345,14 @@ var DiscordRealtimeConsults = class {
338
345
  await session.submitToolResult(callId, { error: "No Discord speaker context available" });
339
346
  return;
340
347
  }
341
- const promise = this.runAgentTurn({
348
+ const result = await this.trackAgentProxyConsult(recent, this.runAgentTurn({
342
349
  context,
343
350
  message: consultMessage
344
- });
345
- if (recent) this.setRecentAgentProxyConsultPromise(recent, promise);
346
- let text;
347
- try {
348
- text = await promise;
349
- } catch (error) {
350
- if (providerEpoch !== this.params.providerEpoch()) return;
351
- const message = formatErrorMessage(error);
352
- logger$2.warn(`discord voice: realtime consult failed call=${callId || "unknown"}: ${message}`);
353
- await session.submitToolResult(callId, { error: message });
354
- return;
355
- }
351
+ }));
356
352
  if (providerEpoch !== this.params.providerEpoch()) return;
357
- logger$2.info(`discord voice: realtime consult answer (${text.length} chars) voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel} owner=${context.senderIsOwner}: ${formatVoiceLogPreview(text)}`);
358
- await session.submitToolResult(callId, { text });
353
+ if ("text" in result) logger$2.info(`discord voice: realtime consult answer (${result.text.length} chars) voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel} owner=${context.senderIsOwner}: ${formatVoiceLogPreview(result.text)}`);
354
+ else if ("error" in result) logger$2.warn(`discord voice: realtime consult failed call=${callId}: ${result.error}`);
355
+ await this.submitAgentProxyConsultResult(callId, session, result);
359
356
  }
360
357
  async handleAcceptedTranscript(acceptedText, forcedSpeakerContext, providerEpoch) {
361
358
  const usesRealtimeAgentHandoff = this.params.usesRealtimeAgentHandoff();
@@ -483,23 +480,17 @@ var DiscordRealtimeConsults = class {
483
480
  logger$2.debug(`discord voice: realtime forced agent consult reason=${DISCORD_REALTIME_FORCED_CONSULT_REASON} consultPolicy=${this.params.consultPolicy()} wakeNamePolicy=${this.params.wakeNamePolicy()} requireWakeName=${this.params.isWakeNameRequired()} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel}`);
484
481
  if (this.params.playback.hasInterruptibleOutputAudio()) logger$2.info(`discord voice: realtime forced agent consult preserving active playback guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.params.playback.outputAudioMs()} outputActive=${this.params.playback.isOutputAudioActive()} playbackChunks=${this.params.harness.outputActivity.snapshot().chunks}`);
485
482
  state.handledByForcedPlayback = true;
486
- try {
487
- const promise = this.runAgentTurn({
488
- context,
489
- message: question
490
- });
491
- this.setRecentAgentProxyConsultPromise(pending, promise);
492
- const text = await promise;
493
- await state.providerDelivery;
494
- if (state.providerEpoch !== this.params.providerEpoch()) return;
495
- logger$2.info(`discord voice: realtime forced agent consult answer (${text.length} chars) elapsedMs=${Date.now() - startedAt} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId}: ${formatVoiceLogPreview(text)}`);
496
- if (text.trim() && state.handledByForcedPlayback) this.params.playback.enqueueExactSpeechMessage(text);
497
- } catch (error) {
498
- await state.providerDelivery;
499
- if (state.providerEpoch !== this.params.providerEpoch()) return;
500
- logger$2.warn(`discord voice: realtime forced agent consult failed elapsedMs=${Date.now() - startedAt}: ${formatErrorMessage(error)}`);
501
- if (state.handledByForcedPlayback) this.params.playback.enqueueExactSpeechMessage(DISCORD_REALTIME_FALLBACK_TEXT);
502
- }
483
+ const result = await this.trackAgentProxyConsult(pending, this.runAgentTurn({
484
+ context,
485
+ message: question
486
+ }));
487
+ const deliveries = this.providerDeliveries.get(state);
488
+ await Promise.all(Array.from(deliveries ?? [], (delivery) => delivery.promise));
489
+ if (state.providerEpoch !== this.params.providerEpoch() || "status" in result) return;
490
+ if ("text" in result) logger$2.info(`discord voice: realtime forced agent consult answer (${result.text.length} chars) elapsedMs=${Date.now() - startedAt} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId}: ${formatVoiceLogPreview(result.text)}`);
491
+ else logger$2.warn(`discord voice: realtime forced agent consult failed elapsedMs=${Date.now() - startedAt}: ${result.error}`);
492
+ const text = "text" in result ? result.text : DISCORD_REALTIME_FALLBACK_TEXT;
493
+ if (text.trim() && state.handledByForcedPlayback) this.params.playback.enqueueExactSpeechMessage(text);
503
494
  }
504
495
  rememberRecentAgentProxyConsultContext(question, context, options = {}) {
505
496
  const handle = this.params.harness.forcedConsults.prepare(question, {
@@ -513,26 +504,19 @@ var DiscordRealtimeConsults = class {
513
504
  if (options.started) this.params.harness.forcedConsults.markStarted(handle);
514
505
  return handle;
515
506
  }
516
- setRecentAgentProxyConsultPromise(recent, promise) {
517
- const state = recent.context;
518
- if (!state) return;
519
- this.params.harness.forcedConsults.markStarted(recent);
520
- state.promise = promise;
521
- promise.then((text) => {
522
- if (state.providerEpoch !== this.params.providerEpoch()) return;
523
- state.result = {
524
- status: "fulfilled",
525
- text
526
- };
527
- this.params.harness.forcedConsults.markDelivered(recent);
528
- }).catch((error) => {
529
- if (state.providerEpoch !== this.params.providerEpoch()) return;
530
- state.result = {
531
- status: "rejected",
532
- error: formatErrorMessage(error)
533
- };
534
- this.params.harness.forcedConsults.markDelivered(recent);
507
+ trackAgentProxyConsult(recent, promise) {
508
+ const state = recent?.context;
509
+ if (recent) this.params.harness.forcedConsults.markStarted(recent);
510
+ const tracked = promise.then((text) => ({ text }), buildRealtimeVoiceAgentErrorProviderResult).then((result) => {
511
+ if (recent && state && state.providerEpoch === this.params.providerEpoch()) {
512
+ state.result = result;
513
+ if ("status" in result) this.params.harness.forcedConsults.markCancelled(recent);
514
+ else this.params.harness.forcedConsults.markDelivered(recent);
515
+ }
516
+ return result;
535
517
  });
518
+ if (state) state.promise = tracked;
519
+ return tracked;
536
520
  }
537
521
  findRecentAgentProxyConsultContext(consultMessage) {
538
522
  return this.params.harness.forcedConsults.findRecent(consultMessage);
@@ -548,80 +532,44 @@ var DiscordRealtimeConsults = class {
548
532
  const state = recent.context;
549
533
  if (!state) return false;
550
534
  if (state.providerEpoch !== this.params.providerEpoch()) return true;
551
- const providerOwnsDelivery = Boolean(state.handledByForcedPlayback && state.promise && !state.result && session.bridge.supportsToolResultSuppression === false);
552
- let resolveProviderDelivery;
553
- if (providerOwnsDelivery) state.providerDelivery = new Promise((resolve) => {
554
- resolveProviderDelivery = resolve;
555
- state.settleProviderDelivery = resolve;
556
- });
557
- const submitAlreadyDelivered = async () => {
558
- if (state.providerEpoch !== this.params.providerEpoch()) return;
559
- await this.submitTerminalRealtimeToolResult(callId, session, {
560
- status: "already_delivered",
561
- message: "OpenClaw already delivered this answer to Discord voice. Do not repeat it."
562
- });
563
- };
564
- const submitResult = async (result) => {
565
- if (state.providerEpoch !== this.params.providerEpoch()) return;
566
- if (state.handledByForcedPlayback && !providerOwnsDelivery) {
567
- await submitAlreadyDelivered();
568
- return;
569
- }
570
- if (result.status === "fulfilled") {
571
- await session.submitToolResult(callId, { text: result.text });
572
- return;
573
- }
574
- await session.submitToolResult(callId, { error: result.error });
575
- };
576
- if (state.result) {
577
- logger$2.info(`discord voice: realtime consult reused recent agent result call=${callId || "unknown"} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
578
- await submitResult(state.result);
579
- return true;
535
+ const pendingResult = state.result ?? state.promise;
536
+ if (!pendingResult) return false;
537
+ const providerDelivery = state.handledByForcedPlayback && !state.result && session.bridge.supportsToolResultSuppression === false ? createDeferred() : void 0;
538
+ if (providerDelivery) {
539
+ const deliveries = this.providerDeliveries.get(state) ?? /* @__PURE__ */ new Set();
540
+ deliveries.add(providerDelivery);
541
+ this.providerDeliveries.set(state, deliveries);
580
542
  }
581
- if (!state.promise) return false;
582
- logger$2.info(`discord voice: realtime consult joined in-flight agent result call=${callId || "unknown"} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
583
- if (state.handledByForcedPlayback && !providerOwnsDelivery) {
584
- await state.promise.catch(() => void 0);
585
- if (state.providerEpoch !== this.params.providerEpoch()) return true;
586
- await submitAlreadyDelivered();
587
- return true;
588
- }
589
- let result;
590
- try {
591
- result = {
592
- status: "fulfilled",
593
- text: await state.promise
594
- };
595
- } catch (error) {
596
- result = {
597
- status: "rejected",
598
- error: formatErrorMessage(error)
599
- };
600
- }
601
- if (state.providerEpoch !== this.params.providerEpoch()) return true;
543
+ logger$2.info(`discord voice: realtime consult ${state.result ? "reused recent" : "joined in-flight"} agent result call=${callId} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
602
544
  try {
603
- await submitResult(result);
604
- if (providerOwnsDelivery) {
605
- state.handledByForcedPlayback = false;
606
- state.settleProviderDelivery = void 0;
607
- resolveProviderDelivery?.(true);
545
+ const result = await pendingResult;
546
+ if (state.providerEpoch !== this.params.providerEpoch()) return true;
547
+ await this.submitAgentProxyConsultResult(callId, session, result, Boolean(state.handledByForcedPlayback && !providerDelivery));
548
+ if (providerDelivery && state.providerEpoch === this.params.providerEpoch()) state.handledByForcedPlayback = false;
549
+ } finally {
550
+ if (providerDelivery) {
551
+ providerDelivery.resolve();
552
+ const deliveries = this.providerDeliveries.get(state);
553
+ deliveries?.delete(providerDelivery);
554
+ if (deliveries?.size === 0) this.providerDeliveries.delete(state);
608
555
  }
609
- } catch (error) {
610
- state.settleProviderDelivery = void 0;
611
- resolveProviderDelivery?.(false);
612
- throw error;
613
556
  }
614
557
  return true;
615
558
  }
559
+ async submitAgentProxyConsultResult(callId, session, result, alreadyDelivered = false) {
560
+ if ("status" in result) await this.submitTerminalRealtimeToolResult(callId, session, CANCELLED_CONSULT_RESULT);
561
+ else if (alreadyDelivered) await this.submitTerminalRealtimeToolResult(callId, session, {
562
+ status: "already_delivered",
563
+ message: "OpenClaw already delivered this answer to Discord voice. Do not repeat it."
564
+ });
565
+ else await session.submitToolResult(callId, result);
566
+ }
616
567
  clearProviderConsultState() {
617
- for (const handle of this.params.harness.forcedConsults.handles()) {
618
- const state = handle.context;
619
- if (!state) continue;
568
+ for (const [state, deliveries] of this.providerDeliveries) {
620
569
  state.handledByForcedPlayback = false;
621
- state.settleProviderDelivery?.(false);
622
- state.settleProviderDelivery = void 0;
623
- state.providerDelivery = void 0;
570
+ for (const delivery of deliveries) delivery.resolve();
624
571
  }
572
+ this.providerDeliveries.clear();
625
573
  this.params.harness.forcedConsults.clear();
626
574
  }
627
575
  };
@@ -631,7 +579,7 @@ const logger$1 = createSubsystemLogger("discord/voice");
631
579
  const DISCORD_REALTIME_CONTROL_SPEECH_DEDUPE_MS = 5e3;
632
580
  const DISCORD_REALTIME_OUTPUT_PLAYBACK_WATCHDOG_MARGIN_MS = 3e3;
633
581
  const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_MESSAGES = 32;
634
- const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_BYTES = 32 * 1024;
582
+ const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_BYTES = 32768;
635
583
  const DISCORD_REALTIME_WAKE_ACKS = [
636
584
  "Yeah.",
637
585
  "Mm-hmm.",
@@ -829,7 +777,7 @@ var DiscordRealtimePlayback = class {
829
777
  return this.outputStream;
830
778
  }
831
779
  stopAfterOverflow(reason, error) {
832
- this.params.stopTerminally(reason);
780
+ this.params.stopTerminally();
833
781
  this.queuedExactSpeechMessages = [];
834
782
  this.exactSpeechState = { status: "idle" };
835
783
  this.clearOutputAudio(reason);
@@ -1018,7 +966,6 @@ var DiscordRealtimeVoiceSession = class {
1018
966
  status: "inactive",
1019
967
  generation: 0
1020
968
  };
1021
- this.nextLifecycleGeneration = 0;
1022
969
  this.consultToolPolicy = "safe-read-only";
1023
970
  this.consultPolicy = "auto";
1024
971
  this.wakeNamePolicy = "never";
@@ -1060,8 +1007,8 @@ var DiscordRealtimeVoiceSession = class {
1060
1007
  onTerminalError: this.params.onTerminalError,
1061
1008
  providerId: () => this.realtimeProviderId,
1062
1009
  realtimeConfig: () => this.realtimeConfig,
1063
- stopTerminally: (reason) => {
1064
- this.stopLifecycle(reason);
1010
+ stopTerminally: () => {
1011
+ this.lifecycle.status = "stopped";
1065
1012
  this.consults.close();
1066
1013
  },
1067
1014
  stopped: () => this.isStopped(),
@@ -1100,11 +1047,10 @@ var DiscordRealtimeVoiceSession = class {
1100
1047
  });
1101
1048
  }
1102
1049
  async connect() {
1103
- const lifecycleGeneration = ++this.nextLifecycleGeneration;
1050
+ const lifecycleGeneration = this.lifecycle.generation + 1;
1104
1051
  this.lifecycle = {
1105
1052
  status: "starting",
1106
- generation: lifecycleGeneration,
1107
- instance: this
1053
+ generation: lifecycleGeneration
1108
1054
  };
1109
1055
  const configuredProviderId = this.realtimeConfig?.provider?.trim();
1110
1056
  if (configuredProviderId) {
@@ -1157,6 +1103,10 @@ var DiscordRealtimeVoiceSession = class {
1157
1103
  toolPolicy,
1158
1104
  consultPolicy
1159
1105
  });
1106
+ const onReady = () => {
1107
+ this.markProviderGenerationObserved();
1108
+ if (this.markLifecycleReady(lifecycleGeneration)) this.playback.drainQueuedExactSpeechMessages("provider-ready");
1109
+ };
1160
1110
  this.bridge = this.harness.createBridge({
1161
1111
  provider: resolved.provider,
1162
1112
  cfg: this.params.cfg,
@@ -1191,11 +1141,11 @@ var DiscordRealtimeVoiceSession = class {
1191
1141
  this.markProviderGenerationObserved();
1192
1142
  return this.consults.handleToolCall(event, session);
1193
1143
  },
1194
- onReady: () => {
1195
- this.markProviderGenerationObserved();
1196
- if (this.markLifecycleReady(lifecycleGeneration)) this.playback.drainQueuedExactSpeechMessages("provider-ready");
1144
+ onReady,
1145
+ onEvent: (event) => {
1146
+ this.handleBridgeEvent(event);
1147
+ if (event.direction === "client" && event.type === "session.reconnect.ready") onReady();
1197
1148
  },
1198
- onEvent: (event) => this.handleBridgeEvent(event),
1199
1149
  onResponseDone: (outcome) => {
1200
1150
  this.markProviderGenerationObserved();
1201
1151
  this.playback.handleResponseDone(outcome);
@@ -1204,10 +1154,16 @@ var DiscordRealtimeVoiceSession = class {
1204
1154
  },
1205
1155
  onError: (error) => this.logRealtimeError(formatErrorMessage(error)),
1206
1156
  onClose: (reason) => {
1207
- this.flushSuppressedRealtimeErrors();
1208
- logVoiceVerbose(`realtime closed: ${reason}`);
1157
+ if (!this.isStopped()) {
1158
+ this.lifecycle.status = "stopped";
1159
+ this.params.onTerminalError(/* @__PURE__ */ new Error(`Realtime provider closed unexpectedly: ${reason}`));
1160
+ }
1209
1161
  }
1210
1162
  });
1163
+ if (this.isStopped()) {
1164
+ this.close();
1165
+ return;
1166
+ }
1211
1167
  const resolvedModel = readProviderConfigString(resolved.providerConfig, "model") ?? resolved.provider.defaultModel;
1212
1168
  const resolvedVoice = readProviderConfigString(resolved.providerConfig, "voice");
1213
1169
  const humanParticipantCount = this.humanParticipantCount();
@@ -1223,7 +1179,7 @@ var DiscordRealtimeVoiceSession = class {
1223
1179
  logger.info(`discord voice: realtime bridge ready mode=${this.params.mode} provider=${resolved.provider.id} model=${resolvedModel ?? "default"} voice=${resolvedVoice ?? "default"}`);
1224
1180
  }
1225
1181
  close() {
1226
- this.stopLifecycle("session close");
1182
+ this.lifecycle.status = "stopped";
1227
1183
  this.providerContinuityEpoch += 1;
1228
1184
  this.flushSuppressedRealtimeErrors();
1229
1185
  this.consults.close();
@@ -1255,21 +1211,9 @@ var DiscordRealtimeVoiceSession = class {
1255
1211
  }
1256
1212
  markLifecycleReady(generation) {
1257
1213
  if (this.lifecycle.status !== "starting" && this.lifecycle.status !== "active" || this.lifecycle.generation !== generation) return false;
1258
- this.lifecycle = {
1259
- status: "active",
1260
- generation,
1261
- instance: this
1262
- };
1214
+ this.lifecycle.status = "active";
1263
1215
  return true;
1264
1216
  }
1265
- stopLifecycle(reason) {
1266
- const generation = this.lifecycle.generation;
1267
- this.lifecycle = {
1268
- status: "stopped",
1269
- generation,
1270
- reason
1271
- };
1272
- }
1273
1217
  humanParticipantCount() {
1274
1218
  return this.params.getHumanParticipantCount?.() ?? 0;
1275
1219
  }
@@ -1293,11 +1237,7 @@ var DiscordRealtimeVoiceSession = class {
1293
1237
  resetProviderContinuity(reason) {
1294
1238
  if (!this.providerGenerationObserved) return;
1295
1239
  this.providerGenerationObserved = false;
1296
- if (this.lifecycle.status === "active") this.lifecycle = {
1297
- status: "starting",
1298
- generation: this.lifecycle.generation,
1299
- instance: this
1300
- };
1240
+ if (this.lifecycle.status === "active") this.lifecycle.status = "starting";
1301
1241
  this.providerContinuityEpoch += 1;
1302
1242
  this.consults.resetProviderContinuity();
1303
1243
  this.turns.resetProviderContinuity();
@@ -1,6 +1,6 @@
1
- import { m as normalizeDiscordToken } from "./accounts-CfQ-vSyf.js";
2
- import { r as fetchDiscord } from "./api-DNVbJyEy.js";
3
- import { a as normalizeDiscordSlug } from "./allow-list-BDMimASD.js";
1
+ import { m as normalizeDiscordToken } from "./accounts-CwJQoLjM.js";
2
+ import { r as fetchDiscord } from "./api-BfmEP6wp.js";
3
+ import { a as normalizeDiscordSlug } from "./allow-list-C2JuH-R0.js";
4
4
  //#region extensions/discord/src/guilds.ts
5
5
  async function listGuilds(token, fetcher, options) {
6
6
  return (await fetchDiscord("/users/@me/guilds", token, fetcher, options)).filter((guild) => typeof guild.id === "string" && typeof guild.name === "string").map((guild) => ({
@@ -1,8 +1,8 @@
1
- import { on as __exportAll } from "./retry-Cv4jCU5Z.js";
2
- import { n as DiscordApiError, r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-DNVbJyEy.js";
3
- import { a as normalizeDiscordSlug } from "./allow-list-BDMimASD.js";
1
+ import { sn as __exportAll } from "./retry-BNCxiyyR.js";
2
+ import { n as DiscordApiError, r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-BfmEP6wp.js";
3
+ import { a as normalizeDiscordSlug } from "./allow-list-C2JuH-R0.js";
4
4
  import { t as isDiscordThreadChannelType } from "./channel-type-CVLZyGYS.js";
5
- import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BLoYaxEG.js";
5
+ import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BJ5UZNNT.js";
6
6
  //#region extensions/discord/src/resolve-channels.ts
7
7
  var resolve_channels_exports = /* @__PURE__ */ __exportAll({ resolveDiscordChannelAllowlist: () => resolveDiscordChannelAllowlist });
8
8
  function parseDiscordChannelInput(raw) {
@@ -1,6 +1,6 @@
1
- import { on as __exportAll } from "./retry-Cv4jCU5Z.js";
2
- import { r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-DNVbJyEy.js";
3
- import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BLoYaxEG.js";
1
+ import { sn as __exportAll } from "./retry-BNCxiyyR.js";
2
+ import { r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-BfmEP6wp.js";
3
+ import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BJ5UZNNT.js";
4
4
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  //#region extensions/discord/src/resolve-users.ts
6
6
  var resolve_users_exports = /* @__PURE__ */ __exportAll({ resolveDiscordUserAllowlist: () => resolveDiscordUserAllowlist });
@@ -74,7 +74,8 @@ async function resolveDiscordUserAllowlist(params) {
74
74
  });
75
75
  continue;
76
76
  }
77
- const guildList = filterDiscordGuilds(await getGuilds(), {
77
+ const allGuilds = await getGuilds();
78
+ const guildList = filterDiscordGuilds(allGuilds, {
78
79
  guildId: parsed.guildId,
79
80
  guildName: parsed.guildName?.trim()
80
81
  });