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:
0xbyt4
2026-03-10 13:31:50 +03:00
parent d0e3b39e69
commit 9d58cafec9
2 changed files with 19 additions and 16 deletions

28
cli.py
View File

@@ -3772,8 +3772,8 @@ class HermesCLI:
else:
self._enable_voice_mode()
else:
print(f"Unknown voice subcommand: {subcommand}")
print("Usage: /voice [on|off|tts|status]")
_cprint(f"Unknown voice subcommand: {subcommand}")
_cprint("Usage: /voice [on|off|tts|status]")
def _enable_voice_mode(self):
"""Enable voice mode after checking requirements."""
@@ -5602,17 +5602,21 @@ class HermesCLI:
self._spinner_text = ""
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:
try:
# Wait for TTS to finish so we don't record the speaker
if self._voice_tts:
self._voice_tts_done.wait(timeout=60)
time.sleep(0.3) # Brief pause after TTS ends
self._voice_start_recording()
app.invalidate()
except Exception as e:
_cprint(f"{_DIM}Voice auto-restart failed: {e}{_RST}")
def _restart_recording():
try:
if self._voice_tts:
self._voice_tts_done.wait(timeout=60)
time.sleep(0.3)
self._voice_start_recording()
app.invalidate()
except Exception as e:
_cprint(f"{_DIM}Voice auto-restart failed: {e}{_RST}")
threading.Thread(target=_restart_recording, daemon=True).start()
except Exception as e:
print(f"Error: {e}")

View File

@@ -875,16 +875,15 @@ class TestHandleVoiceCommandReal:
cli._handle_voice_command("/voice")
cli._enable_voice_mode.assert_called_once()
@patch("builtins.print")
@patch("cli._cprint")
def test_unknown_subcommand(self, _cp, mock_print):
def test_unknown_subcommand(self, mock_cp):
cli = self._cli()
cli._handle_voice_command("/voice foobar")
cli._enable_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)
for c in mock_print.call_args_list)
for c in mock_cp.call_args_list)
class TestEnableVoiceModeReal: