Commit 7ca6a93
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_01BJxPoE8v57ck3EDfeZCpo41 parent 0a5b732 commit 7ca6a93
4 files changed
Lines changed: 1539 additions & 31 deletions
File tree
- libraries/typescript
- src
- observability
- tests/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
| |||
0 commit comments