Skip to content

Commit 7ca6a93

Browse files
committed
feat(pipeline): pause-and-resume false-interruption handling (TypeScript)
TypeScript port of 3877814 — exact parity with the Python semantics, defaults, and events (camelCase ↔ snake_case naming): Opt-in agent.bargeInMode: 'pause_resume' (default 'cancel' keeps today's behaviour byte-identical). LiveKit-style state machine on VAD speech_start while the agent speaks: - PAUSE: gate the sentence/audio send loops on outputPaused and sendClear the carrier so queued audio stops within a frame. The LLM stream and TTS provider stream stay alive: sentences buffer as text (capped at 32) and synthesized audio queues into per-sentence retention entries (capped at ~15 s of playout; overflow while paused degrades to a full cancel, overflow while speaking releases retention for the turn). Mic audio flows to STT while paused and the inbound ring is flushed so the confirm window can actually hear the user. - KILL: a committed final transcript (non-echo, non-hallucination, non-duplicate — the existing handleBargeIn/commitTranscript filter family) within bargeInConfirmMs (default 1500 ms) runs the existing runBargeInCancel path and discards the paused buffers. The overlap window anchored at pause time is preserved so detection_delay measures VAD-T1 -> confirm-T2. - RESUME: window expires with no confirming transcript -> re-send the cleared-but-unheard tail from retained audio at SENTENCE granularity (first sentence not fully played, derived from the #164 playbackBufferedUntil cursor + heard-prefix segments; the partially-played sentence replays from its start) without re-billing TTS, then release the buffered sentences through the normal synth path. Recorded as a false interruption via recordOverlapEnd(false) — the backchannel counter, never an interruption — plus a 'false_interruption' event ({ resumedSentences }). The playback bookkeeping is frozen at the heard offset on pause so a kill still rewrites history to the heard prefix; on resume the replay re-stamps segments so later barge-ins stay accurate. Turn bodies wait out an in-flight pause decision before ending — completes the predecessor's in-progress port by bounding awaitPauseDecision (confirm window + 5 s fail-open margin, mirroring Python's _await_pause_decision) so a teardown race can never strand the dispatch loop. Tests mirror tests/unit/test_barge_in_pause_resume.py: pause gates without cancelling, paused buffering + overflow degradation, resume tail replay + false-interruption metrics/event, kill filters (final-only / hallucination / duplicate / frozen-prefix history rewrite), legacy cancel mode untouched, config-off defaults, streaming-loop integration (resume, kill, stream-ends-paused), and teardown mid-pause. https://claude.ai/code/session_01BJxPoE8v57ck3EDfeZCpo4
1 parent 0a5b732 commit 7ca6a93

4 files changed

Lines changed: 1539 additions & 31 deletions

File tree

libraries/typescript/src/observability/event-bus.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export type PatterEventType =
2525
| 'transcript_final'
2626
| 'llm_chunk'
2727
| 'tts_chunk'
28-
| 'tool_call_started';
28+
| 'tool_call_started'
29+
// Pause-and-resume (bargeInMode: 'pause_resume'): a pause that no
30+
// transcript confirmed resumed from the unheard tail. Payload:
31+
// ``{ resumedSentences: number }``. Mirrors the Python emit.
32+
| 'false_interruption';
2933

3034
type Listener<T = unknown> = (payload: T) => void | Promise<void>;
3135

0 commit comments

Comments
 (0)