feat(cli): ADR-005 Phase 2 — codex adapter - #231
Conversation
Adds the local-CLI-wrapper adapter for `codex`, enabling a developer (or a Commonly agent) to attach a locally-installed codex CLI as a participant in a pod via `commonly agent attach codex` + `commonly agent run`. The argv shape diverges from the ADR-005 §Adapters-shipped-in-v1 table (written against an older codex-acp variant). codex-cli 0.125.0 uses: - new turn: codex exec --json --skip-git-repo-check -o <f> "<prompt>" - resume: codex exec resume --json --skip-git-repo-check -o <f> <sid> "<prompt>" Session continuity = `codex exec resume <thread_id>`, NOT a `--session` flag. The adapter captures `thread_id` from the JSONL `thread.started` event on stdout and reads the agent's final reply from the file written by `--output-last-message` (cleaner than parsing every event variant). Mirrors claude.js shape: - memory preamble (=== Context ===) when ctx.memoryLongTerm is set - test seam via ctx._spawnImpl - 5-min default SIGTERM timeout - registered in cli/src/lib/adapters/index.js next to claude/stub Live smoke deferred — local codex auth needs re-login (refresh token reused) and Stage 2 (gateway-container deploy) is a separate PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k reading from a pipe Surfaced live during ADR-005 Phase 2 end-to-end smoke against api-dev. The codex adapter was using `child_process.spawn`'s default stdio, which inherits a fresh pipe for stdin. Codex 0.125.0's `exec` then prints "Reading additional input from stdin..." and blocks waiting for input that never arrives — the spawn hangs until the adapter's 5-min SIGTERM timeout fires. Interactive smoke worked because codex detects a TTY on stdin and uses the argv prompt directly. Only non-TTY parents (like the `commonly agent run` loop) trigger the hang. Fix: pass `stdio: ['ignore', 'pipe', 'pipe']` so codex sees /dev/null → immediate EOF → falls back to the argv prompt as intended. Verified end-to-end on dev: `@laptop-codex please reply with the single word: pong` round-trips through codex 0.125.0 + the run loop and posts `pong` back to the pod. Adds a regression test that pins `calls[0].opts.stdio` so a future "cleanup" PR can't silently revert this. claude.js may have the same latent issue but currently works in practice — out of scope for this PR; file follow-up if a TTY-less smoke fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Live smoke against Issue caught + fixed: the original adapter used Fix in End-to-end verification: Flow: Tests: 15/15 (added one regression). Full CLI suite still 11/11 green. claude.js has the same latent default but works in practice — flagged as out-of-scope follow-up in the commit message. |
Two important fixes + one consistency nit from the code-reviewer pass. 1. Resume argv ordering. Previously `exec resume --json --skip-git-repo-check -o <file> <sid> <prompt>` — clap parses this correctly today, but a future codex parser change could silently consume <sid> as the value of -o, producing an empty turn every time. Reorder to `exec resume <sid> --json --skip-git-repo-check -o <file> <prompt>` so the positional sits immediately after the subcommand, matching codex's documented signature and parser-independent of clap version. Test pins the exact position (first three argv tokens) plus an explicit guard that <sid> appears before -o. 2. Strengthen the temp-dir cleanup test. Previous version read /proc/self/status and asserted it was a string — always true, no real coverage. Now counts `commonly-codex-*` entries in tmpdir() before and after a failing spawn and asserts the count doesn't grow, which is what the test title claimed all along. 3. Module JSDoc referenced `--output-last-message` while the argv uses the `-o` short alias. Update the comment to match what readers will see in the source. All 15 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Self-review pass via code-reviewer subagent — addressed two important findings + one nit in
Reviewer findings deliberately deferred:
What the reviewer flagged as solid (worth surfacing for posterity): adapter purity holds (ADR-005 invariant #1), JSONL parser correct on chunk-boundary / partial-line / non-JSON / Verdict: Approve with suggestions → suggestions addressed. Tests still 15/15. |
Local-CLI-wrapper adapter for `codex`. `commonly agent attach codex` + `commonly agent run` puts a locally-installed codex CLI in a Commonly pod as a first-class agent, mirroring the Phase 1b `claude` adapter shape (memory preamble, `_spawnImpl` test seam, 5-min SIGTERM timeout). Argv shape diverges from the ADR-005 §Adapters-shipped-in-v1 table (written against an older codex-acp variant). codex-cli 0.125.0 uses `codex exec resume <id>` as a subcommand, NOT a `--session <id>` flag. Session id is captured from the `thread.started` JSONL event; the agent's final reply is read from the file written by `-o <FILE>` (the `--output-last-message` short alias) — cleaner than parsing every event-type variant the model can emit. Includes a `stdio: ['ignore', 'pipe', 'pipe']` fix surfaced during the live smoke against api-dev: codex's `exec` blocks on "Reading additional input from stdin..." when spawned from a non-TTY parent (the run loop). Setting stdin to `'ignore'` gives codex /dev/null → immediate EOF → it falls back to the argv prompt. Pinned with a regression test. End-to-end smoke validated on dev: `@laptop-codex pong` round-trips through codex 0.125.0 + the run loop and posts back to the pod. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Squashed and merged manually as commit b94bf74 to preserve Lily as author (per repo convention — see memory/feedback-pr-merge-pattern.md). |
…#236) Adds a `codex-tools-installer` init container to the clawdbot-gateway deployment that installs `@openai/codex` (pinned via `agents.clawdbot.codexTools.codexVersion`) and the `@commonly/cli` (installed from this repo's `cli/` subdirectory at the pinned git ref) into a shared `/tools` volume the main container mounts read-only with `/tools/bin` prepended to PATH. This is the bridge ADR-005 Stage 2 needs so dev agents (theo / nova / pixel / ops) can eventually mention `@codex` instead of calling `acpx_run`. The wrapper itself shipped in PR #231; this PR puts the substrate where it can run. Why an init container, not a Dockerfile change: `_external/clawdbot` is a submodule on the openclaw fork; touching its Dockerfile would require a fork PR + a submodule pointer bump. The init container path lives entirely in the commonly chart and ships with this deploy. Why @commonly/cli installs from source: it's not on npm yet (ADR-005 Phase 4 publication hasn't shipped). The cli/ subdirectory is a self-contained ~200KB package with one runtime dep. The init container apt-installs git, clones this repo at the pinned ref, copies cli/ into /tools/lib/commonly-cli, runs npm install --omit=dev, and symlinks the bin. Pin the ref to a SHA or tag in values.yaml when stability matters. Soft-fail: if the npm registry or github is unreachable at pod-start time, the wrapper falls through with a warning rather than failing the init container. Gateway routing keeps working (acpx_run continues as fallback); operator can re-trigger on the next pod restart. Hard-failing the gateway pod for a transient outage in one optional capability would strand all agent traffic, which is the wrong trade-off. Auth.json reuse: the existing `clawdbot-auth-seed` init container already provisions chatgpt account-1's codex `auth.json` to `/state/.codex/auth.json`, and the gateway container's `lifecycle.postStart` copies it to `~/.codex/auth.json`. The wrapper reuses that — no new ESO secret. Trade-off: shared quota with the existing acpx_run path. A dedicated codex account for the wrapper is a follow-up if it becomes a bottleneck. Run loop is operator-driven for now (`commonly agent attach codex` + `commonly agent run codex` inside the pod). Auto-start in the container lifecycle is a follow-up after the manual flow validates end-to-end. Runbook at `docs/runbooks/codex-in-gateway-pod.md` covers the operator bootstrap end-to-end and the dev-agent HEARTBEAT cutover plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds the local-CLI-wrapper adapter for
codex, enablingcommonly agent attach codex+commonly agent runto put a locally-installed codex CLI in a Commonly pod as a first-class agent. First Phase 2 adapter (cursor + gemini follow as separate small PRs).This is the same shape as Phase 1b's
claudeadapter — adapters are pure subprocess wrappers per ADR-005 §Adapter pattern, registered incli/src/lib/adapters/index.js, talking to the kernel only via the run loop.Argv shape — diverges from the ADR-005 table
ADR-005 §Adapters-shipped-in-v1 was written against an older codex-acp variant. The current codex-cli (tested: 0.125.0) uses:
codex exec --json --skip-git-repo-check -o <file> "<prompt>"codex exec resume --json --skip-git-repo-check -o <file> <sid> "<prompt>"Session continuity is via the
exec resume <id>subcommand, NOT a--session <id>flag. Documented in the source comment so a future codex bump has one file to update.Output handling
thread.started→ capturesthread_idfor next-turn resumeturn.failed→ rejects witherror.messageso the run loop surfaces it instead of generic exit-code error--output-last-message <FILE>after the process closes — cleaner than parsing every JSONL event variant.Mirrors claude.js convention
=== Context (your persistent memory) === ... === Current turn ===) whenctx.memoryLongTermis setctx._spawnImplfinallydetect()returns{ path, version }ornullTests
cli/__tests__/adapters.codex.test.mjs— 14 tests:detect(): success,which-fallback, ENOENT, non-zero exitspawn(): first-turn argv, resume-turn argv (subcommand + prompt-position), memory preamble (with + without),turn.failedevent surfacing, non-zero exit + stderr trim, timeout + SIGTERM, JSONL chunk-boundary buffering, temp-dir cleanup on rejectionlistAdapterNames()includescodexLive smoke — deferred
This PR ships the adapter + tests. Live smoke (attach + run + @mention round-trip against
api-dev.commonly.me) is deferred because the local codex auth has an expired refresh token. Aftercodex logout && codex login, the smoke is one-liner:What's next (separate PRs)
@openai/codex+@commonly/clito_external/clawdbot/Dockerfile, wirecommonly agent run codexalongside the openclaw process, install the codex agent into dev DM rooms. Lets dev agents (theo/nova/pixel/ops) replaceacpx_runcalls with@codexmentions.Test plan
cli/__tests__/adapters.codex.test.mjs— 14/14 passnpm run linton changed files — no errorscodex loginre-auth — attach + @mention round-trip on api-dev🤖 Generated with Claude Code