diff --git a/extensions/discord/src/voice/manager.e2e.test-support.ts b/extensions/discord/src/voice/manager.e2e.test-support.ts index de0c39d76515..c0f5f1f406b1 100644 --- a/extensions/discord/src/voice/manager.e2e.test-support.ts +++ b/extensions/discord/src/voice/manager.e2e.test-support.ts @@ -44,7 +44,7 @@ export type TestRealtimeBridgeParams = { cfg?: unknown; instructions?: string; interruptResponseOnInputAudio?: boolean; - onEvent?: (event: { detail?: string; direction: "server"; type: string }) => void; + onEvent?: (event: { detail?: string; direction: "client" | "server"; type: string }) => void; onToolCall?: ( event: { args: unknown; callId: string; itemId: string; name: string }, session: unknown, diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 204b91a2c9ea..60d7e99bd0d5 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -3359,6 +3359,29 @@ describe("DiscordVoiceManager", () => { expectUserMessageIncludes("wake answer"); }); + it("does not carry partial wake-name state across provider continuity resets", async () => { + const { entry, bridgeParams } = await createWakeNameFixture(); + const wakeAckCount = () => + sentUserMessages().filter((message) => message.includes('Answer: "Yeah."')).length; + + beginSpeakerTurn(entry); + bridgeParams?.onEvent?.({ direction: "server", type: "input_audio_buffer.speech_started" }); + bridgeParams?.onTranscript?.("user", "Hey, Mol", false); + bridgeParams?.onEvent?.({ direction: "client", type: "session.continuity.reset" }); + bridgeParams?.onEvent?.({ direction: "client", type: "session.continuity.reset" }); + bridgeParams?.onTranscript?.("user", "ty", false); + + expect(wakeAckCount()).toBe(0); + + bridgeParams?.onEvent?.({ direction: "client", type: "session.continuity.reset" }); + bridgeParams?.onTranscript?.("user", "Hey, Molty", false); + expect(wakeAckCount()).toBe(1); + + bridgeParams?.onEvent?.({ direction: "client", type: "session.continuity.reset" }); + bridgeParams?.onTranscript?.("user", "Hey, Molty", false); + expect(wakeAckCount()).toBe(2); + }); + it("treats a bare wake name as an activation for the next realtime transcript", async () => { agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "follow-up answer" }] }); const onUtterance = vi.fn(); diff --git a/extensions/discord/src/voice/realtime.ts b/extensions/discord/src/voice/realtime.ts index ade1b709766c..299bdab35389 100644 --- a/extensions/discord/src/voice/realtime.ts +++ b/extensions/discord/src/voice/realtime.ts @@ -517,6 +517,9 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession { onToolCall: (event, session) => this.handleToolCall(event, session), onEvent: (event) => { const detail = event.detail ? ` ${event.detail}` : ""; + if (event.direction === "client" && event.type === "session.continuity.reset") { + this.resetPartialWakeNameTracking(); + } if (event.direction === "server" && event.type === "input_audio_buffer.speech_started") { this.resetPartialWakeNameTracking(); }