Skip to content

fix(tui): interrupt in-flight TTS playback when starting voice recording - #65192

Open
wreed4 wants to merge 1 commit into
NousResearch:mainfrom
wreed4:fix/tui-voice-interrupt-tts
Open

fix(tui): interrupt in-flight TTS playback when starting voice recording#65192
wreed4 wants to merge 1 commit into
NousResearch:mainfrom
wreed4:fix/tui-voice-interrupt-tts

Conversation

@wreed4

@wreed4 wreed4 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

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 immediately starts listening. 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 interrupt it short of exiting the session.

This adds the same stop_playback() call to the TUI's voice.record 'start' action, restoring parity with the classic CLI's interrupt behavior.

Root cause

  • hermes_cli/voice.py::speak_text plays TTS audio via a subprocess tracked in tools/voice_mode.py's _active_playback.
  • tools/voice_mode.py::stop_playback() terminates that subprocess.
  • cli.py's Ctrl+B handler already calls stop_playback() before starting a new recording (see cli.py around the voice key binding).
  • tui_gateway/server.py's voice.record method (used by the Ink TUI, the default interface) never referenced stop_playback or tools.voice_mode at all — confirmed via grep -rn stop_playback tui_gateway/server.py returning zero matches before this patch.

Fix

Added a stop_playback() call (best-effort, wrapped in try/except like the neighboring stop_continuous cleanup path) at the top of the voice.record 'start' action, before start_continuous() is invoked.

Test plan

  • scripts/run_tests.sh tests/test_tui_gateway_server.py — 323/323 passed, no regressions.
  • Manual mock verification: patched stop_playback and start_continuous, dispatched voice.record with action=start, confirmed both are called and the RPC still returns {status: recording}.
  • Manually verified end-to-end in a live TUI session: TTS now stops immediately when the voice record key is pressed mid-playback.

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'.
@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) tool/tts Text-to-speech and transcription P3 Low — cosmetic, nice to have labels Jul 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #40025 covers the same record-start interruption UX through the broader stop_speaking path, including pending TTS. This PR directly stops active playback, so the approaches overlap but are not byte/mechanism-equivalent.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-638 verifies recorder configuration but does not assert that playback interruption precedes start_continuous().

Suggested changes

  • Add a focused RPC test that patches tools.voice_mode.stop_playback and hermes_cli.voice.start_continuous, dispatches voice.record with action: start, and asserts the required ordering.

Automated hermes-sweeper review.

Comment thread tui_gateway/server.py
from tools.voice_mode import stop_playback

stop_playback()
except Exception:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Twenty 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 consolidation

Keep #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 graph

flowchart 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"
Loading

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants