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,7 +1,7 @@
---
name: multi-agent-mux-resume
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
version: 4.1.3
version: 4.2.0
author: godopu
license: MIT
platforms: [linux, macos]
@@ -49,16 +49,16 @@ ideal resume path:
`agent-sessions.yaml` and on-disk discovery are used to resolve the UUID in this order:
1. **`herdr_sessions[]` row's per-row own id** (`claude_session_id_own` / `agy_conversation_id_own` / `hermes_conversation_id_own` / `grok_session_id_own` / `opencode_session_id_own`) — explicitly saved by `multi-agent-mux-stop` right before teardown (tier-1, race-free).
1. **`herdr_sessions[]` row's per-row own id** (`claude_session_id_own` / `agy_conversation_id_own` / `hermes_conversation_id_own` / `grok_session_id_own` / `opencode_session_id_own` / `codex_session_id_own`) — explicitly saved by `multi-agent-mux-stop` right before teardown (tier-1, race-free).
2. **Workspace-scoped on-disk scan** (adapter `discover()`)
If both are empty → for Class A agents (`agy`, `hermes`, `opencode`), `resume_session.sh` automatically falls back to `FRESH_SPAWN=1` (`spawn-spec`). For Class B agents (`claude`, `grok`), fall back to `multi-agent-mux-create`.
If both are empty → for Class A agents (`agy`, `hermes`, `opencode`, `codex`), `resume_session.sh` automatically falls back to `FRESH_SPAWN=1` (`spawn-spec`). For Class B agents (`claude`, `grok`), fall back to `multi-agent-mux-create`.
## Workflow
```bash
WORKSPACE=/path/to/project
AGENT=claude # claude | agy | hermes | grok | opencode — pass it explicitly
AGENT=claude # claude | agy | hermes | grok | opencode | codex — pass it explicitly
SESSION_NAME=<workspace>-creator-<agent> # same convention as multi-agent-mux-create
# Resolve the isolated herdr server name & load common utils
@@ -70,7 +70,7 @@ UUID=$(bash .agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh
if [ -z "$UUID" ]; then
case "$AGENT" in
agy|hermes|opencode)
agy|hermes|opencode|codex)
: # resume_session.sh handles fresh-spawn fallback automatically (FRESH_SPAWN=1)
;;
*)
@@ -97,6 +97,7 @@ case "$AGENT" in
hermes) CMD_FULL="hermes --resume $UUID --no-restore-cwd --yolo --accept-hooks" ;;
grok) CMD_FULL="grok --resume $UUID --permission-mode bypassPermissions" ;;
opencode) CMD_FULL="opencode --session $UUID --auto --agent build" ;;
codex) CMD_FULL="codex resume --dangerously-bypass-approvals-and-sandbox $UUID" ;;
esac
# 4. Spawn new herdr session + run agent with the saved id
@@ -107,7 +108,7 @@ case "$AGENT" in
# auto-handle trust / bypass dialogs
handle_startup_dialogs "$SESSION_NAME" 20
;;
agy|hermes|grok|opencode)
agy|hermes|grok|opencode|codex)
eval "herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
;;
esac
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# resolve_session_id.sh — multi-agent-mux-resume 의 부속 스크립트
# Usage:
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|grok|opencode>
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|grok|opencode|codex>
# 출력: stdout 으로 UUID 한 줄 (없으면 빈 줄 + exit 0)
#
# P0-C: 전역 agent_identities 를 즉시 반환하지 않는다. lib.sh::find_workspace_uuid
@@ -13,7 +13,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok|opencode> [--session <name>]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok|opencode|codex> [--session <name>]
Outputs the resolved UUID on stdout (empty if not found).
--session prefers that registry row's recorded id; falls back to workspace-wide discovery if it does not verify.
EOF
@@ -36,8 +36,8 @@ done
[ -n "$WORKSPACE" ] || { echo "ERROR: --workspace required" >&2; exit 2; }
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; exit 2; }
case "$AGENT" in
claude|agy|hermes|grok|opencode) ;;
*) echo "ERROR: --agent must be claude, agy, hermes, grok, or opencode" >&2; exit 2 ;;
claude|agy|hermes|grok|opencode|codex) ;;
*) echo "ERROR: --agent must be claude, agy, hermes, grok, opencode, or codex" >&2; exit 2 ;;
esac
find_workspace_uuid "$WORKSPACE" "$AGENT" "$SESSION_NAME"
@@ -9,7 +9,7 @@ source "$LIB_SH"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok|opencode> --session <name> [options]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok|opencode|codex> --session <name> [options]
Options:
--herdr-session NAME specify isolated herdr session name (alias: --herdr-server)
@@ -42,7 +42,7 @@ done
[ -n "$WORKSPACE" ] || { echo "ERROR: --workspace required" >&2; exit 2; }
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; exit 2; }
case "$AGENT" in
claude|agy|hermes|grok|opencode) ;;
claude|agy|hermes|grok|opencode|codex) ;;
*) echo "ERROR: unsupported agent: $AGENT" >&2; exit 2 ;;
esac
[ -n "$SESSION_NAME" ] || { echo "ERROR: --session required" >&2; exit 2; }
@@ -62,7 +62,7 @@ UUID=$(bash "$(dirname "${BASH_SOURCE[0]}")/resolve_session_id.sh" \
FRESH_SPAWN=0
if [ -z "$UUID" ]; then
case "$AGENT" in
agy|hermes|opencode) FRESH_SPAWN=1 ;;
agy|hermes|opencode|codex) FRESH_SPAWN=1 ;;
*)
echo "ERROR: No saved session for $WORKSPACE ($AGENT). Use multi-agent-mux-create first." >&2
exit 1
@@ -119,6 +119,7 @@ if [ "$FRESH_SPAWN" = "1" ]; then
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
hermes) CMD_FULL="${RESOLVED_BIN} --yolo --accept-hooks" ;;
opencode) CMD_FULL="${RESOLVED_BIN} --auto --agent build" ;;
codex) CMD_FULL="${RESOLVED_BIN} --dangerously-bypass-approvals-and-sandbox" ;;
esac
fi
else
@@ -130,6 +131,7 @@ else
hermes) CMD_FULL="${RESOLVED_BIN} --resume $UUID --no-restore-cwd --yolo --accept-hooks" ;;
grok) CMD_FULL="${RESOLVED_BIN} --resume $UUID --permission-mode bypassPermissions" ;;
opencode) CMD_FULL="${RESOLVED_BIN} --session $UUID --auto --agent build" ;;
codex) CMD_FULL="${RESOLVED_BIN} resume --dangerously-bypass-approvals-and-sandbox $UUID" ;;
*) echo "ERROR: unsupported agent: $AGENT" >&2; exit 2 ;;
esac
fi
@@ -4,17 +4,17 @@
# resume UUID 를 per-row own id (claude_session_id_own / agy_conversation_id_own)
# 에 박는다 — agent_identities 전역은 더 이상 primary 아님 (cache 로 강등, P0-C/단계 e).
#
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy|hermes|grok|opencode]
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy|hermes|grok|opencode|codex]
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
usage() {
cat <<EOF
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|grok|opencode] [--herdr-session <name>] [--cmd-full <cmd>]
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|grok|opencode|codex] [--herdr-session <name>] [--cmd-full <cmd>]
--uuid may be empty for an Issue-3 Class A fresh-spawn resume (agy/hermes/
opencode 0-turn stopped session, no conversation id ever assigned) - pass
opencode/codex 0-turn stopped session, no conversation id ever assigned) - pass
--cmd-full so the display command can be recorded without a uuid to embed.
EOF
}
@@ -235,6 +235,16 @@ elif agent == 'opencode':
cp = os.environ.get('CHILD_PID', '0')
if cp.isdigit() and int(cp) > 0:
target['child_pid'] = int(cp)
elif agent == 'codex':
target['pane']['cmd'] = 'codex'
if uuid:
target['pane']['cmd_full'] = f'codex resume --dangerously-bypass-approvals-and-sandbox {uuid}'
target['codex_session_id_own'] = uuid
else:
target['pane']['cmd_full'] = os.environ.get('CMD_FULL_OPT', '') or 'codex --dangerously-bypass-approvals-and-sandbox'
cp = os.environ.get('CHILD_PID', '0')
if cp.isdigit() and int(cp) > 0:
target['child_pid'] = int(cp)
snap = d.setdefault('snapshot', {})
snap['taken_at'] = now