fix(codex): seed app-server sessions with configured cwd

This commit is contained in:
JP Lew 2026-06-04 12:06:40 +05:30 committed by Teknium
parent 745c4db235
commit c11ae8261b
4 changed files with 84 additions and 1 deletions

View file

@ -617,6 +617,10 @@ class SessionManager:
_register_task_cwd(session_id, cwd)
agent = AIAgent(**kwargs)
# Codex app-server sessions are spawned lazily on the first turn. Stamp
# the ACP workspace onto the agent so the Codex runtime starts from the
# editor/session cwd instead of the Hermes daemon's process cwd.
agent.session_cwd = cwd
# ACP stdio transport requires stdout to remain protocol-only JSON-RPC.
# Route any incidental human-readable agent output to stderr instead.
agent._print_fn = _acp_stderr_print

View file

@ -250,7 +250,9 @@ def run_codex_app_server_turn(
# Spawned on first turn, reused across turns, closed at AIAgent
# shutdown (see _cleanup hook).
if not hasattr(agent, "_codex_session") or agent._codex_session is None:
cwd = getattr(agent, "session_cwd", None) or os.getcwd()
from agent.runtime_cwd import resolve_agent_cwd
cwd = getattr(agent, "session_cwd", None) or str(resolve_agent_cwd())
# Approval callback: defer to Hermes' standard prompt flow if a
# CLI thread has installed one. Gateway / cron contexts get the
# codex-side fail-closed default.

View file

@ -77,6 +77,50 @@ class TestCreateSession:
def test_get_nonexistent_session_returns_none(self, manager):
assert manager.get_session("does-not-exist") is None
def test_make_agent_stamps_session_cwd_for_codex_runtime(self, monkeypatch):
class FakeAgent:
model = "fake-model"
def __init__(self, **kwargs):
self.kwargs = kwargs
monkeypatch.setattr("run_agent.AIAgent", FakeAgent)
monkeypatch.setattr(
"acp_adapter.session.load_config",
lambda: {
"model": {
"default": "fake-model",
"provider": "fake-provider",
},
"mcp_servers": {},
},
raising=False,
)
monkeypatch.setattr(
"hermes_cli.config.load_config",
lambda: {
"model": {
"default": "fake-model",
"provider": "fake-provider",
},
"mcp_servers": {},
},
)
monkeypatch.setattr(
"hermes_cli.runtime_provider.resolve_runtime_provider",
lambda requested=None: {
"provider": requested,
"api_mode": "codex_app_server",
"base_url": "https://example.invalid",
"api_key": "test-key",
},
)
monkeypatch.setattr("acp_adapter.session._register_task_cwd", lambda task_id, cwd: None)
state = SessionManager(db=None).create_session(cwd="/tmp/project")
assert state.agent.session_cwd == "/tmp/project"

View file

@ -293,6 +293,39 @@ class TestRunConversationCodexPath:
agent.run_conversation("hi")
assert not client_mock.chat.completions.create.called
def test_gateway_terminal_cwd_seeds_codex_thread_cwd(self, monkeypatch, tmp_path):
"""Gateway sessions set TERMINAL_CWD without stamping agent.session_cwd.
Codex app-server must still start in that configured workspace instead
of falling back to the Hermes daemon process cwd."""
from agent.transports.codex_app_server_session import (
CodexAppServerSession, TurnResult,
)
captured: dict[str, str] = {}
def fake_init(self, **kwargs):
captured["cwd"] = kwargs["cwd"]
self._thread_id = "thread-stub-1"
def fake_run_turn(self, user_input: str, **kwargs):
return TurnResult(
final_text="ok",
projected_messages=[{"role": "assistant", "content": "ok"}],
turn_id="turn-stub-1",
thread_id="thread-stub-1",
)
monkeypatch.setenv("TERMINAL_CWD", str(tmp_path))
monkeypatch.setattr(CodexAppServerSession, "__init__", fake_init)
monkeypatch.setattr(CodexAppServerSession, "run_turn", fake_run_turn)
agent = _make_codex_agent()
assert not hasattr(agent, "session_cwd")
with patch.object(agent, "_spawn_background_review", return_value=None):
agent.run_conversation("hi")
assert captured["cwd"] == str(tmp_path)
class TestReviewForkApiModeDowngrade:
"""When the parent agent runs on codex_app_server, the background