mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
config.yaml terminal.cwd is now the single source of truth for working directory. MESSAGING_CWD and TERMINAL_CWD in .env are deprecated with a migration warning. Changes: 1. config.py: Remove MESSAGING_CWD from OPTIONAL_ENV_VARS (setup wizard no longer prompts for it). Add warn_deprecated_cwd_env_vars() that prints a migration hint when deprecated env vars are detected. 2. gateway/run.py: Replace all MESSAGING_CWD reads with TERMINAL_CWD (which is bridged from config.yaml terminal.cwd). MESSAGING_CWD is still accepted as a backward-compat fallback with deprecation warning. Config bridge skips cwd placeholder values so they don't clobber the resolved TERMINAL_CWD. 3. cli.py: Guard against lazy-import clobbering — when cli.py is imported lazily during gateway runtime (via delegate_tool), don't let load_cli_config() overwrite an already-resolved TERMINAL_CWD with os.getcwd() of the service's working directory. (#10817) 4. hermes_cli/main.py: Add 'hermes memory reset' command with --target all/memory/user and --yes flags. Profile-scoped via HERMES_HOME. Migration path for users with .env settings: Remove MESSAGING_CWD / TERMINAL_CWD from .env Add to config.yaml: terminal: cwd: /your/project/path Addresses: #10225, #4672, #10817, #7663
107 lines
4.4 KiB
Python
107 lines
4.4 KiB
Python
"""Tests that load_cli_config() guards against lazy-import TERMINAL_CWD clobbering.
|
|
|
|
When the gateway resolves TERMINAL_CWD at startup and cli.py is later
|
|
imported lazily (via delegate_tool → CLI_CONFIG), load_cli_config() must
|
|
not overwrite the already-resolved value with os.getcwd().
|
|
|
|
config.yaml terminal.cwd is the canonical source of truth.
|
|
.env TERMINAL_CWD and MESSAGING_CWD are deprecated.
|
|
See issue #10817.
|
|
"""
|
|
|
|
import os
|
|
import pytest
|
|
|
|
|
|
# The sentinel values that mean "resolve at runtime"
|
|
_CWD_PLACEHOLDERS = (".", "auto", "cwd")
|
|
|
|
|
|
def _resolve_terminal_cwd(terminal_config: dict, defaults: dict, env: dict):
|
|
"""Simulate the CWD resolution logic from load_cli_config().
|
|
|
|
This mirrors the code in cli.py that checks for a pre-resolved
|
|
TERMINAL_CWD before falling back to os.getcwd().
|
|
"""
|
|
if terminal_config.get("cwd") in _CWD_PLACEHOLDERS:
|
|
_existing_cwd = env.get("TERMINAL_CWD", "")
|
|
if _existing_cwd and _existing_cwd not in _CWD_PLACEHOLDERS and os.path.isabs(_existing_cwd):
|
|
terminal_config["cwd"] = _existing_cwd
|
|
defaults["terminal"]["cwd"] = _existing_cwd
|
|
else:
|
|
effective_backend = terminal_config.get("env_type", "local")
|
|
if effective_backend == "local":
|
|
terminal_config["cwd"] = "/fake/getcwd" # stand-in for os.getcwd()
|
|
defaults["terminal"]["cwd"] = terminal_config["cwd"]
|
|
else:
|
|
terminal_config.pop("cwd", None)
|
|
|
|
# Simulate the bridging loop: write terminal_config["cwd"] to env
|
|
_file_has_terminal = defaults.get("_file_has_terminal", False)
|
|
if "cwd" in terminal_config:
|
|
if _file_has_terminal or "TERMINAL_CWD" not in env:
|
|
env["TERMINAL_CWD"] = str(terminal_config["cwd"])
|
|
|
|
return env.get("TERMINAL_CWD", "")
|
|
|
|
|
|
class TestLazyImportGuard:
|
|
"""TERMINAL_CWD resolved by gateway must survive a lazy cli.py import."""
|
|
|
|
def test_gateway_resolved_cwd_survives(self):
|
|
"""Gateway set TERMINAL_CWD → lazy cli import must not clobber."""
|
|
env = {"TERMINAL_CWD": "/home/user/workspace"}
|
|
terminal_config = {"cwd": ".", "env_type": "local"}
|
|
defaults = {"terminal": {"cwd": "."}, "_file_has_terminal": False}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "/home/user/workspace"
|
|
|
|
def test_gateway_resolved_cwd_survives_with_file_terminal(self):
|
|
"""Even when config.yaml has a terminal: section, resolved CWD survives."""
|
|
env = {"TERMINAL_CWD": "/home/user/workspace"}
|
|
terminal_config = {"cwd": ".", "env_type": "local"}
|
|
defaults = {"terminal": {"cwd": "."}, "_file_has_terminal": True}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "/home/user/workspace"
|
|
|
|
|
|
class TestConfigCwdResolution:
|
|
"""config.yaml terminal.cwd is the canonical source of truth."""
|
|
|
|
def test_explicit_config_cwd_wins(self):
|
|
"""terminal.cwd: /explicit/path always wins."""
|
|
env = {"TERMINAL_CWD": "/old/gateway/value"}
|
|
terminal_config = {"cwd": "/explicit/path"}
|
|
defaults = {"terminal": {"cwd": "/explicit/path"}, "_file_has_terminal": True}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "/explicit/path"
|
|
|
|
def test_dot_cwd_resolves_to_getcwd_when_no_prior(self):
|
|
"""With no pre-set TERMINAL_CWD, "." resolves to os.getcwd()."""
|
|
env = {}
|
|
terminal_config = {"cwd": "."}
|
|
defaults = {"terminal": {"cwd": "."}, "_file_has_terminal": False}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "/fake/getcwd"
|
|
|
|
def test_remote_backend_pops_cwd(self):
|
|
"""Remote backend + placeholder cwd → popped for backend default."""
|
|
env = {}
|
|
terminal_config = {"cwd": ".", "env_type": "docker"}
|
|
defaults = {"terminal": {"cwd": "."}, "_file_has_terminal": False}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "" # cwd popped, no env var set
|
|
|
|
def test_remote_backend_with_prior_cwd_preserves(self):
|
|
"""Remote backend + pre-resolved TERMINAL_CWD → adopted."""
|
|
env = {"TERMINAL_CWD": "/project"}
|
|
terminal_config = {"cwd": ".", "env_type": "docker"}
|
|
defaults = {"terminal": {"cwd": "."}, "_file_has_terminal": False}
|
|
|
|
result = _resolve_terminal_cwd(terminal_config, defaults, env)
|
|
assert result == "/project"
|