{"version":3,"sources":["../../src/voice/audio_recognition_turn_detection.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Integration tests for `AudioRecognition` audio turn-detection wiring.\n *\n * Recognition owns all streaming turn-detection policy: it holds the in-flight\n * inference request's future (`turnDetectorPredictionFut`), starts requests on\n * VAD events only, awaits the future with the model `predictionTimeout` in the\n * eou bounce, and flushes the stream on turn commits. Covered here:\n *\n * 1. Resumed speech during the endpointing window: a `START_OF_SPEECH`\n *    mid-bounce cancels the in-flight eou task so the prior turn doesn't ship.\n * 2. `onEotPrediction` dedup across the vad-EOS and stt-final triggers that\n *    share one resolved prediction future.\n * 3. The prediction-future lifecycle against VAD events: requests start\n *    exclusively on the silence tick, resumed speech rearms the next pause, SOS\n *    teardown, the flushed-turn short-circuit for late stt finals, and the\n *    predict-timeout fallback signal.\n * 4. The `minSilenceDuration` validation guarding an audio-EOT + VAD pairing.\n *\n * The stream-side request lifecycle lives in `inference/eot/base.test.ts`.\n */\nimport { ParticipantKind } from '@livekit/rtc-node';\nimport { describe, expect, it, vi } from 'vitest';\nimport {\n  BaseStreamingTurnDetector,\n  BaseStreamingTurnDetectorStream,\n  MIN_SILENCE_DURATION_MS,\n  type TurnDetectionEvent,\n} from '../inference/eot/base.js';\nimport { type LanguageCode, asLanguageCode } from '../language.js';\nimport { ChatContext } from '../llm/chat_context.js';\nimport { initializeLogger } from '../log.js';\nimport { Future } from '../utils.js';\nimport { type VAD, type VADEvent, VADEventType } from '../vad.js';\nimport {\n  AudioRecognition,\n  type AudioRecognitionOptions,\n  type RecognitionHooks,\n  type _TurnDetector,\n} from './audio_recognition.js';\n\ninitializeLogger({ pretty: false, level: 'silent' });\n\n/** White-box view of the `AudioRecognition` internals these tests drive. */\ninterface RecognitionInternals {\n  speaking: boolean;\n  isAgentSpeaking: boolean;\n  vad?: VAD;\n  turnDetector?: _TurnDetector | BaseStreamingTurnDetector;\n  turnDetectorStream?: BaseStreamingTurnDetectorStream;\n  turnDetectorPredictionFut?: Future<TurnDetectionEvent>;\n  turnDetectorFlushed: boolean;\n  turnDetectorLatePredictionWarned: boolean;\n  lastEmittedEotPrediction?: TurnDetectionEvent;\n  lastSpeakingTime?: number;\n  lastLanguage?: LanguageCode;\n  audioTranscript: string;\n  audioInterimTranscript: string;\n  audioPreflightTranscript: string;\n  sttRequestIds: string[];\n  bounceEOUTask?: {\n    result: Promise<void>;\n    cancel: () => void;\n    cancelAndWait: () => Promise<void>;\n    done: boolean;\n  };\n  runEOUDetection: (chatCtx: ChatContext, trigger?: 'vad' | 'stt' | 'manual') => void;\n  createVadTask: (vad: VAD | undefined, signal: AbortSignal) => Promise<void>;\n  checkVadSilenceRequirement: (detector?: _TurnDetector | BaseStreamingTurnDetector) => void;\n  updateLastLanguage: (language: LanguageCode, transcript: string) => void;\n  updateTurnDetector: (detector: _TurnDetector | BaseStreamingTurnDetector | undefined) => void;\n  clearUserTurn: () => void;\n}\n\nfunction makeHooks(): RecognitionHooks {\n  return {\n    onInterruption: vi.fn(),\n    onBackchannelConfirmed: vi.fn(),\n    onStartOfSpeech: vi.fn(),\n    onVADInferenceDone: vi.fn(),\n    onEndOfSpeech: vi.fn(),\n    onInterimTranscript: vi.fn(),\n    onFinalTranscript: vi.fn(),\n    onEotPrediction: vi.fn(),\n    onAgentBackchannelOpportunity: vi.fn(),\n    onPreemptiveGeneration: vi.fn(),\n    onUserTurnExceeded: vi.fn(),\n    retrieveChatCtx: () => ChatContext.empty(),\n    onEndOfTurn: vi.fn(async () => false), // don't commit by default\n  };\n}\n\nfunction makeRecognition(opts: Partial<AudioRecognitionOptions> = {}): {\n  recognition: AudioRecognition;\n  internals: RecognitionInternals;\n  hooks: RecognitionHooks;\n} {\n  const hooks = makeHooks();\n  const full: AudioRecognitionOptions = {\n    recognitionHooks: hooks,\n    stt: undefined,\n    vad: undefined,\n    interruptionDetection: undefined,\n    turnDetectionMode: 'vad',\n    minEndpointingDelay: 10,\n    maxEndpointingDelay: 500,\n    getLinkedParticipant: () => ({ sid: 'p1', identity: 'bob', kind: ParticipantKind.AGENT }),\n    ...opts,\n  };\n  const recognition = new AudioRecognition(full);\n  return { recognition, internals: recognition as unknown as RecognitionInternals, hooks };\n}\n\n/**\n * A fake audio-EOT detector stream that passes `instanceof BaseStreamingTurnDetectorStream`\n * (so `runEOUDetection` selects the audio path). `predict` hands out a fresh\n * pending future each call, mirroring the real stream; tests install\n * resolved/pending futures directly on `internals.turnDetectorPredictionFut` to\n * model cached/awaiting predictions.\n */\nfunction makeAudioStream(): BaseStreamingTurnDetectorStream {\n  const stream = Object.create(BaseStreamingTurnDetectorStream.prototype);\n  stream.supportsLanguage = vi.fn(async () => true);\n  stream.unlikelyThreshold = vi.fn(async () => 0.5);\n  // backchannel disabled by default (server sent no thresholds); the\n  // backchannel-emit tests override this with a positive threshold.\n  stream.backchannelThreshold = vi.fn(async () => undefined);\n  Object.defineProperty(stream, 'predictionTimeout', { value: 10 });\n  stream.predict = vi.fn(() => new Future<TurnDetectionEvent>());\n  stream.cancelInference = vi.fn();\n  stream.flush = vi.fn();\n  return stream as BaseStreamingTurnDetectorStream;\n}\n\nfunction makeAudioDetector(stream: BaseStreamingTurnDetectorStream): BaseStreamingTurnDetector {\n  const detector = Object.create(BaseStreamingTurnDetector.prototype);\n  detector.stream = vi.fn(() => stream);\n  return detector as BaseStreamingTurnDetector;\n}\n\n/** A resolved prediction future, as if the transport already answered. */\nfunction resolvedPrediction(\n  probability: number,\n  opts: {\n    inferenceDuration?: number;\n    detectionDelay?: number;\n    backchannelProbability?: number;\n  } = {},\n): { fut: Future<TurnDetectionEvent>; event: TurnDetectionEvent } {\n  const event: TurnDetectionEvent = {\n    type: 'eot_prediction',\n    endOfTurnProbability: probability,\n    lastSpeakingTimeMs: 0,\n    inferenceDuration: opts.inferenceDuration,\n    detectionDelay: opts.detectionDelay,\n    backchannelProbability: opts.backchannelProbability,\n  };\n  const fut = new Future<TurnDetectionEvent>();\n  fut.resolve(event);\n  return { fut, event };\n}\n\nfunction predictMock(stream: BaseStreamingTurnDetectorStream): ReturnType<typeof vi.fn> {\n  return stream.predict as unknown as ReturnType<typeof vi.fn>;\n}\n\nfunction cancelInferenceMock(stream: BaseStreamingTurnDetectorStream): ReturnType<typeof vi.fn> {\n  return stream.cancelInference as unknown as ReturnType<typeof vi.fn>;\n}\n\nfunction inferenceDone(rawAccumulatedSpeech: number, rawAccumulatedSilence = 0): VADEvent {\n  return {\n    type: VADEventType.INFERENCE_DONE,\n    samplesIndex: 0,\n    timestamp: 0,\n    speechDuration: 0,\n    silenceDuration: 0,\n    frames: [],\n    probability: 0,\n    inferenceDuration: 0,\n    speaking: false,\n    rawAccumulatedSilence,\n    rawAccumulatedSpeech,\n  };\n}\n\nfunction startOfSpeech(): VADEvent {\n  return {\n    type: VADEventType.START_OF_SPEECH,\n    samplesIndex: 0,\n    timestamp: 0,\n    speechDuration: 500,\n    silenceDuration: 0,\n    frames: [],\n    probability: 0,\n    inferenceDuration: 0,\n    speaking: true,\n    rawAccumulatedSilence: 0,\n    rawAccumulatedSpeech: 500,\n  };\n}\n\nfunction endOfSpeech(): VADEvent {\n  return {\n    type: VADEventType.END_OF_SPEECH,\n    samplesIndex: 0,\n    timestamp: 0,\n    speechDuration: 0,\n    silenceDuration: 300,\n    frames: [],\n    probability: 0,\n    inferenceDuration: 0,\n    speaking: false,\n    rawAccumulatedSilence: 300,\n    rawAccumulatedSpeech: 0,\n  };\n}\n\n/** Let queued microtasks + the VAD loop body run to completion. */\nfunction flush(): Promise<void> {\n  return new Promise((resolve) => setImmediate(resolve));\n}\n\ndescribe('TestLanguageTracking', () => {\n  it('does not replace a concrete language with a non-specific language', () => {\n    const { internals } = makeRecognition();\n    internals.lastLanguage = asLanguageCode('en');\n\n    internals.updateLastLanguage(asLanguageCode('multi'), 'ambiguous phrase');\n\n    expect(internals.lastLanguage).toBe(asLanguageCode('en'));\n  });\n\n  it('does not initialize language with a non-specific language', () => {\n    const { internals } = makeRecognition();\n    internals.lastLanguage = undefined;\n\n    internals.updateLastLanguage(asLanguageCode('multi'), 'we');\n\n    expect(internals.lastLanguage).toBeUndefined();\n  });\n\n  it('still updates concrete language after a long transcript', () => {\n    const { internals } = makeRecognition();\n    internals.lastLanguage = asLanguageCode('en');\n\n    internals.updateLastLanguage(asLanguageCode('fr'), 'bonjour');\n\n    expect(internals.lastLanguage).toBe(asLanguageCode('fr'));\n  });\n});\n\n/**\n * Drive `createVadTask` against a scripted VAD stream so VAD events flow\n * through the real handler. `feed()` resolves once the event has been processed\n * and the loop has parked awaiting the next one.\n */\nfunction runScriptedVad(internals: RecognitionInternals): {\n  feed: (ev: VADEvent) => Promise<void>;\n  stop: () => Promise<void>;\n} {\n  let resolveNext: ((r: IteratorResult<VADEvent>) => void) | null = null;\n  const buffered: VADEvent[] = [];\n  let closed = false;\n\n  const stream = {\n    updateInputStream(_s: unknown) {},\n    detachInputStream() {},\n    close() {\n      closed = true;\n      if (resolveNext) {\n        resolveNext({ done: true, value: undefined as never });\n        resolveNext = null;\n      }\n    },\n    [Symbol.asyncIterator](): AsyncIterator<VADEvent> {\n      return {\n        next(): Promise<IteratorResult<VADEvent>> {\n          if (buffered.length > 0) {\n            return Promise.resolve({ done: false, value: buffered.shift()! });\n          }\n          if (closed) {\n            return Promise.resolve({ done: true, value: undefined as never });\n          }\n          return new Promise((res) => {\n            resolveNext = res;\n          });\n        },\n      };\n    },\n  };\n\n  const vad = { stream: () => stream } as unknown as VAD;\n  const controller = new AbortController();\n  const task = internals.createVadTask(vad, controller.signal);\n\n  return {\n    async feed(ev: VADEvent) {\n      if (resolveNext) {\n        const res = resolveNext;\n        resolveNext = null;\n        res({ done: false, value: ev });\n      } else {\n        buffered.push(ev);\n      }\n      await flush();\n      await flush();\n    },\n    async stop() {\n      controller.abort();\n      await task.catch(() => {});\n    },\n  };\n}\n\ndescribe('VAD task lifecycle', () => {\n  it('ends speech when the VAD task closes mid-speech', async () => {\n    const { internals, hooks } = makeRecognition();\n    const vad = runScriptedVad(internals);\n\n    await vad.feed(startOfSpeech());\n    expect(internals.speaking).toBe(true);\n\n    await vad.stop();\n\n    expect(hooks.onEndOfSpeech).toHaveBeenCalledWith(undefined);\n    expect(internals.speaking).toBe(false);\n  });\n});\n\ndescribe('TestResumedSpeechAbortsCommit', () => {\n  it('cancels the in-flight bounce when VAD start-of-speech arrives during endpointing', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // sub-threshold prediction (0.2 < 0.5) extends endpointing to maxDelay\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.2).fut;\n\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    const task = internals.bounceEOUTask;\n    expect(task).toBeDefined();\n\n    // The bounce is parked in the ~500ms endpointing delay. Resumed speech\n    // well inside that window tears the bounce down via the VAD SOS handler.\n    await new Promise((r) => setTimeout(r, 50));\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(startOfSpeech());\n    } finally {\n      await vad.stop();\n    }\n\n    await task!.result.catch(() => {});\n\n    expect(hooks.onEndOfTurn).not.toHaveBeenCalled();\n  });\n});\n\ndescribe('TestEotPredictionDedup', () => {\n  it('emits onEotPrediction once across vad then stt triggers', async () => {\n    const { internals, hooks } = makeRecognition();\n    // One prediction per inference request — both triggers read this event by\n    // reference from the held future.\n    const { fut, event } = resolvedPrediction(0.2, { inferenceDuration: 50, detectionDelay: 100 });\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.turnDetectorPredictionFut = fut;\n\n    // vad trigger: bounce emits, then parks in the endpointing sleep.\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    await flush();\n    await flush();\n    expect(hooks.onEotPrediction).toHaveBeenCalledTimes(1);\n\n    // stt trigger: cancels the parked vad bounce and runs a fresh one that\n    // reads the same resolved future. Dedup must suppress a second emit.\n    internals.runEOUDetection(ChatContext.empty(), 'stt');\n    await flush();\n    await flush();\n\n    expect(hooks.onEotPrediction).toHaveBeenCalledTimes(1);\n    expect(internals.lastEmittedEotPrediction).toBe(event);\n\n    await internals.bounceEOUTask?.cancelAndWait().catch(() => {});\n  });\n\n  it('emits on every bounce for a text-based detector', async () => {\n    const { internals, hooks } = makeRecognition();\n    // A text detector is not a BaseStreamingTurnDetector → no streaming window,\n    // so there's no shared prediction event and dedup never applies.\n    const textDetector: _TurnDetector = {\n      model: 'fake',\n      provider: 'fake',\n      supportsLanguage: vi.fn(async () => true),\n      unlikelyThreshold: vi.fn(async () => 0.5),\n      predictEndOfTurn: vi.fn(async () => 0.2),\n    };\n    internals.turnDetector = textDetector;\n    internals.turnDetectorStream = undefined;\n    internals.audioTranscript = 'hello there';\n\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    await flush();\n    await flush();\n    expect(hooks.onEotPrediction).toHaveBeenCalledTimes(1);\n\n    internals.runEOUDetection(ChatContext.empty(), 'stt');\n    await flush();\n    await flush();\n    expect(hooks.onEotPrediction).toHaveBeenCalledTimes(2);\n\n    await internals.bounceEOUTask?.cancelAndWait().catch(() => {});\n  });\n\n  it('clearUserTurn resets the dedup guard so the next turn emits again', () => {\n    const { internals } = makeRecognition();\n    internals.lastEmittedEotPrediction = resolvedPrediction(0.2).event;\n    internals.audioInterimTranscript = '';\n    internals.audioPreflightTranscript = '';\n    internals.sttRequestIds = [];\n\n    internals.clearUserTurn();\n\n    expect(internals.lastEmittedEotPrediction).toBeUndefined();\n  });\n});\n\ndescribe('TestBackchannelOpportunityEmit', () => {\n  // `onAgentBackchannelOpportunity` fires whenever the backchannel probability\n  // clears its threshold, regardless of end-of-turn; the event carries the\n  // end-of-turn probability and threshold so AgentActivity can gauge how close\n  // the pause is to a reply.\n  async function drive(internals: RecognitionInternals): Promise<void> {\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    await flush();\n    await flush();\n    await internals.bounceEOUTask?.cancelAndWait().catch(() => {});\n  }\n\n  it('emits with eot context when the turn continues', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    stream.backchannelThreshold = vi.fn(async () => 0.5);\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // eot 0.2 < unlikely 0.5 → turn continues; backchannel 0.8 >= 0.5 → emit\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {\n      backchannelProbability: 0.8,\n    }).fut;\n\n    await drive(internals);\n\n    expect(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);\n    const ev = (hooks.onAgentBackchannelOpportunity as ReturnType<typeof vi.fn>).mock.calls[0]![0];\n    expect(ev.probability).toBeCloseTo(0.8);\n    expect(ev.threshold).toBeCloseTo(0.5);\n    expect(ev.endOfTurnProbability).toBeCloseTo(0.2);\n    expect(ev.endOfTurnThreshold).toBeCloseTo(0.5);\n  });\n\n  it('emits once across vad then stt triggers (shares the eot dedupe)', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    stream.backchannelThreshold = vi.fn(async () => 0.5);\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // both triggers read the same cached prediction by reference\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {\n      backchannelProbability: 0.8,\n    }).fut;\n\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    await flush();\n    await flush();\n    expect(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);\n\n    // stt trigger runs a fresh bounce against the same resolved future; the\n    // dedupe that suppresses the second eot emit must suppress this too.\n    internals.runEOUDetection(ChatContext.empty(), 'stt');\n    await flush();\n    await flush();\n\n    expect(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);\n    await internals.bounceEOUTask?.cancelAndWait().catch(() => {});\n  });\n\n  it('emits with eot context when the turn ends', async () => {\n    // The turn-continuing gate was dropped: a backchannel above threshold still\n    // fires at end-of-turn, carrying the EOT context (probability past the\n    // threshold) so AgentActivity can let it lead the reply.\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    stream.backchannelThreshold = vi.fn(async () => 0.5);\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // eot 0.9 >= unlikely 0.5 → turn ends; backchannel 0.8 >= 0.5 → still emits\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.9, {\n      backchannelProbability: 0.8,\n    }).fut;\n\n    await drive(internals);\n\n    expect(hooks.onAgentBackchannelOpportunity).toHaveBeenCalledTimes(1);\n    const ev = (hooks.onAgentBackchannelOpportunity as ReturnType<typeof vi.fn>).mock.calls[0]![0];\n    expect(ev.endOfTurnProbability).toBeCloseTo(0.9);\n    expect(ev.endOfTurnThreshold).toBeCloseTo(0.5);\n  });\n\n  it('does not emit below threshold', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    stream.backchannelThreshold = vi.fn(async () => 0.7);\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // backchannel 0.4 < 0.7 → no emit (turn continues at eot 0.2)\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {\n      backchannelProbability: 0.4,\n    }).fut;\n\n    await drive(internals);\n\n    expect(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();\n  });\n\n  it('does not emit when backchannel is disabled', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    // default fake threshold is undefined (server sent no backchannel defaults)\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.2, {\n      backchannelProbability: 0.9,\n    }).fut;\n\n    await drive(internals);\n\n    expect(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();\n  });\n\n  it('does not emit for a text-based detector', async () => {\n    // A text detector produces no streaming prediction event, so there is no\n    // backchannel probability to act on.\n    const { internals, hooks } = makeRecognition();\n    const textDetector: _TurnDetector = {\n      model: 'fake',\n      provider: 'fake',\n      supportsLanguage: vi.fn(async () => true),\n      unlikelyThreshold: vi.fn(async () => 0.5),\n      predictEndOfTurn: vi.fn(async () => 0.2),\n    };\n    internals.turnDetector = textDetector;\n    internals.turnDetectorStream = undefined;\n    internals.audioTranscript = 'hello there';\n\n    await drive(internals);\n\n    expect(hooks.onAgentBackchannelOpportunity).not.toHaveBeenCalled();\n  });\n});\n\ndescribe('TestPredictionFutureLifecycle', () => {\n  it('silence tick starts a request once', async () => {\n    const { internals } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.speaking = true;\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(inferenceDone(0, 300));\n      await vad.feed(inferenceDone(0, 400));\n\n      expect(predictMock(stream).mock.calls.length).toBe(1);\n      expect(internals.turnDetectorPredictionFut).toBeDefined();\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('resumed speech without SOS rearms the next pause', async () => {\n    const { internals } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.speaking = true;\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(inferenceDone(0, 300));\n      const firstFut = internals.turnDetectorPredictionFut;\n      expect(firstFut).toBeDefined();\n      firstFut!.resolve(resolvedPrediction(0.1).event);\n\n      // Speech resumes inside the still-open VAD segment → drop the request.\n      await vad.feed(inferenceDone(1, 0));\n      expect(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);\n      expect(cancelInferenceMock(stream)).toHaveBeenCalledWith();\n      expect(internals.turnDetectorPredictionFut).toBeUndefined();\n\n      // The next pause gets a fresh window.\n      await vad.feed(inferenceDone(0, 300));\n      expect(predictMock(stream).mock.calls.length).toBe(2);\n      expect(internals.turnDetectorPredictionFut).toBeDefined();\n      expect(internals.turnDetectorPredictionFut).not.toBe(firstFut);\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('silence tick starts a request even while the agent is speaking', async () => {\n    // The agent-speaking gate was dropped: the silence tick warms a prediction\n    // during the user's pause even while the agent is still speaking.\n    const { internals } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.speaking = true;\n    internals.isAgentSpeaking = true;\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(inferenceDone(0, 300));\n      expect(predictMock(stream).mock.calls.length).toBe(1);\n      expect(internals.turnDetectorPredictionFut).toBeDefined();\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('EOS consumes the silence-tick request without predicting', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.speaking = true;\n    const { fut } = resolvedPrediction(0.9);\n    internals.turnDetectorPredictionFut = fut;\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(endOfSpeech());\n      expect(predictMock(stream).mock.calls.length).toBe(0);\n      expect(internals.turnDetectorPredictionFut).toBe(fut);\n      expect(internals.bounceEOUTask).toBeDefined();\n      await internals.bounceEOUTask!.result.catch(() => {});\n      expect(hooks.onEotPrediction).toHaveBeenCalledTimes(1);\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('SOS tears down the request and rearms', async () => {\n    const { internals } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.turnDetectorPredictionFut = new Future<TurnDetectionEvent>();\n    internals.turnDetectorFlushed = true;\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(startOfSpeech());\n      expect(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);\n      expect(cancelInferenceMock(stream)).toHaveBeenCalledWith();\n      expect(internals.turnDetectorPredictionFut).toBeUndefined();\n      expect(internals.turnDetectorFlushed).toBe(false);\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('EOS never starts a request', async () => {\n    const { internals } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n\n    const vad = runScriptedVad(internals);\n    try {\n      await vad.feed(endOfSpeech());\n      expect(predictMock(stream).mock.calls.length).toBe(0);\n      expect(internals.turnDetectorPredictionFut).toBeUndefined();\n\n      const { fut } = resolvedPrediction(0.9);\n      internals.turnDetectorPredictionFut = fut;\n      await vad.feed(endOfSpeech());\n      expect(predictMock(stream).mock.calls.length).toBe(0);\n      expect(internals.turnDetectorPredictionFut).toBe(fut);\n    } finally {\n      await vad.stop();\n    }\n  });\n\n  it('late stt final after flush short-circuits and warns once', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    internals.turnDetectorFlushed = true;\n\n    for (let i = 0; i < 2; i++) {\n      internals.runEOUDetection(ChatContext.empty(), 'stt');\n      expect(internals.bounceEOUTask).toBeDefined();\n      await internals.bounceEOUTask!.result.catch(() => {});\n    }\n\n    expect(predictMock(stream).mock.calls.length).toBe(0);\n    expect(hooks.onEotPrediction).not.toHaveBeenCalled();\n    // Warn-once: the flag flips on the first late prediction, debug after.\n    expect(internals.turnDetectorLatePredictionWarned).toBe(true);\n  });\n\n  it('predict timeout signals fallback and drops the future', async () => {\n    const { internals, hooks } = makeRecognition();\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // A pending future that never resolves → times out at predictionTimeout.\n    internals.turnDetectorPredictionFut = new Future<TurnDetectionEvent>();\n\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    expect(internals.bounceEOUTask).toBeDefined();\n    await internals.bounceEOUTask!.result.catch(() => {});\n\n    expect(cancelInferenceMock(stream)).toHaveBeenCalledTimes(1);\n    expect(cancelInferenceMock(stream)).toHaveBeenCalledWith({ timedOut: true });\n    expect(internals.turnDetectorPredictionFut).toBeUndefined();\n    expect(hooks.onEotPrediction).not.toHaveBeenCalled();\n    expect(stream.unlikelyThreshold).not.toHaveBeenCalled();\n    expect(hooks.onEndOfTurn).toHaveBeenCalledTimes(1);\n  });\n\n  it('commit flushes the stream and marks the turn flushed', async () => {\n    const { internals, hooks } = makeRecognition();\n    (hooks.onEndOfTurn as ReturnType<typeof vi.fn>).mockResolvedValue(true);\n    const stream = makeAudioStream();\n    internals.turnDetectorStream = stream;\n    internals.turnDetector = makeAudioDetector(stream);\n    // confident → no maxDelay extension\n    internals.turnDetectorPredictionFut = resolvedPrediction(0.9).fut;\n\n    internals.runEOUDetection(ChatContext.empty(), 'vad');\n    expect(internals.bounceEOUTask).toBeDefined();\n    await internals.bounceEOUTask!.result.catch(() => {});\n\n    expect(stream.flush).toHaveBeenCalledWith('turn committed');\n    expect(internals.turnDetectorPredictionFut).toBeUndefined();\n    expect(internals.turnDetectorFlushed).toBe(true);\n  });\n});\n\ndescribe('TestVadMinSilenceRequirement', () => {\n  // The audio EOT detector needs ~200ms of trailing silence, so the VAD must\n  // report END_OF_SPEECH no earlier than that floor + a 50ms margin.\n  const requiredMs = MIN_SILENCE_DURATION_MS + 50;\n  const fakeVad = (minSilenceDuration: number | null): VAD =>\n    ({ minSilenceDuration }) as unknown as VAD;\n\n  it('raises when min silence is too low for an audio detector', () => {\n    const { internals } = makeRecognition();\n    internals.vad = fakeVad(requiredMs - 1);\n    internals.turnDetector = makeAudioDetector(makeAudioStream());\n\n    expect(() => internals.checkVadSilenceRequirement()).toThrow(/minSilenceDuration/);\n  });\n\n  it('passes when min silence is adequate', () => {\n    const { internals } = makeRecognition();\n    internals.vad = fakeVad(requiredMs + 250);\n    internals.turnDetector = makeAudioDetector(makeAudioStream());\n\n    expect(() => internals.checkVadSilenceRequirement()).not.toThrow();\n  });\n\n  it('skips validation for a non-audio detector', () => {\n    const { internals } = makeRecognition();\n    internals.vad = fakeVad(requiredMs - 1);\n    internals.turnDetector = { model: 'x', provider: 'x' } as unknown as _TurnDetector;\n\n    expect(() => internals.checkVadSilenceRequirement()).not.toThrow();\n  });\n\n  it('skips validation when there is no VAD', () => {\n    const { internals } = makeRecognition();\n    internals.vad = undefined;\n    internals.turnDetector = makeAudioDetector(makeAudioStream());\n\n    expect(() => internals.checkVadSilenceRequirement()).not.toThrow();\n  });\n\n  it('skips validation when the VAD exposes no min-silence knob', () => {\n    const { internals } = makeRecognition();\n    // A VAD whose minSilenceDuration is null can't be validated → allowed.\n    internals.vad = fakeVad(null);\n    internals.turnDetector = makeAudioDetector(makeAudioStream());\n\n    expect(() => internals.checkVadSilenceRequirement()).not.toThrow();\n  });\n\n  it('updateTurnDetector validates the pairing before building a stream', () => {\n    const { internals } = makeRecognition();\n    internals.vad = fakeVad(requiredMs - 1);\n    const stream = makeAudioStream();\n    const detector = makeAudioDetector(stream);\n\n    expect(() => internals.updateTurnDetector(detector)).toThrow(/minSilenceDuration/);\n\n    // Aborted before adopting the detector or opening a stream.\n    expect(internals.turnDetectorStream).toBeUndefined();\n    expect((detector.stream as ReturnType<typeof vi.fn>).mock.calls.length).toBe(0);\n  });\n});\n"],"mappings":"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","names":[]}