fix(flow): drain trailing audio and harden chunked ASR pipeline

- P0/P1: tail-drain state machine in FlowContinuousCapture with converter
  flush; FlowSessionManager awaits drain before finalize
- P2: FlowCaptureTailDrain policy/tracker, pipeline diagnostics, unit tests
- P3: short final chunk merged re-transcription in ChunkedUtterancePipeline
- P4: UtteranceTranscriptStitcher composedSafely fallback; preview path parity
  in LiveDictationController

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-06 05:13:55 +00:00
parent 537a68552a
commit 8b105430e3
14 changed files with 690 additions and 47 deletions
@@ -21,6 +21,12 @@ public struct UtteranceTranscriptStitcher: Sendable {
}
}
/// Drop the highest-index segment (used when re-transcribing a merged tail chunk).
public mutating func removeLastSegment() {
guard !segments.isEmpty else { return }
segments.removeLast()
}
public func composed() -> String {
guard let first = segments.first else { return "" }
var result = first.text
@@ -30,6 +36,21 @@ public struct UtteranceTranscriptStitcher: Sendable {
return result
}
/// Prefer overlap-aware merge, but fall back to naive join when dedup would drop real content.
public func composedSafely() -> String {
let merged = composed()
guard segments.count >= 2 else { return merged }
let naive = segments.map(\.text).joined(separator: " ")
if merged.count + 16 < naive.count {
FlowPipelineDiagnostics.logStitcherSafeFallback(
naiveLength: naive.count,
mergedLength: merged.count
)
return naive
}
return merged
}
/// Merge `next` onto `previous`, dropping duplicated suffix/prefix overlap.
public static func mergeWithOverlap(previous: String, next: String) -> String {
let trimmedNext = next.trimmingCharacters(in: .whitespacesAndNewlines)