mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(tui_gateway): honor launch profile terminal.cwd for dashboard chat
Dashboard /chat for the default (launch) profile attaches to the dashboard process's in-memory TUI gateway. The Node PTY child receives a bridged TERMINAL_CWD env var, but the in-memory gateway process does not, so cwd resolution fell through to os.getcwd() (wherever `hermes dashboard` was launched) and ignored the configured terminal.cwd. Read the launch profile's config.yaml directly in the in-memory cwd resolution: a configured terminal.cwd now wins over a stale process env and the launch directory. Widened to the resume/fallback session-cwd sites (not just _completion_cwd) via a shared _default_session_cwd() helper so fresh AND resumed sessions honor the config. Co-authored-by: ygd58 <buraysandro9@gmail.com>
This commit is contained in:
parent
83f14b2f21
commit
f3af7930c2
2 changed files with 97 additions and 9 deletions
|
|
@ -188,13 +188,54 @@ def test_completion_cwd_prefers_profile_over_stale_env(monkeypatch, tmp_path):
|
|||
stale.mkdir()
|
||||
|
||||
monkeypatch.setenv("TERMINAL_CWD", str(stale))
|
||||
monkeypatch.setattr(server, "_load_cfg", lambda: {})
|
||||
monkeypatch.setattr(server, "_profile_home", lambda name: home if name else None)
|
||||
|
||||
assert server._completion_cwd({"profile": "ef-design"}) == str(profile_b)
|
||||
# No profile → unchanged fallback to the launch env var.
|
||||
# No profile and no launch config → fallback to the launch env var.
|
||||
assert server._completion_cwd({}) == str(stale)
|
||||
|
||||
|
||||
def test_completion_cwd_prefers_launch_config_over_stale_env(monkeypatch, tmp_path):
|
||||
"""Dashboard /chat's launch-profile in-memory gateway must honor config.
|
||||
|
||||
The embedded Node TUI child gets TERMINAL_CWD from the dashboard PTY bridge,
|
||||
but the default-profile chat attaches to the dashboard process's already
|
||||
running in-memory gateway. That process may not have TERMINAL_CWD in its own
|
||||
environment (or has a stale one), so config.yaml is read directly and wins
|
||||
over the process env before falling back to the launch directory.
|
||||
"""
|
||||
configured = tmp_path / "omni"
|
||||
configured.mkdir()
|
||||
stale = tmp_path / "hermes-agent"
|
||||
stale.mkdir()
|
||||
|
||||
monkeypatch.setenv("TERMINAL_CWD", str(stale))
|
||||
monkeypatch.setattr(server, "_load_cfg", lambda: {"terminal": {"cwd": str(configured)}})
|
||||
monkeypatch.setattr(server, "_profile_home", lambda _name: None)
|
||||
|
||||
assert server._completion_cwd({}) == str(configured)
|
||||
|
||||
|
||||
def test_default_session_cwd_prefers_launch_config(monkeypatch, tmp_path):
|
||||
"""A freshly created / resumed session with no explicit cwd lands in the
|
||||
configured terminal.cwd, not os.getcwd(), even when the in-memory gateway
|
||||
process env carries a stale TERMINAL_CWD."""
|
||||
configured = tmp_path / "workspace"
|
||||
configured.mkdir()
|
||||
stale = tmp_path / "launch-dir"
|
||||
stale.mkdir()
|
||||
|
||||
monkeypatch.setenv("TERMINAL_CWD", str(stale))
|
||||
monkeypatch.setattr(server, "_load_cfg", lambda: {"terminal": {"cwd": str(configured)}})
|
||||
|
||||
assert server._default_session_cwd() == str(configured)
|
||||
|
||||
# No launch config → fall back to the process env var.
|
||||
monkeypatch.setattr(server, "_load_cfg", lambda: {})
|
||||
assert server._default_session_cwd() == str(stale)
|
||||
|
||||
|
||||
def test_completion_cwd_explicit_cwd_wins_over_profile(monkeypatch, tmp_path):
|
||||
"""An explicit client-provided cwd still beats the profile config."""
|
||||
explicit = tmp_path / "explicit"
|
||||
|
|
|
|||
|
|
@ -955,6 +955,24 @@ def _profile_scoped(handler):
|
|||
_CWD_PLACEHOLDERS = {".", "auto", "cwd"}
|
||||
|
||||
|
||||
def _configured_cwd_from_cfg(cfg: dict | None) -> str | None:
|
||||
"""Return an absolute, existing ``terminal.cwd`` from a config mapping.
|
||||
|
||||
Returns None for placeholders (``.``/``auto``/``cwd``), missing values, or
|
||||
paths that don't resolve to a real directory.
|
||||
"""
|
||||
if not isinstance(cfg, dict):
|
||||
return None
|
||||
terminal_cfg = cfg.get("terminal")
|
||||
if not isinstance(terminal_cfg, dict):
|
||||
return None
|
||||
raw = str(terminal_cfg.get("cwd") or "").strip()
|
||||
if not raw or raw in _CWD_PLACEHOLDERS:
|
||||
return None
|
||||
resolved = os.path.abspath(os.path.expanduser(raw))
|
||||
return resolved if os.path.isdir(resolved) else None
|
||||
|
||||
|
||||
def _profile_configured_cwd(profile_home: Path | None) -> str | None:
|
||||
"""Resolve a non-launch profile's ``terminal.cwd`` from its own config.yaml.
|
||||
|
||||
|
|
@ -974,15 +992,39 @@ def _profile_configured_cwd(profile_home: Path | None) -> str | None:
|
|||
return None
|
||||
with open(p, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
raw = str((data.get("terminal") or {}).get("cwd") or "").strip()
|
||||
if not raw or raw in _CWD_PLACEHOLDERS:
|
||||
return None
|
||||
resolved = os.path.abspath(os.path.expanduser(raw))
|
||||
return resolved if os.path.isdir(resolved) else None
|
||||
return _configured_cwd_from_cfg(data)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _launch_configured_cwd() -> str | None:
|
||||
"""Resolve the launch profile's ``terminal.cwd`` from config.yaml.
|
||||
|
||||
Dashboard ``/chat`` for the launch profile attaches to the dashboard
|
||||
process's in-memory TUI gateway. The Node PTY child receives a bridged
|
||||
``TERMINAL_CWD`` env var, but this in-memory process does not — so reading
|
||||
the process env alone leaves a fresh chat starting in ``os.getcwd()``
|
||||
(wherever ``hermes dashboard`` was launched) instead of the configured
|
||||
``terminal.cwd``. Read config directly so changing ``terminal.cwd`` affects
|
||||
new in-memory TUI sessions too.
|
||||
"""
|
||||
try:
|
||||
return _configured_cwd_from_cfg(_load_cfg())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _default_session_cwd() -> str:
|
||||
"""Fallback cwd for a session with no explicit / stored / profile cwd.
|
||||
|
||||
Mirrors the launch-config-aware tail of :func:`_completion_cwd` so freshly
|
||||
created AND resumed sessions land in the configured ``terminal.cwd`` rather
|
||||
than ``os.getcwd()`` when the in-memory gateway's process env has no bridged
|
||||
``TERMINAL_CWD``.
|
||||
"""
|
||||
return _launch_configured_cwd() or os.getenv("TERMINAL_CWD") or os.getcwd()
|
||||
|
||||
|
||||
def write_json(obj: dict) -> bool:
|
||||
"""Emit one JSON frame. Routes via the most-specific transport available.
|
||||
|
||||
|
|
@ -1373,6 +1415,11 @@ def _completion_cwd(params: dict | None = None) -> str:
|
|||
# A session bound to another profile resolves its workspace from THAT
|
||||
# profile's config before falling back to the launch profile's env var.
|
||||
or _profile_configured_cwd(_profile_home(params.get("profile")))
|
||||
# The launch profile's dashboard /chat attaches to the dashboard's
|
||||
# in-memory gateway, which does NOT inherit the PTY child's bridged
|
||||
# TERMINAL_CWD. Read the launch profile's config.yaml directly so a
|
||||
# configured terminal.cwd wins over a stale process env / launch dir.
|
||||
or _launch_configured_cwd()
|
||||
or os.environ.get("TERMINAL_CWD")
|
||||
or os.getcwd()
|
||||
)
|
||||
|
|
@ -5401,7 +5448,7 @@ def _(rid, params: dict) -> dict:
|
|||
if lease is not None:
|
||||
lease.release()
|
||||
return _err(rid, 5000, f"resume failed: {e}")
|
||||
cwd = profile_resume_cwd or os.getenv("TERMINAL_CWD", os.getcwd())
|
||||
cwd = profile_resume_cwd or _default_session_cwd()
|
||||
record = _deferred_session_record(
|
||||
target,
|
||||
cols=cols,
|
||||
|
|
@ -5474,7 +5521,7 @@ def _(rid, params: dict) -> dict:
|
|||
# the build drops the provider ("No LLM provider configured").
|
||||
overrides = _stored_session_runtime_overrides(found) or {}
|
||||
model_override = overrides.get("model_override") or {}
|
||||
cwd = profile_resume_cwd or os.getenv("TERMINAL_CWD", os.getcwd())
|
||||
cwd = profile_resume_cwd or _default_session_cwd()
|
||||
record = _deferred_session_record(
|
||||
target,
|
||||
cols=cols,
|
||||
|
|
@ -5760,7 +5807,7 @@ def _fallback_session_info(session: dict) -> dict:
|
|||
if agent is not None:
|
||||
return _session_info(agent)
|
||||
return {
|
||||
"cwd": os.getenv("TERMINAL_CWD", os.getcwd()),
|
||||
"cwd": _default_session_cwd(),
|
||||
"lazy": True,
|
||||
"model": _resolve_model(),
|
||||
"skills": {},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue