mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(voice): bare stop phrase ends the voice chat on every surface, spoken or typed
Saying OR typing a configured stop phrase (voice.stop_phrases, default
"stop") now ends the voice chat everywhere, not just classic CLI PTT:
- hermes_cli/voice.py: new explicit on_stop_phrase callback through
start_continuous/stop_continuous. The force-transcribe path previously
DISCARDED the stop phrase silently — with auto_restart=False the client
re-arms the next capture, so the conversation never ended. Both halt
paths now fire on_stop_phrase (fallback: on_silent_limit for legacy
callers) as user intent, distinct from the no-speech timeout.
- tui_gateway/server.py: voice.record wires on_stop_phrase and emits
voice.transcript {stop_phrase: true} after flipping HERMES_VOICE(_TTS)
off and stopping streaming TTS — same teardown as /voice off. The TTS
barge-in monitor stop-checks its transcript too. prompt.submit consumes
a TYPED bare stop phrase at the server-side choke point when voice mode
is on (returns {voice_stopped: true}, no turn starts).
- ui-tui: voice.transcript {stop_phrase} ends voice mode with a clear
'voice chat ended' notice (distinct from the no-speech-limit message);
submitPrompt releases the busy latch on a consumed voice_stopped reply.
- cli.py: _typed_voice_stop in process_loop — typing a bare stop phrase
while voice mode/continuous is active ends voice mode instead of
sending 'stop' to the agent; typed 'stop' outside voice mode is
unchanged. Voice transcripts skip the check (already stop-checked).
- desktop: interceptsTypedVoiceStop — the composer's onSubmit ends the
live voice conversation (same path as clicking end on the pill) when a
bare stop command is typed with no attachments; renderer-owned loop, so
handled client-side like the existing spoken isVoiceStopCommand.
- tools/voice_mode.py: transcribe_recording never lets the Whisper
hallucination filter swallow a configured stop phrase (e.g. 'bye'
configured as a stop phrase is both a hallucination-blocklist entry and
a stop phrase — stop-phrase check now wins).
Tests: continuous-loop signal (sabotage-verified), force-transcribe stop
signal + legacy fallback, hallucination-filter ordering, typed-stop CLI
unit tests (voice on/off/longer text), prompt.submit typed-stop gateway
tests, TUI vitest for stop_phrase event handling, desktop vitest for the
typed-stop interceptor.
This commit is contained in:
parent
738725d18b
commit
ba13132298
15 changed files with 605 additions and 13 deletions
35
cli.py
35
cli.py
|
|
@ -12250,6 +12250,33 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
_cprint(f" {_DIM}/voice tts to toggle speech output{_RST}")
|
||||
_cprint(f" {_DIM}/voice off to disable voice mode{_RST}")
|
||||
|
||||
def _typed_voice_stop(self, user_input) -> bool:
|
||||
"""Typed bare stop phrase during an active voice chat ends the chat.
|
||||
|
||||
Saying "stop" ends the voice chat (PR #73106); TYPING the same bare
|
||||
stop phrase while voice mode is on must behave identically instead of
|
||||
sending "stop" to the agent as a turn. Guarded on voice mode being ON
|
||||
— typed "stop" outside voice chat passes through to the agent exactly
|
||||
as before. Reuses ``is_voice_stop_phrase`` (same config
|
||||
``voice.stop_phrases``, same exact-match semantics), so longer typed
|
||||
messages containing "stop" are never swallowed.
|
||||
"""
|
||||
if not isinstance(user_input, str):
|
||||
return False
|
||||
with self._voice_lock:
|
||||
voice_on = self._voice_mode or self._voice_continuous
|
||||
if not voice_on:
|
||||
return False
|
||||
try:
|
||||
from tools.voice_mode import is_voice_stop_phrase
|
||||
if not is_voice_stop_phrase(user_input):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
_cprint(f"\n{_DIM}Stop phrase typed — ending voice chat.{_RST}")
|
||||
self._disable_voice_mode()
|
||||
return True
|
||||
|
||||
def _disable_voice_mode(self):
|
||||
"""Disable voice mode, cancel any active recording, and stop TTS."""
|
||||
recorder = None
|
||||
|
|
@ -16522,6 +16549,14 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
|||
user_input, _had_mouse_reports = _strip_leaked_terminal_responses_with_meta(user_input)
|
||||
if _had_mouse_reports:
|
||||
self._recover_terminal_input_modes(reason="mouse reports leaked into submitted input")
|
||||
|
||||
# Typed bare stop phrase while a voice chat is active ends
|
||||
# the voice chat (same semantics as SAYING "stop") instead
|
||||
# of sending the word to the agent. Voice transcripts are
|
||||
# already stop-checked at the transcription points, so this
|
||||
# only intercepts typed input.
|
||||
if not is_voice_input and self._typed_voice_stop(user_input):
|
||||
continue
|
||||
|
||||
# Check for commands — but detect dragged/pasted file paths first.
|
||||
# See _detect_file_drop() for details.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue