From 9633ddd8d843e919b238c9355be78c22d1751e80 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 14 Mar 2026 06:31:32 -0700 Subject: [PATCH] fix: initialize CLI voice state for single-query mode - initialize voice and interrupt runtime state in HermesCLI.__init__ - prevent chat -q from crashing before run() has executed - add regression coverage for single-query state initialization --- cli.py | 30 ++++++++++++++++++++++++++++++ tests/test_cli_init.py | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/cli.py b/cli.py index 7bd455bd0..094be22e9 100755 --- a/cli.py +++ b/cli.py @@ -1289,11 +1289,41 @@ class HermesCLI: self._history_file = _hermes_home / ".hermes_history" self._last_invalidate: float = 0.0 # throttle UI repaints self._app = None + + # State shared by interactive run() and single-query chat mode. + # These must exist before any direct chat() call because single-query + # mode does not go through run(). + self._agent_running = False + self._pending_input = queue.Queue() + self._interrupt_queue = queue.Queue() + self._should_exit = False + self._last_ctrl_c_time = 0 + self._clarify_state = None + self._clarify_freetext = False + self._clarify_deadline = 0 + self._sudo_state = None + self._sudo_deadline = 0 + self._approval_state = None + self._approval_deadline = 0 + self._approval_lock = threading.Lock() self._secret_state = None self._secret_deadline = 0 self._spinner_text: str = "" # thinking spinner text for TUI self._command_running = False self._command_status = "" + self._attached_images: list[Path] = [] + self._image_counter = 0 + + # Voice mode state (also reinitialized inside run() for interactive TUI). + self._voice_lock = threading.Lock() + self._voice_mode = False + self._voice_tts = False + self._voice_recorder = None + self._voice_recording = False + self._voice_processing = False + self._voice_continuous = False + self._voice_tts_done = threading.Event() + self._voice_tts_done.set() # Background task tracking: {task_id: threading.Thread} self._background_tasks: Dict[str, threading.Thread] = {} diff --git a/tests/test_cli_init.py b/tests/test_cli_init.py index 1afb7c912..5ebd301ed 100644 --- a/tests/test_cli_init.py +++ b/tests/test_cli_init.py @@ -95,6 +95,17 @@ class TestVerboseAndToolProgress: assert cli.tool_progress_mode in ("off", "new", "all", "verbose") +class TestSingleQueryState: + def test_voice_and_interrupt_state_initialized_before_run(self): + """Single-query mode calls chat() without going through run().""" + cli = _make_cli() + assert cli._voice_tts is False + assert cli._voice_mode is False + assert cli._voice_tts_done.is_set() + assert hasattr(cli, "_interrupt_queue") + assert hasattr(cli, "_pending_input") + + class TestHistoryDisplay: def test_history_numbers_only_visible_messages_and_summarizes_tools(self, capsys): cli = _make_cli()