fix(execute-code): honor session cwd overrides
parent
298a94926f
commit
4d6686c18a
|
|
@ -220,6 +220,17 @@ class TestResolveChildCwd(unittest.TestCase):
|
||||||
with patch.dict(os.environ, {"TERMINAL_CWD": "~"}):
|
with patch.dict(os.environ, {"TERMINAL_CWD": "~"}):
|
||||||
self.assertEqual(_resolve_child_cwd("project", "/tmp/staging"), home)
|
self.assertEqual(_resolve_child_cwd("project", "/tmp/staging"), home)
|
||||||
|
|
||||||
|
def test_project_prefers_registered_task_cwd_override(self):
|
||||||
|
import tempfile
|
||||||
|
import tools.terminal_tool as terminal_tool
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
task_id = "session-cwd-test"
|
||||||
|
with patch.dict(os.environ, {"TERMINAL_CWD": "/does/not/exist"}):
|
||||||
|
with patch.object(terminal_tool, "_task_env_overrides", {}, create=False):
|
||||||
|
terminal_tool.register_task_env_overrides(task_id, {"cwd": td})
|
||||||
|
self.assertEqual(_resolve_child_cwd("project", "/tmp/staging", task_id=task_id), td)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Schema description
|
# Schema description
|
||||||
|
|
@ -316,6 +327,28 @@ class TestExecuteCodeModeIntegration(unittest.TestCase):
|
||||||
os.path.realpath(td),
|
os.path.realpath(td),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_project_mode_uses_registered_session_cwd_override(self):
|
||||||
|
"""Project mode must honor session.cwd.set-style overrides even when
|
||||||
|
TERMINAL_CWD is absent or points elsewhere."""
|
||||||
|
import tempfile
|
||||||
|
import tools.terminal_tool as terminal_tool
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
task_id = "session-cwd-test"
|
||||||
|
with patch.dict(os.environ, {"TERMINAL_CWD": "/does/not/exist"}):
|
||||||
|
with patch.object(terminal_tool, "_task_env_overrides", {}, create=False):
|
||||||
|
terminal_tool.register_task_env_overrides(task_id, {"cwd": td})
|
||||||
|
with _mock_mode("project"):
|
||||||
|
with patch("model_tools.handle_function_call", side_effect=_mock_handle_function_call):
|
||||||
|
raw = execute_code(
|
||||||
|
code="import os; print(os.getcwd())",
|
||||||
|
task_id=task_id,
|
||||||
|
enabled_tools=list(SANDBOX_ALLOWED_TOOLS),
|
||||||
|
)
|
||||||
|
result = json.loads(raw)
|
||||||
|
self.assertEqual(result["status"], "success")
|
||||||
|
self.assertEqual(os.path.realpath(result["output"].strip()), os.path.realpath(td))
|
||||||
|
|
||||||
def test_project_mode_interpreter_is_venv_python(self):
|
def test_project_mode_interpreter_is_venv_python(self):
|
||||||
"""Project mode: sys.executable inside the child is the venv's python
|
"""Project mode: sys.executable inside the child is the venv's python
|
||||||
when VIRTUAL_ENV is set to a real venv."""
|
when VIRTUAL_ENV is set to a real venv."""
|
||||||
|
|
|
||||||
|
|
@ -1339,7 +1339,7 @@ def execute_code(
|
||||||
# Env scrubbing and tool whitelist apply identically in both modes.
|
# Env scrubbing and tool whitelist apply identically in both modes.
|
||||||
_mode = _get_execution_mode()
|
_mode = _get_execution_mode()
|
||||||
_child_python = _resolve_child_python(_mode)
|
_child_python = _resolve_child_python(_mode)
|
||||||
_child_cwd = _resolve_child_cwd(_mode, tmpdir, task_id=task_id)
|
_child_cwd = _resolve_child_cwd(_mode, tmpdir, task_id=task_id or "")
|
||||||
_script_path = os.path.join(tmpdir, "script.py")
|
_script_path = os.path.join(tmpdir, "script.py")
|
||||||
|
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
|
|
@ -1745,29 +1745,43 @@ def _resolve_child_python(mode: str) -> str:
|
||||||
return sys.executable
|
return sys.executable
|
||||||
|
|
||||||
|
|
||||||
def _resolve_child_cwd(mode: str, staging_dir: str, task_id: Optional[str] = None) -> str:
|
def _resolve_child_cwd(mode: str, staging_dir: str, task_id: str = "") -> str:
|
||||||
"""Resolve the working directory for the execute_code subprocess.
|
"""Resolve the working directory for the execute_code subprocess.
|
||||||
|
|
||||||
- ``strict``: the staging tmpdir (today's behavior).
|
- ``strict``: the staging tmpdir (today's behavior).
|
||||||
- ``project``: the session's registered cwd override (from
|
- ``project``: the session's own cwd — its per-session cwd record
|
||||||
``session.cwd.set``), then ``TERMINAL_CWD``, then ``os.getcwd()``.
|
(written after every completed terminal command), then the raw
|
||||||
Falls back to the staging tmpdir as a last resort so we never invoke
|
per-session cwd override registered via ``session.cwd.set`` /
|
||||||
Popen with a nonexistent cwd.
|
``register_task_env_overrides``, then the session's TERMINAL_CWD
|
||||||
|
(same as the terminal tool), or ``os.getcwd()`` if none points at a
|
||||||
|
real dir. Falls back to the staging tmpdir as a last resort so we
|
||||||
|
never invoke Popen with a nonexistent cwd.
|
||||||
|
|
||||||
|
This mirrors the resolution ladder file tools and the terminal use
|
||||||
|
(record → registered override → TERMINAL_CWD), so all file-writing
|
||||||
|
paths within a session agree on the working directory. (#56047)
|
||||||
"""
|
"""
|
||||||
if mode != "project":
|
if mode != "project":
|
||||||
return staging_dir
|
return staging_dir
|
||||||
# Check per-session cwd override first (registered via session.cwd.set
|
|
||||||
# → register_task_env_overrides). This is the same lookup used by
|
|
||||||
# write_file/read_file/patch/terminal so all tool paths agree on the
|
|
||||||
# working directory within a session. (#56047)
|
|
||||||
if task_id:
|
if task_id:
|
||||||
|
# 1. The session's cwd record — IS the session's `cd` state.
|
||||||
|
try:
|
||||||
|
from tools.terminal_tool import get_session_cwd
|
||||||
|
|
||||||
|
recorded = get_session_cwd(task_id)
|
||||||
|
except Exception:
|
||||||
|
recorded = None
|
||||||
|
if recorded and os.path.isdir(recorded):
|
||||||
|
return recorded
|
||||||
|
# 2. Registered workspace override (session.cwd.set → gateway/TUI/ACP).
|
||||||
try:
|
try:
|
||||||
from tools.file_tools import _registered_task_cwd_override
|
from tools.file_tools import _registered_task_cwd_override
|
||||||
override = _registered_task_cwd_override(task_id)
|
|
||||||
if override and os.path.isdir(override):
|
session_cwd = _registered_task_cwd_override(task_id)
|
||||||
return override
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
session_cwd = None
|
||||||
|
if session_cwd and os.path.isdir(session_cwd):
|
||||||
|
return session_cwd
|
||||||
raw = os.environ.get("TERMINAL_CWD", "").strip()
|
raw = os.environ.get("TERMINAL_CWD", "").strip()
|
||||||
if raw:
|
if raw:
|
||||||
expanded = os.path.expanduser(raw)
|
expanded = os.path.expanduser(raw)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue