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
27 changes: 23 additions & 4 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,28 @@ def _reload_runtime_env_preserving_config_authority() -> None:
if isinstance(agent_cfg, dict) and "max_turns" in agent_cfg:
os.environ["HERMES_MAX_ITERATIONS"] = str(agent_cfg["max_turns"])

# Re-bridge terminal.* so long-lived gateways pick up config changes on
# /new or /reset without needing a full restart. Placeholder cwd values
# mean "use the startup fallback"; clear any stale explicit bridge first
# so explicit -> placeholder updates do not keep the old TERMINAL_CWD.
terminal_cfg = cfg.get("terminal")
if isinstance(terminal_cfg, dict):
raw_cwd = str(terminal_cfg.get("cwd", "")).strip()
if "cwd" not in terminal_cfg or raw_cwd in {".", "auto", "cwd"}:
os.environ.pop("TERMINAL_CWD", None)
else:
os.environ.pop("TERMINAL_CWD", None)
from hermes_cli.config import apply_terminal_config_to_env
apply_terminal_config_to_env(config=cfg, override=True)
_apply_terminal_cwd_fallback()


def _apply_terminal_cwd_fallback() -> None:
configured_cwd = os.environ.get("TERMINAL_CWD", "")
if not configured_cwd or configured_cwd in {".", "auto", "cwd"}:
fallback = os.getenv("MESSAGING_CWD") or str(Path.home())
os.environ["TERMINAL_CWD"] = fallback


_DOCKER_VOLUME_SPEC_RE = re.compile(r"^(?P<host>.+):(?P<container>/[^:]+?)(?::(?P<options>[^:]+))?$")
_DOCKER_MEDIA_OUTPUT_CONTAINER_PATHS = {"/output", "/outputs"}
Expand Down Expand Up @@ -1221,10 +1243,7 @@ def _reload_runtime_env_preserving_config_authority() -> None:
# by the config bridge above). When it's unset or a placeholder, default
# to home directory. MESSAGING_CWD is accepted as a backward-compat
# fallback (deprecated — the warning above tells users to migrate).
_configured_cwd = os.environ.get("TERMINAL_CWD", "")
if not _configured_cwd or _configured_cwd in {".", "auto", "cwd"}:
_fallback = os.getenv("MESSAGING_CWD") or str(Path.home())
os.environ["TERMINAL_CWD"] = _fallback
_apply_terminal_cwd_fallback()

from gateway.config import (
Platform,
Expand Down
52 changes: 52 additions & 0 deletions tests/gateway/test_config_cwd_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import os
import json

from hermes_cli.config import apply_terminal_config_to_env


def _simulate_config_bridge(cfg: dict, initial_env: dict | None = None):
"""Simulate the gateway config bridge logic from gateway/run.py.
Expand Down Expand Up @@ -75,6 +77,27 @@ def _simulate_config_bridge(cfg: dict, initial_env: dict | None = None):
return env


def _apply_terminal_cwd_fallback(env: dict):
configured_cwd = env.get("TERMINAL_CWD", "")
if not configured_cwd or configured_cwd in {".", "auto", "cwd"}:
env["TERMINAL_CWD"] = env.get("MESSAGING_CWD") or "/root"


def _simulate_runtime_env_reload(cfg: dict, initial_env: dict | None = None):
"""Simulate _reload_runtime_env_preserving_config_authority cwd handling."""
env = dict(initial_env or {})
terminal_cfg = cfg.get("terminal")
if isinstance(terminal_cfg, dict):
raw_cwd = str(terminal_cfg.get("cwd", "")).strip()
if "cwd" not in terminal_cfg or raw_cwd in {".", "auto", "cwd"}:
env.pop("TERMINAL_CWD", None)
else:
env.pop("TERMINAL_CWD", None)
apply_terminal_config_to_env(env=env, config=cfg, override=True)
_apply_terminal_cwd_fallback(env)
return env


class TestTopLevelCwdAlias:
"""Top-level `cwd:` should be treated as `terminal.cwd`."""

Expand Down Expand Up @@ -243,3 +266,32 @@ def test_tilde_with_nested_precedence(self):
}
result = _simulate_config_bridge(cfg)
assert result["TERMINAL_CWD"] == os.path.expanduser("~/nested")


class TestRuntimeReloadTerminalCwd:
"""Runtime config reload should match startup terminal.cwd fallback rules."""

def test_runtime_reload_updates_explicit_terminal_cwd(self):
cfg = {"terminal": {"cwd": "/new/project", "backend": "docker"}}
result = _simulate_runtime_env_reload(
cfg,
{"TERMINAL_CWD": "/old/project", "TERMINAL_ENV": "local"},
)
assert result["TERMINAL_CWD"] == "/new/project"
assert result["TERMINAL_ENV"] == "docker"

def test_runtime_reload_placeholder_clears_stale_terminal_cwd(self):
cfg = {"terminal": {"cwd": "."}}
result = _simulate_runtime_env_reload(
cfg,
{"TERMINAL_CWD": "/old/project", "MESSAGING_CWD": "/fallback/project"},
)
assert result["TERMINAL_CWD"] == "/fallback/project"

def test_runtime_reload_missing_cwd_clears_stale_terminal_cwd(self):
cfg = {"terminal": {"backend": "local"}}
result = _simulate_runtime_env_reload(
cfg,
{"TERMINAL_CWD": "/old/project", "MESSAGING_CWD": "/fallback/project"},
)
assert result["TERMINAL_CWD"] == "/fallback/project"