mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 05:11:26 +00:00
fix(cli): drive _prompt_text_input directly when off main thread (#23454)
Slash commands (/clear, /new, /undo, /reload-mcp) are dispatched from the process_loop daemon thread. prompt_toolkit.run_in_terminal returns a coroutine that only the main-thread event loop can drive, so calling it from a daemon thread orphans the coroutine — the input prompt never renders and user keystrokes leak into the composer instead of the confirmation prompt (issue #23185). Mirror the thread-aware guard already in _run_curses_picker: when off the main thread, fall back to a direct input() call. Also wrap run_in_terminal in try/except so WSL / Warp / other emulators that silently drop the scheduled coroutine fall back to input() too. Tests: tests/cli/test_prompt_text_input_thread_safety.py covers main thread (run_in_terminal path), daemon thread (direct input fallback), no-app, run_in_terminal-raises, and EOF handling.
This commit is contained in:
parent
62cfe79e93
commit
c5f1f863ac
2 changed files with 131 additions and 2 deletions
24
cli.py
24
cli.py
|
|
@ -6008,7 +6008,17 @@ class HermesCLI:
|
|||
return result[0]
|
||||
|
||||
def _prompt_text_input(self, prompt_text: str) -> str | None:
|
||||
"""Prompt for free-text input safely inside or outside prompt_toolkit."""
|
||||
"""Prompt for free-text input safely inside or outside prompt_toolkit.
|
||||
|
||||
Mirrors the thread-aware guard in ``_run_curses_picker``: ``run_in_terminal``
|
||||
returns a coroutine that must be awaited by the prompt_toolkit event loop,
|
||||
which only exists on the main thread. Slash commands are dispatched from
|
||||
the ``process_loop`` daemon thread (see issue #23185), so calling
|
||||
``run_in_terminal`` from there orphans the coroutine — ``_ask`` never runs,
|
||||
and user keystrokes leak into the composer instead. Fall back to a direct
|
||||
``input()`` when we're off the main thread.
|
||||
"""
|
||||
import threading
|
||||
result = [None]
|
||||
|
||||
def _ask():
|
||||
|
|
@ -6017,13 +6027,23 @@ class HermesCLI:
|
|||
except (KeyboardInterrupt, EOFError):
|
||||
pass
|
||||
|
||||
if self._app:
|
||||
in_main_thread = threading.current_thread() is threading.main_thread()
|
||||
|
||||
if self._app and in_main_thread:
|
||||
from prompt_toolkit.application import run_in_terminal
|
||||
was_visible = self._status_bar_visible
|
||||
self._status_bar_visible = False
|
||||
self._app.invalidate()
|
||||
try:
|
||||
run_in_terminal(_ask)
|
||||
except Exception:
|
||||
# WSL / Warp / certain terminal emulators silently drop the
|
||||
# scheduled coroutine. Fall back to a direct input() so the
|
||||
# user's keystrokes don't leak into the agent buffer.
|
||||
try:
|
||||
_ask()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._status_bar_visible = was_visible
|
||||
self._app.invalidate()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue