From f48b3120375d81733ed15fd16e2a606f19081923 Mon Sep 17 00:00:00 2001 From: xxxigm Date: Wed, 17 Jun 2026 09:21:24 +0700 Subject: [PATCH 1/3] 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 From fbaad3031abe74a14dc02569eb73d003f23120bb Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:33:56 +0530 Subject: [PATCH 2/3] test(cli): URL tokens must not trigger filesystem path completion Regression coverage for the keystroke-latency fix: a URL token contains "/", so the bare-slash path heuristic used to return it as a path word and run os.listdir on every keystroke. Assert _extract_path_word rejects http/https/ssh scheme tokens, that ordinary paths (incl. a bare colon) are unaffected, and that the completer never touches the filesystem for a URL under the cursor. --- tests/hermes_cli/test_path_completion.py | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/hermes_cli/test_path_completion.py b/tests/hermes_cli/test_path_completion.py index b41a36e2ec6..549d8d6a2c1 100644 --- a/tests/hermes_cli/test_path_completion.py +++ b/tests/hermes_cli/test_path_completion.py @@ -59,6 +59,32 @@ class TestExtractPathWord: def test_just_tilde_slash(self): assert SlashCommandCompleter._extract_path_word("~/") == "~/" + def test_url_is_not_treated_as_path(self): + # A URL contains "/" so the bare slash heuristic would otherwise return + # it as a path word, firing os.listdir("https:") on every keystroke. + assert SlashCommandCompleter._extract_path_word("see https://paste.rs/abc") is None + + def test_http_url_is_not_treated_as_path(self): + assert SlashCommandCompleter._extract_path_word("ref http://example.com/x") is None + + def test_scheme_alone_is_enough_to_reject(self): + # The "://" scheme separator is the signal, even before any path part + # has been typed. + assert SlashCommandCompleter._extract_path_word("ssh://host") is None + + def test_path_word_with_colon_but_no_scheme_still_resolves(self): + # Only the "://" scheme separator should reject; a bare colon inside a + # real path token must not regress path detection. + assert ( + SlashCommandCompleter._extract_path_word("open ./a:b/c.py") == "./a:b/c.py" + ) + + def test_ordinary_path_unaffected_by_url_guard(self): + assert ( + SlashCommandCompleter._extract_path_word("edit src/pkg/mod.py") + == "src/pkg/mod.py" + ) + class TestPathCompletions: def test_lists_current_directory(self, tmp_path): @@ -155,6 +181,23 @@ class TestIntegration: completions = list(completer.get_completions(doc, event)) assert completions == [] + def test_url_does_not_touch_filesystem(self, completer, monkeypatch): + # Regression for laggy typing: a URL token contains "/", so before the + # scheme guard it reached _path_completions and called os.listdir on + # every keystroke. Assert no completions AND that the filesystem is + # never touched while a URL is under the cursor. + import hermes_cli.commands as commands_mod + + def _fail(*_args, **_kwargs): + raise AssertionError("os.listdir must not run for a URL token") + + monkeypatch.setattr(commands_mod.os, "listdir", _fail) + + text = "open https://paste.rs/abc" + doc = Document(text, cursor_position=len(text)) + event = MagicMock() + assert list(completer.get_completions(doc, event)) == [] + def test_absolute_path_triggers_completion(self, completer): doc = Document("check /etc/hos", cursor_position=14) event = MagicMock() From ca6542f602b01abba41f0a0ea5e1213e1acaa9f4 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:36:01 +0530 Subject: [PATCH 3/3] docs(cli): note URL exclusion in _extract_path_word docstring The docstring described a token as path-like when it contains a "/" separator, but the keystroke-latency fix now excludes "://" scheme tokens (URLs) even though they contain "/". Document the exclusion so the contract matches the behavior. --- hermes_cli/commands.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index ed6f83f39e6..f81d50eace9 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -1280,6 +1280,10 @@ class SlashCommandCompleter(Completer): current word doesn't look like a path. A word is path-like when it starts with ``./``, ``../``, ``~/``, ``/``, or contains a ``/`` separator (e.g. ``src/main.py``). + + Tokens containing a ``://`` scheme separator (e.g. URLs like + ``https://example.com/x``) are excluded even though they contain a + ``/`` — they are never useful local-path completions. """ if not text: return None