fix(discord): drain queued voice replies after stream close · openclaw/openclaw@88b64e4
vincentkoc
·
2026-06-22
·
via Recent Commits to openclaw:main
File tree
extensions/discord/src/voice
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2627,7 +2627,8 @@ describe("DiscordVoiceManager", () => {
|
2627 | 2627 | try { |
2628 | 2628 | agentCommandMock |
2629 | 2629 | .mockResolvedValueOnce({ payloads: [{ text: "first answer" }] }) |
2630 | | -.mockResolvedValueOnce({ payloads: [{ text: "second answer" }] }); |
| 2630 | +.mockResolvedValueOnce({ payloads: [{ text: "second answer" }] }) |
| 2631 | +.mockResolvedValueOnce({ payloads: [{ text: "third answer" }] }); |
2631 | 2632 | const manager = createManager({ |
2632 | 2633 | groupPolicy: "open", |
2633 | 2634 | voice: { |
@@ -2638,6 +2639,7 @@ describe("DiscordVoiceManager", () => {
|
2638 | 2639 | }); |
2639 | 2640 | |
2640 | 2641 | await manager.join({ guildId: "g1", channelId: "1001" }); |
| 2642 | +const player = getLastAudioPlayer(); |
2641 | 2643 | const entry = getSessionEntry(manager) as { |
2642 | 2644 | realtime?: { |
2643 | 2645 | beginSpeakerTurn: ( |
@@ -2679,6 +2681,19 @@ describe("DiscordVoiceManager", () => {
|
2679 | 2681 | |
2680 | 2682 | await vi.advanceTimersByTimeAsync(1_510); |
2681 | 2683 | expectUserMessageIncludes("second answer"); |
| 2684 | + |
| 2685 | +const idleHandler = player.on.mock.calls.find(([event]) => event === "idle")?.[1] as |
| 2686 | +| (() => void) |
| 2687 | +| undefined; |
| 2688 | +idleHandler?.(); |
| 2689 | +const thirdTurn = entry.realtime?.beginSpeakerTurn( |
| 2690 | +{ extraSystemPrompt: undefined, senderIsOwner: true, speakerLabel: "Owner" }, |
| 2691 | +"u-owner", |
| 2692 | +); |
| 2693 | +thirdTurn?.sendInputAudio(Buffer.alloc(8)); |
| 2694 | +bridgeParams?.onTranscript?.("user", "third question", true); |
| 2695 | +await vi.advanceTimersByTimeAsync(260); |
| 2696 | +expectUserMessageNotIncludes("third answer"); |
2682 | 2697 | } finally { |
2683 | 2698 | vi.useRealTimers(); |
2684 | 2699 | } |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -408,8 +408,11 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
|
408 | 408 | | { message: string; suppressed: number; lastLoggedAt: number } |
409 | 409 | | undefined; |
410 | 410 | private readonly playerIdleHandler = () => { |
| 411 | +const hadOutputAudio = this.isOutputAudioActive(); |
411 | 412 | this.resetOutputStream("player-idle"); |
412 | | -this.completeExactSpeechResponse("player-idle"); |
| 413 | +if (hadOutputAudio) { |
| 414 | +this.completeExactSpeechResponse("player-idle"); |
| 415 | +} |
413 | 416 | }; |
414 | 417 | |
415 | 418 | constructor( |
@@ -779,9 +782,13 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
|
779 | 782 | return; |
780 | 783 | } |
781 | 784 | this.logOutputAudioStopped(reason); |
| 785 | +this.clearOutputPlaybackWatchdog(); |
782 | 786 | this.outputStream = null; |
783 | 787 | this.resetOutputAudioStats(); |
784 | | -this.completeExactSpeechResponse(reason, { drain: false }); |
| 788 | +// The Opus resource can close without Discord emitting player idle. This |
| 789 | +// close path releases queued exact speech, so clear the old watchdog before |
| 790 | +// the next response owns exact-speech state. |
| 791 | +this.completeExactSpeechResponse(reason); |
785 | 792 | } |
786 | 793 | |
787 | 794 | private queueOutputAudio(stream: PassThrough, discordPcm: Buffer): void { |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。