Add Codex remote-control backend
This commit is contained in:
+5
-3
@@ -4,7 +4,8 @@
|
||||
#
|
||||
# • UNIT tests — always run (pure logic, no agents spawned). A failure fails the suite.
|
||||
# • CLAUDE smoke — live, run when the `claude` CLI is available; SKIPs otherwise.
|
||||
# • OPENCODE smoke — live, run when `opencode` + creds are available; SKIPs otherwise.
|
||||
# • CODEX smoke — live, run when Codex is installed, logged in, and Remote Control connects.
|
||||
# • OPENCODE smoke — live, run when `opencode` + creds are available; SKIPs otherwise.
|
||||
# • ISOLATION sanity — after the live runs: assert no leftover aotest-* tmux sessions, and that
|
||||
# the live cc-ci-* sessions are untouched.
|
||||
#
|
||||
@@ -19,7 +20,7 @@ set -uo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO="$(cd "$HERE/.." && pwd)"
|
||||
RC=0
|
||||
UNIT=FAIL CLAUDE=SKIP OPENCODE=SKIP ISO=PASS
|
||||
UNIT=FAIL CLAUDE=SKIP CODEX=SKIP OPENCODE=SKIP ISO=PASS
|
||||
|
||||
echo "######################################################################"
|
||||
echo "# agent-orchestrator test suite"
|
||||
@@ -47,6 +48,7 @@ run_smoke() {
|
||||
|
||||
# ── live smoke tests (when backends available) ──────────────────────────────────────
|
||||
run_smoke "CLAUDE" "$HERE/smoke_claude.sh"; case $? in 0) CLAUDE=PASS;; 2) CLAUDE=SKIP;; *) CLAUDE=FAIL; RC=1;; esac
|
||||
run_smoke "CODEX" "$HERE/smoke_codex.sh"; case $? in 0) CODEX=PASS;; 2) CODEX=SKIP;; *) CODEX=FAIL; RC=1;; esac
|
||||
run_smoke "OPENCODE" "$HERE/smoke_opencode.sh"; case $? in 0) OPENCODE=PASS;; 2) OPENCODE=SKIP;; *) OPENCODE=FAIL; RC=1;; esac
|
||||
|
||||
# ── isolation sanity ────────────────────────────────────────────────────────────────
|
||||
@@ -69,7 +71,7 @@ fi
|
||||
|
||||
# ── summary ─────────────────────────────────────────────────────────────────────────
|
||||
echo; echo "######################################################################"
|
||||
echo "# SUMMARY: unit=$UNIT claude=$CLAUDE opencode=$OPENCODE isolation=$ISO"
|
||||
echo "# SUMMARY: unit=$UNIT claude=$CLAUDE codex=$CODEX opencode=$OPENCODE isolation=$ISO"
|
||||
echo "######################################################################"
|
||||
[ "$RC" -eq 0 ] && echo "ALL RUN TESTS PASSED (skips are OK)" || echo "SUITE FAILED"
|
||||
exit "$RC"
|
||||
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# Isolated live smoke of the Codex backend, driven entirely through agents.py.
|
||||
# It starts one uniquely-named tmux session, verifies Remote Control and the Codex TUI, then
|
||||
# removes only that session. The shared Remote Control daemon intentionally remains available.
|
||||
set -uo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO="$(cd "$HERE/.." && pwd)"
|
||||
CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo "$HOME/.local/bin/codex")}"
|
||||
MODEL="${AOTEST_CODEX_MODEL:-}"
|
||||
PREFIX="aotest-x-$$-"
|
||||
SANDBOX="$(mktemp -d)"
|
||||
CFG="$SANDBOX/agents.toml"
|
||||
FAILED=0
|
||||
|
||||
pass(){ echo " PASS: $*"; }
|
||||
fail(){ echo " FAIL: $*"; FAILED=1; }
|
||||
|
||||
cleanup(){
|
||||
local rc=$?
|
||||
python3 "$REPO/agents.py" --config "$CFG" down probe >/dev/null 2>&1 || true
|
||||
if command -v tmux >/dev/null 2>&1; then
|
||||
tmux ls 2>/dev/null | sed 's/:.*//' | grep "^${PREFIX}" | while read -r s; do
|
||||
tmux kill-session -t "=$s" 2>/dev/null || true
|
||||
done || true
|
||||
fi
|
||||
rm -rf "$SANDBOX"
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
echo "=== codex backend smoke (isolated: prefix=${PREFIX}) ==="
|
||||
command -v tmux >/dev/null 2>&1 || { echo "SKIP: tmux not on PATH"; exit 0; }
|
||||
[ -x "$CODEX_BIN" ] || command -v "$CODEX_BIN" >/dev/null 2>&1 \
|
||||
|| { echo "SKIP: codex binary not found ($CODEX_BIN)"; exit 0; }
|
||||
"$CODEX_BIN" login status >/dev/null 2>&1 \
|
||||
|| { echo "SKIP: Codex is not logged in"; exit 0; }
|
||||
remote_status="$("$CODEX_BIN" remote-control start --json 2>&1)"
|
||||
echo "$remote_status" | grep -q '"status":"connected"' \
|
||||
|| { echo "SKIP: Codex Remote Control is not connected: $remote_status"; exit 0; }
|
||||
pass "Codex Remote Control reports connected"
|
||||
|
||||
model_line=""
|
||||
[ -n "$MODEL" ] && model_line="model = \"$MODEL\""
|
||||
cat > "$CFG" <<EOF
|
||||
[defaults]
|
||||
project_dir = "$REPO"
|
||||
session_prefix = "$PREFIX"
|
||||
log_dir = "$SANDBOX/state"
|
||||
backend = "codex"
|
||||
$model_line
|
||||
watch = "none"
|
||||
|
||||
[backend.codex]
|
||||
bin = "$CODEX_BIN"
|
||||
preamble = "{bin} remote-control start --json >/dev/null"
|
||||
flags = "--dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust --no-alt-screen"
|
||||
supports_resume = false
|
||||
prompt_delivery = "arg"
|
||||
process_name = "codex"
|
||||
footer_ui = true
|
||||
log_grace = 180
|
||||
submit_key = "Enter"
|
||||
startup_prompt_re = "Trusting the directory|Do you trust"
|
||||
startup_prompt_response = "1"
|
||||
startup_prompt_delay = 2
|
||||
stall_idle = 300
|
||||
active_re = "esc to interrupt|working|thinking|running|searching"
|
||||
limit_re = "usage limit|limit reached"
|
||||
|
||||
[[agent]]
|
||||
name = "probe"
|
||||
kind = "persistent"
|
||||
prompt = "You are a harness self-test. Reply exactly CODEX_BACKEND_READY and then wait silently. Do not use tools or modify files."
|
||||
EOF
|
||||
|
||||
python3 "$REPO/agents.py" --config "$CFG" up probe \
|
||||
|| { fail "agents.py up probe errored"; echo "=== CODEX BACKEND SMOKE: FAIL ==="; exit 1; }
|
||||
|
||||
sleep 12
|
||||
if tmux has-session -t "=${PREFIX}probe" 2>/dev/null; then
|
||||
cmd="$(tmux display-message -p -t "=${PREFIX}probe:" '#{pane_current_command}')"
|
||||
pass "session ${PREFIX}probe created via agents.py (pane command: ${cmd})"
|
||||
else
|
||||
fail "${PREFIX}probe session was not created"
|
||||
echo "=== CODEX BACKEND SMOKE: FAIL ==="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pane="$(tmux capture-pane -p -t "=${PREFIX}probe:" -S -200 2>/dev/null)"
|
||||
if [ "$cmd" = "codex" ] && echo "$pane" | grep -q "CODEX_BACKEND_READY"; then
|
||||
pass "Codex TUI attached and completed the probe"
|
||||
else
|
||||
fail "Codex probe did not complete (cmd=${cmd}); tail: $(echo "$pane" | grep -vE '^\s*$' | tail -5)"
|
||||
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"
|
||||
else
|
||||
fail "agents.py status did not report probe RUNNING"
|
||||
fi
|
||||
|
||||
python3 "$REPO/agents.py" --config "$CFG" down probe >/dev/null 2>&1
|
||||
sleep 2
|
||||
if tmux has-session -t "=${PREFIX}probe" 2>/dev/null; then
|
||||
fail "${PREFIX}probe still alive after agents.py down"
|
||||
else
|
||||
pass "agents.py down cleanly removed the session"
|
||||
fi
|
||||
|
||||
if [ "$FAILED" = 0 ]; then echo "=== CODEX BACKEND SMOKE: PASS ==="; exit 0
|
||||
else echo "=== CODEX BACKEND SMOKE: FAIL ==="; exit 1; fi
|
||||
+53
-1
@@ -74,6 +74,22 @@ stall_idle = 900
|
||||
active_re = "esc interrupt|thinking|inferring|running tool|tool call|preparing patch|reading|searching"
|
||||
limit_re = "usage limit|limit reached"
|
||||
|
||||
[backend.codex]
|
||||
bin = "codex"
|
||||
preamble = "{bin} remote-control start --json >/dev/null"
|
||||
flags = "--dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust --no-alt-screen"
|
||||
supports_resume = false
|
||||
prompt_delivery = "arg"
|
||||
process_name = "codex"
|
||||
footer_ui = true
|
||||
log_grace = 180
|
||||
submit_key = "Enter"
|
||||
startup_prompt_re = "Trusting the directory|Do you trust"
|
||||
startup_prompt_response = "1"
|
||||
startup_prompt_delay = 0
|
||||
active_re = "working|thinking|running"
|
||||
limit_re = "usage limit|limit reached"
|
||||
|
||||
[backend.demo]
|
||||
bin = "echo up; exec sleep 100000"
|
||||
prompt_delivery = "exec"
|
||||
@@ -102,6 +118,13 @@ kind = "persistent"
|
||||
backend = "opencode"
|
||||
prompt = "hi"
|
||||
|
||||
[[agent]]
|
||||
name = "cx"
|
||||
kind = "persistent"
|
||||
backend = "codex"
|
||||
model = "gpt-test"
|
||||
prompt = "hi from codex"
|
||||
|
||||
[[agent]]
|
||||
name = "custom"
|
||||
kind = "persistent"
|
||||
@@ -233,10 +256,39 @@ class TestExampleConfig(unittest.TestCase):
|
||||
cfg = agents.load_config(ex)
|
||||
self.assertIn("builder", cfg["agents"])
|
||||
self.assertIn("adversary", cfg["agents"])
|
||||
for be in ("demo", "claude", "opencode"):
|
||||
for be in ("demo", "claude", "codex", "opencode"):
|
||||
self.assertIn(be, cfg["backends"], f"backend {be} missing from example")
|
||||
self.assertEqual(len(agents.phases(cfg)), 2)
|
||||
|
||||
def test_codex_launch_prepares_remote_control_then_starts_tui(self):
|
||||
tmp = tempfile.mkdtemp(prefix="aotest-ut-codex-")
|
||||
old_alive, old_new, old_log = agents.session_alive, agents.new_session, agents.log
|
||||
old_capture, old_run = agents.capture_pane, agents._run
|
||||
try:
|
||||
cfg = agents.load_config(_make_project(tmp))
|
||||
launched = []
|
||||
sent = []
|
||||
agents.session_alive = lambda _session: False
|
||||
agents.new_session = lambda session, cwd, cmd, log_path: launched.append(cmd)
|
||||
agents.log = lambda _msg: None
|
||||
agents.capture_pane = lambda *_args, **_kwargs: "Trusting the directory"
|
||||
agents._run = lambda command: sent.append(command)
|
||||
agents.start_agent(cfg, cfg["agents"]["cx"])
|
||||
self.assertEqual(len(launched), 1)
|
||||
cmd = launched[0]
|
||||
self.assertTrue(cmd.startswith(
|
||||
"codex remote-control start --json >/dev/null && codex "))
|
||||
self.assertIn("--model 'gpt-test'", cmd)
|
||||
self.assertIn(
|
||||
"--dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust --no-alt-screen",
|
||||
cmd)
|
||||
self.assertIn("kickoff-aotest-ut-cx.txt", cmd)
|
||||
self.assertTrue(any("1" in command for command in sent))
|
||||
finally:
|
||||
agents.session_alive, agents.new_session, agents.log = old_alive, old_new, old_log
|
||||
agents.capture_pane, agents._run = old_capture, old_run
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
# ── kickoff-template assembly ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user