feat(agent): add full codex agent support (v4.2.0)

- Implement CodexAdapter in lib_py/agents/adapters/codex.py with safe flags and TUI readiness
- Extend create_session, resume_session, stop_session, and reconcile for codex CLI
- Support codex agent delegation and loop integration across all multi-agent skills
- Add unit, component, and contract test suites for codex integration
- Verify all 462 tests passing cleanly
This commit is contained in:
Godopu committed 2026-09-05 23:02:29 +09:00
1 parent de0a9c2e81
commit 76f10b2096
45 files changed
+2045 -276

No files matched your search

@@ -1,14 +1,14 @@
---
name: multi-agent-mux-status
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
version: 4.1.3
version: 4.2.0
author: godopu
license: MIT
platforms: [linux, macos]
environments: [terminal, herdr]
metadata:
hermes:
tags: [agent, herdr, claude, antigravity, agy, grok, status, read-only, snapshot]
tags: [agent, herdr, claude, antigravity, agy, grok, opencode, codex, status, read-only, snapshot]
related_skills: [multi-agent-mux-create, multi-agent-mux-resume, multi-agent-mux-stop, multi-agent-mux-monitor]
prereq_skills: [multi-agent-mux-create, multi-agent-mux-monitor]
---
@@ -53,12 +53,12 @@ def resume_on_disk(s):
name = s.get('name', '')
cwd = (s.get('pane') or {}).get('cwd', '')
agent = None
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode', 'codex'):
if any(name.endswith(f'-{r}-{a}') for r in ('creator', 'planner', 'reviewer')) or name.endswith(f'-{a}'):
agent = a
break
if not agent:
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode', 'codex'):
if f"-{a}" in name or f"_{a}" in name:
agent = a
break
@@ -125,6 +125,29 @@ def resume_on_disk(s):
except Exception:
return 'MISSING'
return 'no'
if agent == 'codex':
u = s.get('codex_session_id_own')
if u:
codex_home = os.environ.get('CODEX_HOME', f"{home}/.codex")
db_matches = glob.glob(f"{codex_home}/state_*.sqlite")
if db_matches:
db = max(db_matches, key=os.path.getmtime)
try:
import sqlite3
conn = sqlite3.connect(db)
r = conn.execute("SELECT rollout_path FROM threads WHERE id=?", (u,)).fetchone()
conn.close()
if r and r[0] and os.path.exists(r[0]):
return 'yes'
if r:
return 'yes'
except Exception:
pass
m = glob.glob(f"{codex_home}/sessions/*/*/*/rollout-*-{u}.jsonl")
if m and os.path.exists(m[0]):
return 'yes'
return 'MISSING'
return 'no'
return '?'
@@ -231,12 +254,12 @@ def resume_on_disk(s):
name = s.get('name', '')
cwd = (s.get('pane') or {}).get('cwd', '')
agent = None
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode', 'codex'):
if any(name.endswith(f'-{r}-{a}') for r in ('creator', 'planner', 'reviewer')) or name.endswith(f'-{a}'):
agent = a
break
if not agent:
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
for a in ('claude', 'agy', 'hermes', 'grok', 'opencode', 'codex'):
if f"-{a}" in name or f"_{a}" in name:
agent = a
break
@@ -303,6 +326,29 @@ def resume_on_disk(s):
except Exception:
return 'MISSING'
return 'no'
if agent == 'codex':
u = s.get('codex_session_id_own')
if u:
codex_home = os.environ.get('CODEX_HOME', f"{home}/.codex")
db_matches = glob.glob(f"{codex_home}/state_*.sqlite")
if db_matches:
db = max(db_matches, key=os.path.getmtime)
try:
import sqlite3
conn = sqlite3.connect(db)
r = conn.execute("SELECT rollout_path FROM threads WHERE id=?", (u,)).fetchone()
conn.close()
if r and r[0] and os.path.exists(r[0]):
return 'yes'
if r:
return 'yes'
except Exception:
pass
m = glob.glob(f"{codex_home}/sessions/*/*/*/rollout-*-{u}.jsonl")
if m and os.path.exists(m[0]):
return 'yes'
return 'MISSING'
return 'no'
return '?'