merge: tail-drain and chunked ASR hardening into main

Resolve FlowContinuousCapture by combining tail-drain gate/drain logic
with flow-abcd route-change and interruption recovery.
This commit is contained in:
Rocky
2026-07-06 18:21:34 +08:00
14 changed files with 696 additions and 53 deletions
@@ -11,4 +11,8 @@ enum FlowDiagnostics {
static func log(_ message: String) {
OSGLog.flow.info("\(message, privacy: .public)")
}
static func logDrain(_ report: FlowCaptureDrainReport) {
FlowPipelineDiagnostics.logDrain(report)
}
}
@@ -477,21 +477,21 @@ final class FlowSessionManager: ObservableObject {
// Close the mic gate first, then mark processing before dropping the
// recording flag so the poll loop cannot start a second utterance.
capture.endUtterance()
FlowSessionBridge.setRecordingState(.processing)
isUtteranceRecording = false
isUtteranceProcessing = true
FlowLiveActivityController.update(phase: .processing)
// Do NOT cancel `asrTask` or `asr` the preview pipeline relies on
// the consumer staying alive until `.final` lands (see
// `PreviewASRControllerStateTests`).
// Do NOT cancel `asrTask` or `asr` drain trailing PCM, then finalize.
finalizeTask?.cancel()
finalizeTask = Task { @MainActor [weak self] in
await self?.finalizeUtterance()
guard let self else { return }
let drainReport = await self.capture.endUtteranceAndDrain()
FlowDiagnostics.logDrain(drainReport)
await self.finalizeUtterance()
}
debug("utterance stopped, finalizing")
debug("utterance stopped, draining tail")
}
private func abortUtterance() {