fix(gateway-windows): anchor detached/startup cwd at HERMES_HOME
This commit is contained in:
@ -308,6 +308,29 @@ def get_startup_entry_path() -> Path:
|
||||
return _startup_dir() / f"{_sanitize_filename(get_task_name())}.cmd"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stable working directory
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _stable_gateway_working_dir(project_root: Path) -> str:
|
||||
"""Return a stable cwd for detached/startup gateway runs.
|
||||
|
||||
Mirror the POSIX service invariant: anchor at ``HERMES_HOME`` whenever it
|
||||
exists so Scheduled Task / Startup launches do not fail at the ``cd`` step
|
||||
after a transient checkout or worktree is moved away. Fall back to the
|
||||
source checkout only if ``HERMES_HOME`` cannot be resolved yet.
|
||||
"""
|
||||
from hermes_cli.config import get_hermes_home
|
||||
|
||||
try:
|
||||
home = get_hermes_home()
|
||||
if home and Path(home).is_dir():
|
||||
return str(Path(home).resolve())
|
||||
except Exception:
|
||||
pass
|
||||
return str(project_root)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Script rendering
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -321,7 +344,7 @@ def _build_gateway_cmd_script(
|
||||
"""Build the ``gateway.cmd`` wrapper content (CRLF-terminated).
|
||||
|
||||
The script:
|
||||
- cd's into the project directory
|
||||
- cd's into a stable working directory
|
||||
- exports HERMES_HOME, PYTHONIOENCODING, VIRTUAL_ENV
|
||||
- invokes ``pythonw -m hermes_cli.main [--profile X] gateway run``
|
||||
directly so the wrapper cmd.exe exits without a visible gateway console
|
||||
@ -380,7 +403,7 @@ def _write_task_script() -> Path:
|
||||
)
|
||||
|
||||
python_path = get_python_path()
|
||||
working_dir = str(PROJECT_ROOT)
|
||||
working_dir = _stable_gateway_working_dir(PROJECT_ROOT)
|
||||
hermes_home = str(Path(get_hermes_home()).resolve())
|
||||
profile_arg = _profile_arg(hermes_home)
|
||||
|
||||
@ -547,7 +570,8 @@ def _build_gateway_argv() -> tuple[list[str], str, dict[str, str]]:
|
||||
)
|
||||
|
||||
python_exe, venv_dir, extra_pythonpath = _resolve_detached_python(get_python_path())
|
||||
working_dir = str(PROJECT_ROOT)
|
||||
project_root = str(PROJECT_ROOT)
|
||||
working_dir = _stable_gateway_working_dir(PROJECT_ROOT)
|
||||
hermes_home = str(Path(get_hermes_home()).resolve())
|
||||
profile_arg = _profile_arg(hermes_home)
|
||||
|
||||
@ -562,7 +586,7 @@ def _build_gateway_argv() -> tuple[list[str], str, dict[str, str]]:
|
||||
"HERMES_GATEWAY_DETACHED": "1",
|
||||
"VIRTUAL_ENV": str(venv_dir),
|
||||
}
|
||||
_prepend_pythonpath(env_overlay, [working_dir, *extra_pythonpath] if extra_pythonpath else [])
|
||||
_prepend_pythonpath(env_overlay, [project_root, *extra_pythonpath] if extra_pythonpath else [project_root])
|
||||
return argv, working_dir, env_overlay
|
||||
|
||||
|
||||
|
||||
@ -78,9 +78,11 @@ def test_build_gateway_argv_uses_base_pythonw_for_uv_venv_launcher(monkeypatch,
|
||||
project = tmp_path / "project"
|
||||
scripts = project / "venv" / "Scripts"
|
||||
site_packages = project / "venv" / "Lib" / "site-packages"
|
||||
hermes_home = tmp_path / "hermes-home"
|
||||
base = tmp_path / "uv" / "python" / "cpython-3.11-windows-x86_64-none"
|
||||
scripts.mkdir(parents=True)
|
||||
site_packages.mkdir(parents=True)
|
||||
hermes_home.mkdir()
|
||||
base.mkdir(parents=True)
|
||||
|
||||
venv_python = scripts / "python.exe"
|
||||
@ -99,17 +101,55 @@ def test_build_gateway_argv_uses_base_pythonw_for_uv_venv_launcher(monkeypatch,
|
||||
monkeypatch.setattr(gateway, "PROJECT_ROOT", project)
|
||||
monkeypatch.setattr(gateway, "get_python_path", lambda: str(venv_python))
|
||||
monkeypatch.setattr(gateway, "_profile_arg", lambda hermes_home: "")
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: str(tmp_path / "hermes-home"))
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: str(hermes_home))
|
||||
|
||||
argv, cwd, env_overlay = gateway_windows._build_gateway_argv()
|
||||
|
||||
assert argv[:3] == [str(base_pythonw), "-m", "hermes_cli.main"]
|
||||
assert cwd == str(project)
|
||||
assert cwd == str(hermes_home.resolve())
|
||||
assert env_overlay["VIRTUAL_ENV"] == str(project / "venv")
|
||||
assert str(project) in env_overlay["PYTHONPATH"].split(gateway_windows.os.pathsep)
|
||||
assert str(site_packages) in env_overlay["PYTHONPATH"].split(gateway_windows.os.pathsep)
|
||||
|
||||
|
||||
class TestStableWindowsGatewayWorkingDir:
|
||||
def test_stable_gateway_working_dir_uses_hermes_home(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: home)
|
||||
assert gateway_windows._stable_gateway_working_dir(tmp_path / "checkout") == str(home.resolve())
|
||||
|
||||
def test_stable_gateway_working_dir_falls_back_to_project_root(self, tmp_path, monkeypatch):
|
||||
missing = tmp_path / "missing" / ".hermes"
|
||||
project = tmp_path / "checkout"
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: missing)
|
||||
assert gateway_windows._stable_gateway_working_dir(project) == str(project)
|
||||
|
||||
|
||||
def test_write_task_script_anchors_cmd_cd_at_hermes_home(monkeypatch, tmp_path):
|
||||
project = tmp_path / "project"
|
||||
hermes_home = tmp_path / "hermes-home"
|
||||
hermes_home.mkdir()
|
||||
python_exe = project / "venv" / "Scripts" / "python.exe"
|
||||
python_exe.parent.mkdir(parents=True)
|
||||
python_exe.write_text("", encoding="utf-8")
|
||||
script_path = tmp_path / "gateway.cmd"
|
||||
|
||||
monkeypatch.setattr(gateway_windows, "_assert_windows", lambda: None)
|
||||
monkeypatch.setattr(gateway, "PROJECT_ROOT", project)
|
||||
monkeypatch.setattr(gateway, "get_python_path", lambda: str(python_exe))
|
||||
monkeypatch.setattr(gateway, "_profile_arg", lambda hermes_home: "")
|
||||
monkeypatch.setattr("hermes_cli.config.get_hermes_home", lambda: str(hermes_home))
|
||||
monkeypatch.setattr(gateway_windows, "get_task_script_path", lambda: script_path)
|
||||
|
||||
written = gateway_windows._write_task_script()
|
||||
content = script_path.read_text(encoding="utf-8")
|
||||
|
||||
assert written == script_path
|
||||
assert f"cd /d {gateway_windows._quote_cmd_script_arg(str(hermes_home.resolve()))}" in content
|
||||
assert f"cd /d {gateway_windows._quote_cmd_script_arg(str(project))}" not in content
|
||||
|
||||
|
||||
def _arrange_startup_fallback(monkeypatch, tmp_path, running_pids):
|
||||
script_path = tmp_path / "Hermes_Gateway_alice.cmd"
|
||||
startup_entry = tmp_path / "Startup" / "Hermes_Gateway_alice.cmd"
|
||||
|
||||
Reference in New Issue
Block a user