fix(tui): interrupt in-flight TTS playback when starting voice recording - #65192
fix(tui): interrupt in-flight TTS playback when starting voice recording#65192wreed4 wants to merge 1 commit into
Conversation
The classic CLI (cli.py) calls stop_playback() before opening the mic on its Ctrl+B / record-key handler, so pressing the key while Hermes is speaking cuts off TTS and starts listening immediately. The TUI's voice.record RPC handler (tui_gateway/server.py) never had this call — pressing the record key while TTS was playing would start recording but leave the audio playing in the background with no way to stop it short of exiting. This adds the same stop_playback() call to the TUI's voice.record 'start' action, restoring parity with the classic CLI's interrupt behavior. Verified: 323/323 existing tests in tests/test_tui_gateway_server.py pass, plus a manual mock check confirming stop_playback() and start_continuous() are both invoked on 'start'.
Related: #40025 covers the same record-start interruption UX through the broader |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused TUI/classic-CLI parity fix. Current main confirms the premise: tui_gateway/server.py:13518-13561 starts capture without stopping audio, whereas the classic handler calls stop_playback() before recording at cli.py:14268-14276.
Problems
- The patch adds no regression coverage. The existing TUI record-start coverage at
tests/test_tui_gateway_server.py:567-638verifies recorder configuration but does not assert that playback interruption precedesstart_continuous().
Suggested changes
- Add a focused RPC test that patches
tools.voice_mode.stop_playbackandhermes_cli.voice.start_continuous, dispatchesvoice.recordwithaction: start, and asserts the required ordering.
Automated hermes-sweeper review.
| from tools.voice_mode import stop_playback | ||
|
|
||
| stop_playback() | ||
| except Exception: |
There was a problem hiding this comment.
Please add a regression test for this ordering in tests/test_tui_gateway_server.py: patch stop_playback and start_continuous, dispatch voice.record with action: start, and assert playback interruption occurs before recording starts.
SummaryTwenty PRs are associated with this three-issue complex: #40025 and #65192 address TUI PTT interruption, #49884/#50257 plus partial #50650/#51227 address quoted voice-beep booleans, and #64086 changes Python playback rather than the reported Desktop queue; the remaining diffs cover sibling config coercion, TTS cleanup/playback, or regex performance. The recorded best-fix set is #40025 and #65192 for #40010 and #49884 for #49883, while no recorded best fix addresses the Desktop cause in #63697. Related pull requests
Duplicates#15334 is the source duplicate salvaged as #15387; #32712 and #32713 have the same diff; #49884 and closed #50257 implement the same complete beep fix, with closed #50650 and #51227 as partial subsets; #54419 was replaced by rebased #56446. #40025 and #65192 overlap on #40010 but are not mechanism-equivalent: #65192 stops active playback, whereas #40025 also cancels queued, synthesizing, and streaming TTS and handles recorder re-arm. Suggested consolidationKeep #65192 open with a salvage path: preserve its focused active-playback stop and add the contributor-requested RPC ordering regression before any further disposition; this respects its recorded best-fix status and keep-open review rather than closing it behind the broader #40025. Keep #40025 open for its broader queue-safe cancellation path, retain #49884 as the focused #49883 salvage candidate, and use author action on #64086 to rebase or split into the Desktop auto-speak hook with a replacement regression; the already-closed #50257/#50650/#51227 need no further duplicate action. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I40010(["issue #40010 (open)"])
P65192["PR #65192 (open)"]
P65192 -->|best fix| I40010
class I40010 open
class P65192 open
class P65192 best
class P65192 target
click I40010 "https://github.com/NousResearch/hermes-agent/issues/40010"
click P65192 "https://github.com/NousResearch/hermes-agent/pull/65192"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 20 pull requests and 3 issues in this complex. Each diff was read against this issue; Assessment working set: 193 kB of PR diffs, 33 kB of issue/PR text, 16 kB of discussion (28 comments), 10 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Summary
The classic CLI (
cli.py) callsstop_playback()before opening the mic on its Ctrl+B / record-key handler, so pressing the key while Hermes is speaking cuts off TTS and immediately starts listening. The TUI'svoice.recordRPC handler (tui_gateway/server.py) never had this call — pressing the record key while TTS was playing would start recording but leave the audio playing in the background, with no way to interrupt it short of exiting the session.This adds the same
stop_playback()call to the TUI'svoice.record'start' action, restoring parity with the classic CLI's interrupt behavior.Root cause
hermes_cli/voice.py::speak_textplays TTS audio via a subprocess tracked intools/voice_mode.py's_active_playback.tools/voice_mode.py::stop_playback()terminates that subprocess.cli.py's Ctrl+B handler already callsstop_playback()before starting a new recording (seecli.pyaround the voice key binding).tui_gateway/server.py'svoice.recordmethod (used by the Ink TUI, the default interface) never referencedstop_playbackortools.voice_modeat all — confirmed viagrep -rn stop_playback tui_gateway/server.pyreturning zero matches before this patch.Fix
Added a
stop_playback()call (best-effort, wrapped in try/except like the neighboringstop_continuouscleanup path) at the top of thevoice.record'start' action, beforestart_continuous()is invoked.Test plan
scripts/run_tests.sh tests/test_tui_gateway_server.py— 323/323 passed, no regressions.stop_playbackandstart_continuous, dispatchedvoice.recordwithaction=start, confirmed both are called and the RPC still returns{status: recording}.