fix(tui_gateway): honor target profile's terminal.cwd on desktop profile switch (#40892)

* fix(tui_gateway): honor target profile's terminal.cwd on desktop profile switch

The desktop's app-global remote mode serves every profile from one
tui_gateway backend, so the process-global TERMINAL_CWD only reflects the
launch profile. After switching profiles, a new session resolved its
workspace from that stale env var and inherited the previous profile's
directory.

Add _profile_configured_cwd() to read a non-launch profile's own
terminal.cwd from its config.yaml (skipping placeholder/empty/missing and
non-existent paths so callers fall back cleanly), and wire it into
_completion_cwd() with precedence: explicit client cwd -> existing session
cwd -> bound profile's configured cwd -> TERMINAL_CWD -> os.getcwd().

Fixes #40334

* test(tui_gateway): cover per-profile cwd resolution (#40334)

Pin the new contract: _profile_configured_cwd reads a profile's own
terminal.cwd and rejects placeholders/missing paths, and _completion_cwd
prefers a bound profile's cwd over a stale launch-profile TERMINAL_CWD
while still letting an explicit client cwd win.
This commit is contained in:
xxxigm
2026-06-09 19:45:29 -05:00
committed by GitHub
parent 59ea2f98e6
commit 93340fa3c1
2 changed files with 98 additions and 2 deletions
+40 -2
View File
@@ -688,6 +688,40 @@ def _profile_home(profile: str | None) -> Path | None:
return home if (home / "state.db").exists() or home.exists() else None
# Placeholder ``terminal.cwd`` values that don't name a real directory — the
# gateway resolves these to the home dir at runtime, so they must NOT be treated
# as an explicit workspace (mirrors gateway/run.py's config bridge).
_CWD_PLACEHOLDERS = {".", "auto", "cwd"}
def _profile_configured_cwd(profile_home: Path | None) -> str | None:
"""Resolve a non-launch profile's ``terminal.cwd`` from its own config.yaml.
The desktop's app-global remote mode serves every profile from one backend,
so the process-global ``TERMINAL_CWD`` belongs to the *launch* profile. A new
session bound to another profile must take its workspace from THAT profile's
config, not the stale env var (issue #40334). Returns an absolute, existing
directory, or None for placeholders / missing / invalid paths.
"""
if profile_home is None:
return None
try:
import yaml
p = Path(profile_home) / "config.yaml"
if not p.exists():
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
except Exception:
return None
def write_json(obj: dict) -> bool:
"""Emit one JSON frame. Routes via the most-specific transport available.
@@ -995,9 +1029,13 @@ def _normalize_completion_path(path_part: str) -> str:
def _completion_cwd(params: dict | None = None) -> str:
params = params or {}
raw = (
(params or {}).get("cwd")
or _sessions.get((params or {}).get("session_id") or "", {}).get("cwd")
params.get("cwd")
or _sessions.get(params.get("session_id") or "", {}).get("cwd")
# 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")))
or os.environ.get("TERMINAL_CWD")
or os.getcwd()
)