fix(tui): preserve remote cwd for ssh sessions

This commit is contained in:
zwcf5200 2026-06-04 10:57:58 +08:00 committed by Teknium
parent 89040e0db3
commit 0e0d704f2d
2 changed files with 91 additions and 11 deletions

View file

@ -101,7 +101,13 @@ class SSHEnvironment(BaseEnvironment):
cmd = self._build_ssh_command()
cmd.append("echo 'SSH connection established'")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=15,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
error_msg = result.stderr.strip() or result.stdout.strip()
raise RuntimeError(f"SSH connection failed: {error_msg}")
@ -113,7 +119,13 @@ class SSHEnvironment(BaseEnvironment):
try:
cmd = self._build_ssh_command()
cmd.append("echo $HOME")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=10,
stdin=subprocess.DEVNULL,
)
home = result.stdout.strip()
if home and result.returncode == 0:
logger.debug("SSH: remote home = %s", home)
@ -134,7 +146,13 @@ class SSHEnvironment(BaseEnvironment):
dirs = [base, f"{base}/skills", f"{base}/credentials", f"{base}/cache"]
cmd = self._build_ssh_command()
cmd.append(quoted_mkdir_command(dirs))
subprocess.run(cmd, capture_output=True, text=True, timeout=10)
subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=10,
stdin=subprocess.DEVNULL,
)
# _get_sync_files provided via iter_sync_files in FileSyncManager init
@ -143,7 +161,13 @@ class SSHEnvironment(BaseEnvironment):
parent = str(Path(remote_path).parent)
mkdir_cmd = self._build_ssh_command()
mkdir_cmd.append(f"mkdir -p {shlex.quote(parent)}")
subprocess.run(mkdir_cmd, capture_output=True, text=True, timeout=10)
subprocess.run(
mkdir_cmd,
capture_output=True,
text=True,
timeout=10,
stdin=subprocess.DEVNULL,
)
scp_cmd = ["scp", "-o", f"ControlPath={self.control_socket}"]
if self.port != 22:
@ -151,7 +175,13 @@ class SSHEnvironment(BaseEnvironment):
if self.key_path:
scp_cmd.extend(["-i", self.key_path])
scp_cmd.extend([host_path, f"{self.user}@{self.host}:{remote_path}"])
result = subprocess.run(scp_cmd, capture_output=True, text=True, timeout=30)
result = subprocess.run(
scp_cmd,
capture_output=True,
text=True,
timeout=30,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
raise RuntimeError(f"scp failed: {result.stderr.strip()}")
@ -174,7 +204,13 @@ class SSHEnvironment(BaseEnvironment):
if parents:
cmd = self._build_ssh_command()
cmd.append(quoted_mkdir_command(parents))
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
raise RuntimeError(f"remote mkdir failed: {result.stderr.strip()}")
@ -217,7 +253,10 @@ class SSHEnvironment(BaseEnvironment):
ssh_cmd.append(f"tar xf - --no-overwrite-dir -C {shlex.quote(base)}")
tar_proc = subprocess.Popen(
tar_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
tar_cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
try:
ssh_proc = subprocess.Popen(
@ -269,7 +308,13 @@ class SSHEnvironment(BaseEnvironment):
ssh_cmd = self._build_ssh_command()
ssh_cmd.append(f"tar cf - -C / {shlex.quote(rel_base)}")
with open(dest, "wb") as f:
result = subprocess.run(ssh_cmd, stdout=f, stderr=subprocess.PIPE, timeout=120)
result = subprocess.run(
ssh_cmd,
stdin=subprocess.DEVNULL,
stdout=f,
stderr=subprocess.PIPE,
timeout=120,
)
if result.returncode != 0:
raise RuntimeError(f"SSH bulk download failed: {result.stderr.decode(errors='replace').strip()}")
@ -277,7 +322,13 @@ class SSHEnvironment(BaseEnvironment):
"""Batch-delete remote files in one SSH call."""
cmd = self._build_ssh_command()
cmd.append(quoted_rm_command(remote_paths))
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=10,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
raise RuntimeError(f"remote rm failed: {result.stderr.strip()}")
@ -310,7 +361,12 @@ class SSHEnvironment(BaseEnvironment):
try:
cmd = ["ssh", "-o", f"ControlPath={self.control_socket}",
"-O", "exit", f"{self.user}@{self.host}"]
subprocess.run(cmd, capture_output=True, timeout=5)
subprocess.run(
cmd,
capture_output=True,
timeout=5,
stdin=subprocess.DEVNULL,
)
except (OSError, subprocess.SubprocessError):
pass
try:

View file

@ -813,6 +813,30 @@ def _completion_cwd(params: dict | None = None) -> str:
return os.getcwd()
def _terminal_task_cwd(session: dict | None) -> str:
"""Return the cwd that terminal_tool should use for this TUI session.
``_completion_cwd`` validates paths on the host so file completion does not
point at nonsense. Non-local terminal backends are different: their cwd is
inside the target environment, so an SSH path like /home/user/workspace may
not exist on the local macOS host but is still the correct execution cwd.
"""
backend = (os.environ.get("TERMINAL_ENV") or "").strip().lower()
if backend and backend != "local":
raw = os.environ.get("TERMINAL_CWD", "").strip()
if not raw:
try:
terminal_cfg = _load_cfg().get("terminal", {})
if isinstance(terminal_cfg, dict):
raw = str(terminal_cfg.get("cwd") or "").strip()
except Exception:
raw = ""
if raw and raw not in {".", "auto", "cwd"}:
return raw
return _session_cwd(session)
def _git_branch_for_cwd(cwd: str) -> str:
try:
result = subprocess.run(
@ -851,7 +875,7 @@ def _register_session_cwd(session: dict | None) -> None:
from tools.terminal_tool import register_task_env_overrides
register_task_env_overrides(
session["session_key"], {"cwd": _session_cwd(session)}
session["session_key"], {"cwd": _terminal_task_cwd(session)}
)
except Exception:
pass