feat(flow): add whole-utterance batch ASR fallback (P1)

- Accumulate utterance PCM in FlowContinuousCapture for batch retry
- Run full-utterance transcribeChunk when stitched final lags partial
- Refactor Mac MLX tail drain to shared FlowUtteranceEndCoordinator
- Add FlowUtterancePCMStore, UtteranceBatchFallbackPolicy, and tests

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-26 10:15:31 +00:00
parent c7ee891a90
commit f0240c3088
9 changed files with 256 additions and 3 deletions
+11 -3
View File
@@ -43,6 +43,7 @@ enum MacMLXLiveCapture {
let drainTracker = FlowCaptureDrainTracker()
let draining = OSAllocatedUnfairLock(initialState: false)
let drainComplete = OSAllocatedUnfairLock(initialState: false)
let pendingFeed = OSAllocatedUnfairLock(initialState: [Float]())
let feedIntervalSamples = 1_600 // 100 ms @ 16 kHz
@@ -56,6 +57,11 @@ enum MacMLXLiveCapture {
for await _ in finishSignal {
draining.withLock { $0 = true }
drainTracker.beginDrain()
_ = await FlowUtteranceEndCoordinator.awaitTailCapture(
tracker: drainTracker,
policy: tailDrainPolicy
)
drainComplete.withLock { $0 = true }
break
}
}
@@ -63,10 +69,12 @@ enum MacMLXLiveCapture {
group.addTask {
for await snapshot in audioStream {
if Task.isCancelled { break }
if drainComplete.withLock({ $0 }) { break }
if draining.withLock({ $0 }) {
drainTracker.noteAudio(samples: snapshot.samples, policy: tailDrainPolicy)
let decision = drainTracker.shouldFinish(policy: tailDrainPolicy)
if decision.finished { break }
drainTracker.noteAudio(
samples: snapshot.samples,
policy: tailDrainPolicy
)
}
pendingFeed.withLock { buffer in
buffer.append(contentsOf: snapshot.samples)