feat(voice): CLI + TUI tell the model when its spoken reply was cut

CLI: the VAD monitor's playback cut and the record-key interrupt both
mark the latch; the chat path prepends the note via the existing
_prepend_note_to_message channel. TUI: _tts_stream_stop() grows a
user_barge flag (False for /voice off — a mode change isn't an
interruption; no-op when speech already finished), the VAD monitor
marks on cut, prompt.submit accepts interrupted:true from clients, and
_run_prompt_submit pops the latch into the run message.
This commit is contained in:
Brooklyn Nicholson 2026-07-22 17:53:06 -05:00
parent 393c100a92
commit 05b3637d8b
3 changed files with 85 additions and 5 deletions

9
cli.py
View file

@ -11296,6 +11296,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
def _cut_playback():
if not self._voice_tts_done.is_set():
from tools.tts_streaming import mark_speech_interrupted
mark_speech_interrupted()
self._voice_barge_capture.set()
stop_event.set()
stop_playback()
@ -12302,6 +12304,11 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
if _srn:
agent_message = _prepend_note_to_message(agent_message, _srn)
self._pending_skills_reload_note = None
# Barged mid-speech (VAD or record key)? Tell the model it was
# cut off — same one-shot, API-local note channel as above.
from tools.tts_streaming import SPEECH_INTERRUPTED_NOTE, take_speech_interrupted
if take_speech_interrupted():
agent_message = _prepend_note_to_message(agent_message, SPEECH_INTERRUPTED_NOTE)
_moa_cfg = getattr(self, "_pending_moa_config", None)
self._pending_moa_config = None
if _moa_cfg is None:
@ -14179,6 +14186,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
# the stop event drains the streaming pipeline if one is live.
if not cli_ref._voice_tts_done.is_set():
try:
from tools.tts_streaming import mark_speech_interrupted
mark_speech_interrupted()
if cli_ref._voice_tts_stop is not None:
cli_ref._voice_tts_stop.set()
from tools.voice_mode import stop_playback