diff --git a/README.md b/README.md index 6165248..88d3d33 100644 --- a/README.md +++ b/README.md @@ -204,14 +204,16 @@ prompt_delivery = "exec" # {kickoff}=prompt file, {session}=session name, For an `"arg"` backend the flag *templates* are configurable (so you can point at a non-claude CLI): `resume_flag` (default `--resume '{id}'`), `model_flag` (default `--model '{model}'`), -`remote_control_flag` (default `--remote-control '{session}'`), and `remote_addr_flag` (default -`--remote '{addr}'`). A backend that sets `process_name` +`remote_control_flag` (default `--remote-control '{session}'`), `remote_addr_flag` (default +`--remote '{addr}'`), and `remote_cwd_flag` (default `-C {dir}` whenever `remote_addr` is set). +A backend that sets `process_name` participates in backend-mismatch healing; one that doesn't (e.g. `demo`) never does. An optional `preamble` runs first and must succeed before the TUI starts; it supports `{bin}`, `{session}`, `{model}`, and `{dir}` templates. The Codex backend uses it to idempotently start the shared Remote Control daemon, then uses `remote_addr = "unix://"` to connect each tmux-hosted Codex -TUI to that daemon. This keeps the app-server as the sole thread-store writer while local and -Remote clients share the session. +TUI to that daemon. The accompanying `-C` is required because the daemon's process directory is +independent of the agent's. This keeps the app-server as the sole thread-store writer while local +and Remote clients share the session in the configured project directory. For an unattended TUI with a deterministic first-run gate, `startup_prompt_re` opts into one screen check after `startup_prompt_delay` seconds; on a match, the harness types the explicitly configured `startup_prompt_response` and `submit_key`. Codex uses this to trust the agent's diff --git a/agents.py b/agents.py index 9159748..66775a9 100755 --- a/agents.py +++ b/agents.py @@ -350,10 +350,15 @@ def start_agent(cfg, agent, *, force=False): parts.append(_render_template(backend.get("remote_control_flag", "--remote-control '{session}'"), {"session": session})) if backend.get("remote_addr"): - parts.append(_render_template(backend.get("remote_addr_flag", "--remote '{addr}'"), { + remote_values = { "addr": backend["remote_addr"], "session": session, "model": model, "dir": shlex.quote(cwd), - })) + } + parts.append(_render_template( + backend.get("remote_addr_flag", "--remote '{addr}'"), remote_values)) + remote_cwd_flag = backend.get("remote_cwd_flag", "-C {dir}") + if remote_cwd_flag: + parts.append(_render_template(remote_cwd_flag, remote_values)) if model: parts.append(_render_template(backend.get("model_flag", "--model '{model}'"), {"model": model})) if backend.get("flags"): diff --git a/tests/smoke_codex.sh b/tests/smoke_codex.sh index efeeca0..b0419e3 100755 --- a/tests/smoke_codex.sh +++ b/tests/smoke_codex.sh @@ -96,6 +96,12 @@ else fail "Codex probe did not complete (cmd=${cmd}); tail: $(echo "$pane" | grep -vE '^\s*$' | tail -5)" fi +if echo "$pane" | grep -Fq "$REPO"; then + pass "daemon-created Codex thread uses the requested project directory" +else + fail "Codex thread did not use requested directory $REPO" +fi + if python3 "$REPO/agents.py" --config "$CFG" status \ | grep -E '^\s*probe\b' | grep -q RUNNING; then pass "agents.py status reports probe RUNNING" diff --git a/tests/test_unit.py b/tests/test_unit.py index d37e7e3..3239c52 100755 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -279,8 +279,9 @@ class TestExampleConfig(unittest.TestCase): cmd = launched[0] self.assertTrue(cmd.startswith( "codex remote-control start --json >/dev/null && " - "codex --remote 'unix://' --model 'gpt-test' ")) + "codex --remote 'unix://' -C ")) self.assertEqual(cmd.count("--remote 'unix://'"), 1) + self.assertIn(f"-C {agents.shlex.quote(cfg['agents']['cx']['dir'])}", cmd) self.assertIn("--model 'gpt-test'", cmd) self.assertIn( "--dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust --no-alt-screen",