diff --git a/cli.py b/cli.py index e2ee9a267..98476a427 100755 --- a/cli.py +++ b/cli.py @@ -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}") diff --git a/tests/tools/test_voice_cli_integration.py b/tests/tools/test_voice_cli_integration.py index e7be698d3..105b27fc4 100644 --- a/tests/tools/test_voice_cli_integration.py +++ b/tests/tools/test_voice_cli_integration.py @@ -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: