fix: move process_loop voice restart to daemon thread, use _cprint consistently
- process_loop's continuous mode restart called _voice_start_recording() directly, blocking the loop if play_beep/sd.wait hangs — queued user input would stall silently. Dispatch to daemon thread like Ctrl+B handler. - Replace print() with _cprint() in _handle_voice_command for consistency with the rest of the voice mode code.
This commit is contained in:
parent
d0e3b39e69
commit
9d58cafec9
2 changed files with 19 additions and 16 deletions
28
cli.py
28
cli.py
|
|
@ -3772,8 +3772,8 @@ class HermesCLI:
|
||||||
else:
|
else:
|
||||||
self._enable_voice_mode()
|
self._enable_voice_mode()
|
||||||
else:
|
else:
|
||||||
print(f"Unknown voice subcommand: {subcommand}")
|
_cprint(f"Unknown voice subcommand: {subcommand}")
|
||||||
print("Usage: /voice [on|off|tts|status]")
|
_cprint("Usage: /voice [on|off|tts|status]")
|
||||||
|
|
||||||
def _enable_voice_mode(self):
|
def _enable_voice_mode(self):
|
||||||
"""Enable voice mode after checking requirements."""
|
"""Enable voice mode after checking requirements."""
|
||||||
|
|
@ -5602,17 +5602,21 @@ class HermesCLI:
|
||||||
self._spinner_text = ""
|
self._spinner_text = ""
|
||||||
app.invalidate() # Refresh status line
|
app.invalidate() # Refresh status line
|
||||||
|
|
||||||
# Continuous voice: auto-restart recording after agent responds
|
# Continuous voice: auto-restart recording after agent responds.
|
||||||
|
# Dispatch to a daemon thread so play_beep (sd.wait) and
|
||||||
|
# AudioRecorder.start (lock acquire) never block process_loop —
|
||||||
|
# otherwise queued user input would stall silently.
|
||||||
if self._voice_mode and self._voice_continuous and not self._voice_recording:
|
if self._voice_mode and self._voice_continuous and not self._voice_recording:
|
||||||
try:
|
def _restart_recording():
|
||||||
# Wait for TTS to finish so we don't record the speaker
|
try:
|
||||||
if self._voice_tts:
|
if self._voice_tts:
|
||||||
self._voice_tts_done.wait(timeout=60)
|
self._voice_tts_done.wait(timeout=60)
|
||||||
time.sleep(0.3) # Brief pause after TTS ends
|
time.sleep(0.3)
|
||||||
self._voice_start_recording()
|
self._voice_start_recording()
|
||||||
app.invalidate()
|
app.invalidate()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_cprint(f"{_DIM}Voice auto-restart failed: {e}{_RST}")
|
_cprint(f"{_DIM}Voice auto-restart failed: {e}{_RST}")
|
||||||
|
threading.Thread(target=_restart_recording, daemon=True).start()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: {e}")
|
print(f"Error: {e}")
|
||||||
|
|
|
||||||
|
|
@ -875,16 +875,15 @@ class TestHandleVoiceCommandReal:
|
||||||
cli._handle_voice_command("/voice")
|
cli._handle_voice_command("/voice")
|
||||||
cli._enable_voice_mode.assert_called_once()
|
cli._enable_voice_mode.assert_called_once()
|
||||||
|
|
||||||
@patch("builtins.print")
|
|
||||||
@patch("cli._cprint")
|
@patch("cli._cprint")
|
||||||
def test_unknown_subcommand(self, _cp, mock_print):
|
def test_unknown_subcommand(self, mock_cp):
|
||||||
cli = self._cli()
|
cli = self._cli()
|
||||||
cli._handle_voice_command("/voice foobar")
|
cli._handle_voice_command("/voice foobar")
|
||||||
cli._enable_voice_mode.assert_not_called()
|
cli._enable_voice_mode.assert_not_called()
|
||||||
cli._disable_voice_mode.assert_not_called()
|
cli._disable_voice_mode.assert_not_called()
|
||||||
# Should print usage via print() (not _cprint)
|
# Should print usage via _cprint
|
||||||
assert any("Unknown" in str(c) or "unknown" in str(c)
|
assert any("Unknown" in str(c) or "unknown" in str(c)
|
||||||
for c in mock_print.call_args_list)
|
for c in mock_cp.call_args_list)
|
||||||
|
|
||||||
|
|
||||||
class TestEnableVoiceModeReal:
|
class TestEnableVoiceModeReal:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue