Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions agent/coding_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import os
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional
Expand Down Expand Up @@ -97,6 +98,10 @@

_GIT_TIMEOUT = 2.5

# Win32 CREATE_NO_WINDOW — suppress the console flash when the windowless
# gateway shells out to git to build per-turn coding context. 0 elsewhere.
_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0


# Per-model edit-format steering. Matching the edit tool format to how a model
# was trained reduces mistakes and wasted reasoning (OpenAI/Codex handle
Expand Down Expand Up @@ -594,6 +599,7 @@ def _git(cwd: Path, *args: str) -> str:
capture_output=True,
text=True,
timeout=_GIT_TIMEOUT,
creationflags=_NO_WINDOW,
)
except (OSError, subprocess.SubprocessError):
return ""
Expand Down
7 changes: 7 additions & 0 deletions agent/context_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
import os
import re
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Awaitable, Callable

from agent.model_metadata import estimate_tokens_rough

# Win32 CREATE_NO_WINDOW — suppress the console flash when the windowless
# gateway shells out to git/rg to resolve @-file references. 0 elsewhere.
_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0

_QUOTED_REFERENCE_VALUE = r'(?:`[^`\n]+`|"[^"\n]+"|\'[^\'\n]+\')'
REFERENCE_PATTERN = re.compile(
rf"(?<![\w/])@(?:(?P<simple>diff|staged)\b|(?P<kind>file|folder|git|url):(?P<value>{_QUOTED_REFERENCE_VALUE}(?::\d+(?:-\d+)?)?|\S+))"
Expand Down Expand Up @@ -298,6 +303,7 @@ def _expand_git_reference(
text=True,
timeout=30,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
except subprocess.TimeoutExpired:
return f"{ref.raw}: git command timed out (30s)", None
Expand Down Expand Up @@ -491,6 +497,7 @@ def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:
text=True,
timeout=10,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
return None
Expand Down
8 changes: 8 additions & 0 deletions agent/lsp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,15 @@ async def _spawn(self) -> None:
env.update(self._env)

cmd = self._command
creationflags = 0
if sys.platform == "win32":
cmd = self._win_wrap_cmd(cmd)
# Suppress the cmd.exe console window that would otherwise
# flash every time we launch a ``.cmd``-wrapped language
# server (e.g. pyright-langserver.CMD). CREATE_NO_WINDOW.
from hermes_cli._subprocess_compat import windows_hide_flags

creationflags = windows_hide_flags()

try:
self._proc = await asyncio.create_subprocess_exec(
Expand All @@ -271,6 +278,7 @@ async def _spawn(self) -> None:
stderr=asyncio.subprocess.PIPE,
env=env,
cwd=self._cwd,
creationflags=creationflags,
)
except FileNotFoundError as e:
raise LSPProtocolError(
Expand Down
8 changes: 8 additions & 0 deletions agent/lsp/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@

logger = logging.getLogger("agent.lsp.install")

# Win32 CREATE_NO_WINDOW — suppress the console window that flashes when
# we shell out to npm/pip/go (all ``.cmd``/console apps on Windows) to
# auto-install a language server. 0 on non-Windows so the kwarg is inert.
_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0

# Package-name → install-strategy hint registry. Each entry is a
# tuple of strategy name + package name + executable name. When the
# install completes, we look for the executable in
Expand Down Expand Up @@ -263,6 +268,7 @@ def _install_npm(
text=True,
timeout=300,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
if proc.returncode != 0:
logger.warning(
Expand Down Expand Up @@ -312,6 +318,7 @@ def _install_go(pkg: str, bin_name: str) -> Optional[str]:
timeout=600,
env=env,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
if proc.returncode != 0:
logger.warning(
Expand Down Expand Up @@ -350,6 +357,7 @@ def _install_pip(pkg: str, bin_name: str) -> Optional[str]:
text=True,
timeout=300,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
if proc.returncode != 0:
logger.warning(
Expand Down
32 changes: 32 additions & 0 deletions agent/lsp/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
# folds collapse to one entry.
_workspace_cache: dict = {}

# Cache: (start_dir, markers, excludes, ceiling) → resolved root (or None).
# ``nearest_root`` is invoked ~5× per file write (via ``enabled_for``,
# ``_get_or_spawn``, ``_mark_broken_for_file``, and the typescript
# double-resolve), each time re-stat'ing up to ``markers × parents``
# paths. Memoizing collapses that to one walk per (dir, server-marker-set).
# Same staleness profile as ``_workspace_cache`` — cleared on shutdown /
# ``hermes lsp restart``.
_root_cache: dict = {}


def normalize_path(path: str) -> str:
"""Normalize a path for use as a stable map key.
Expand Down Expand Up @@ -140,6 +149,28 @@ def nearest_root(
markers_list = list(markers)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a regression test that proves a repeated identical lookup avoids a second filesystem walk and that clear_cache() restores lookup behavior. Current tests/agent/lsp/test_workspace.py covers root resolution but not this new cache contract.

excludes_list = list(excludes) if excludes else []

cache_key = (
str(start_path),
tuple(markers_list),
tuple(excludes_list),
str(ceiling_path) if ceiling_path is not None else None,
)
if cache_key in _root_cache:
return _root_cache[cache_key]

result = _nearest_root_uncached(
start_path, ceiling_path, markers_list, excludes_list
)
_root_cache[cache_key] = result
return result


def _nearest_root_uncached(
start_path: Path,
ceiling_path: Optional[Path],
markers_list: list,
excludes_list: list,
) -> Optional[str]:
cur = start_path
# Defensive cap matching ``find_git_worktree``. Bounded walk
# protects against pathological inputs even though the
Expand Down Expand Up @@ -211,6 +242,7 @@ def clear_cache() -> None:
up stale results from a previous session.
"""
_workspace_cache.clear()
_root_cache.clear()


__all__ = [
Expand Down
8 changes: 8 additions & 0 deletions tools/checkpoint_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
from hermes_constants import get_hermes_home
Expand All @@ -64,6 +65,11 @@

logger = logging.getLogger(__name__)

# Win32 CREATE_NO_WINDOW — suppress the console window that flashes when
# the windowless gateway (pythonw.exe) shells out to git for shadow
# checkpoints on every agent file edit. 0 on non-Windows (inert kwarg).
_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -308,6 +314,7 @@ def _run_git(
env=env,
cwd=str(normalized_working_dir),
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
ok = result.returncode == 0
stdout = result.stdout.strip()
Expand Down Expand Up @@ -428,6 +435,7 @@ def _init_store(store: Path, working_dir: str) -> Optional[str]:
capture_output=True, text=True,
env=init_env, timeout=_GIT_TIMEOUT,
stdin=subprocess.DEVNULL,
creationflags=_NO_WINDOW,
)
if result.returncode != 0:
return f"Shadow store init failed: {result.stderr.strip()}"
Expand Down