From f48b3120375d81733ed15fd16e2a606f19081923 Mon Sep 17 00:00:00 2001 From: xxxigm Date: Wed, 17 Jun 2026 09:21:24 +0700 Subject: [PATCH] fix(cli): keep typing responsive by not blocking the keystroke loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interactive CLI input box runs its completer with `complete_while_typing=True`, so `SlashCommandCompleter.get_completions` is invoked on *every* keystroke. That completer does blocking I/O: fuzzy `@`-file indexing shells out to `rg`/`fd` (up to a 2s timeout) and file-path completion calls `os.listdir` + `stat`. Because the completer was passed inline (never wrapped in `ThreadedCompleter`), all of this ran synchronously on the prompt_toolkit event loop, stalling the render after each key — very noticeable on WSL2 and other slow-filesystem setups ("typing in the prompt box being very latent"). Two fixes: - Wrap the input completer in `ThreadedCompleter` so completion work runs off the UI event loop and never blocks rendering between keystrokes. - Stop treating URLs as file paths in `_extract_path_word`: a token like `https://example.com/x` contains `/`, so it triggered `os.listdir` on every keystroke while typing/pasting a link (listing a bogus `https:` dir) for a completion that can never be useful. Skip any token with a `://` scheme separator. (cherry picked from commit b5be2ba276c29cc12fce1d1a580bc782cd557353) --- cli.py | 9 ++++++++- hermes_cli/commands.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cli.py b/cli.py index bc4f4a76bef..4ca07fa0bf5 100644 --- a/cli.py +++ b/cli.py @@ -12024,6 +12024,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): # Create the input area with multiline (Alt+Enter), autocomplete, and paste handling from prompt_toolkit.auto_suggest import AutoSuggestFromHistory + from prompt_toolkit.completion import ThreadedCompleter _completer = SlashCommandCompleter( @@ -12039,7 +12040,13 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): wrap_lines=True, read_only=Condition(lambda: bool(cli_ref._command_running)), history=FileHistory(str(self._history_file)), - completer=_completer, + # complete_while_typing fires the completer on every keystroke. The + # completer does blocking work — fuzzy @-file indexing shells out to + # rg/fd (up to a 2s timeout) and path completion hits os.listdir/stat + # — so running it inline would stall the render loop on each key (very + # noticeable on WSL2/slow filesystems). ThreadedCompleter moves it off + # the UI event loop, keeping typing responsive. + completer=ThreadedCompleter(_completer), complete_while_typing=True, auto_suggest=SlashCommandAutoSuggest( history_suggest=AutoSuggestFromHistory(), diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index a1e20dabc08..ed6f83f39e6 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -1291,6 +1291,12 @@ class SlashCommandCompleter(Completer): word = text[i + 1:] if not word: return None + # URLs contain "/" but are not local paths. Treating them as paths fires + # os.listdir on every keystroke while typing/pasting a link (e.g. an + # https:// URL becomes a listdir of "https:") — pure latency, never a + # useful completion. Skip any token with a scheme separator. + if "://" in word: + return None # Only trigger path completion for path-like tokens if word.startswith(("./", "../", "~/", "/")) or "/" in word: return word