feat: add Codex + Copilot CLI harness backends#24
Conversation
There was a problem hiding this comment.
Pull request overview
This PR expands omac’s harness ecosystem by adding Codex CLI and GitHub Copilot CLI as first-class harness backends, plus follow-on CLI/config improvements (manifest rendering command, inner-binary preflight checks, and *_HOME config-home overrides) and associated docs/bridge assets.
Changes:
- Add
codex/copilotharness descriptors, session-list kinds, and session listing implementations. - Extract a shared skills-manifest renderer (
internal/manifest.Render) and expose it viaomac manifest. - Add preflight inner-harness binary checks (start/serve) and advisory harness-binary reporting in
omac doctor, plus docs/specs/bridge hook assets.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents new harness tokens/aliases and updates usage examples and harness tables. |
| openspec/changes/support-codex-copilot-harnesses/tasks.md | Adds/records implementation task tracking for codex+copilot and related follow-ons. |
| openspec/changes/support-codex-copilot-harnesses/proposal.md | Adds an OpenSpec proposal describing codex+copilot harness support. |
| internal/session/session.go | Adds Codex/Copilot session store discovery and listing dispatch. |
| internal/session/session_test.go | Extends session-listing tests to cover new harnesses and missing-store behavior. |
| internal/sandboxprofile/resolve.go | Allows default sandbox filesystem access to ~/.codex and ~/.copilot. |
| internal/manifest/manifest.go | Introduces a harness-agnostic manifest renderer for activate-response JSON. |
| internal/manifest/manifest_test.go | Adds unit tests for manifest rendering across skill states. |
| internal/config/harness.go | Registers codex/copilot harnesses; adds HomeEnv + ConfigHome() and wires global dirs through it. |
| internal/config/harness_test.go | Adds tests for codex/copilot descriptors and *_HOME overrides. |
| internal/cli/start.go | Adds shared checkInnerBinary and wires it into omac start launch flow. |
| internal/cli/serve.go | Wires inner-binary preflight check into omac serve. |
| internal/cli/manifest_cmd.go | Adds omac manifest subcommand for rendering manifest text from activate JSON. |
| internal/cli/manifest_cmd_test.go | Adds tests for omac manifest (stdin/file/invalid/misuse cases). |
| internal/cli/doctor.go | Adds an “Inner harnesses” section reporting binary presence on PATH. |
| internal/cli/cli.go | Registers the new manifest subcommand and updates usage text. |
| internal/cli/binary_check_test.go | Adds unit tests for checkInnerBinary and doctor output coverage. |
| internal/builtinskills/assets/omac-write-a-skill/references/creating-a-skill.md | Updates bundled guidance doc to mention codex/copilot harness directories and bridges. |
| docs/superpowers/specs/2026-06-29-deferred-items-design.md | Adds a design/spec for the “deferred items” (manifest cmd, binary checks, env overrides). |
| docs/superpowers/specs/2026-06-29-codex-copilot-backends-design.md | Adds a detailed design spec for codex/copilot harness support. |
| docs/superpowers/plans/2026-06-29-deferred-items.md | Adds an implementation plan for deferred items (now implemented in this PR). |
| docs/superpowers/plans/2026-06-29-codex-copilot-backends.md | Adds an implementation plan for remaining codex/copilot work. |
| CREATING_A_SKILL.md | Updates public skill authoring doc for codex/copilot directories and bridges. |
| .gitignore | Ignores local /.worktrees/ directory. |
| .copilot/hooks/omac.json | Adds Copilot hook registration JSON (SessionStart/SessionEnd). |
| .copilot/hooks/omac-bridge.sh | Adds Copilot bridge hook script (activate/deactivate + manifest injection). |
| .copilot/hooks/bridge_test.go | Adds a Go test intended to sanity-check the Copilot bridge script. |
| .codex/hooks/omac-bridge.sh | Adds Codex bridge hook script (activate + manifest injection; no deactivate). |
| .codex/hooks/bridge_test.go | Adds a Go test intended to sanity-check the Codex bridge script. |
| .codex/hooks.json | Adds Codex hook registration JSON (SessionStart only). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
bf21e86 to
66a6dde
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 32 changed files in this pull request and generated 12 comments.
Comments suppressed due to low confidence (1)
README.md:399
- These rows are now inaccurate: harness-specific directories (including
~/.claude,~/.codex,~/.copilot, and OpenCode’s config/runtime dirs) are no longer granted by the default profile and are injected per-harness at launch time. Also~/.claude.jsonis no longer in the default profile read list.
| `~/.claude`, `~/.codex`, `~/.copilot` | read+write | default profile `filesystem.allow` |
| `~/.cache`, `~/Library/Caches` | read+write | default profile `filesystem.allow` |
| `~/go`, `~/.rustup`, `~/.cargo` | read+write | default profile `filesystem.allow` |
| `~/.config/opencode`, `~/.opencode/bin` | read-only | default profile `filesystem.read` |
| `~/.nvm`, `~/.gitconfig`, `~/.gitignore_global`, `~/.claude.json` | read-only | default profile `filesystem.read` |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 34 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
README.md:399
- The sandbox visibility table is now out of sync with the updated sandbox profile + harness-injected
SandboxDirs.~/.config/opencode/~/.opencode/binare no longer part of the default profilefilesystem.read, and harness runtime dirs are granted read+write via injected--allowflags instead. Please update this table to reflect the new sources/permissions (and keep~/.claude.jsonlisted if it remains required).
| Selected harness config/state dirs (e.g. `~/.claude`, `~/.codex`, `~/.copilot`, `~/.local/share/opencode`) | read+write | `harness.SandboxDirs` → `--allow` flags (injected at launch) |
| `~/.cache`, `~/Library/Caches` | read+write | default profile `filesystem.allow` |
| `~/go`, `~/.rustup`, `~/.cargo` | read+write | default profile `filesystem.allow` |
| `~/.config/opencode`, `~/.opencode/bin` | read-only | default profile `filesystem.read` |
| `~/.nvm`, `~/.gitconfig`, `~/.gitignore_global`, `~/.claude.json` | read-only | default profile `filesystem.read` |
| `--session-id <id>`). Sessions come from the harness's own | ||
| store (opencode `session list`; Claude Code's | ||
| ~/.claude/projects files; codex `codex session list`; copilot | ||
| `copilot session list`). Non-interactive stdin prints the |
| Read: []string{ | ||
| "~/.config/opencode", | ||
| "~/.opencode/bin", | ||
| "~/.gitconfig", | ||
| "~/.gitignore_global", | ||
| "~/.nvm", | ||
| "~/.bun/bin", |
| { | ||
| "type": "command", | ||
| "command": "bash \"$(git rev-parse --show-toplevel)/.codex/hooks/omac-bridge.sh\"", | ||
| "statusMessage": "Loading omac skills" | ||
| } |
| { | ||
| "type": "command", | ||
| "bash": "bash \"$(git rev-parse --show-toplevel)/.copilot/hooks/omac-bridge.sh\"", | ||
| "cwd": ".", | ||
| "timeoutSec": 30 | ||
| } |
| { | ||
| "type": "command", | ||
| "bash": "bash \"$(git rev-parse --show-toplevel)/.copilot/hooks/omac-bridge.sh\"", | ||
| "cwd": ".", | ||
| "timeoutSec": 10 | ||
| } |
| import ( | ||
| "encoding/json" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
| ) |
| // The bridge is inert when OMAC_CONTROL_BASE is unset, so we set it to | ||
| // a dummy value that will cause activation to fail gracefully (curl | ||
| // error → empty manifest → no output). To test the manifest rendering | ||
| // path, we need a live control plane. Instead, test that the bridge: | ||
| // 1. Exists and is executable | ||
| // 2. Accepts a SessionStart payload without error | ||
| // 3. Is inert (no output) when control plane is unreachable | ||
| // | ||
| // This is the minimal check: the script runs, doesn't crash, and is | ||
| // a no-op when the control plane is absent. |
| func TestCodexBridgeScriptExists(t *testing.T) { | ||
| if _, err := exec.Command("test", "-f", codexBridgePath).Output(); err != nil { | ||
| t.Skipf("codex bridge script not found at %s", codexBridgePath) | ||
| } | ||
| } |
| func TestCopilotBridgeScriptExists(t *testing.T) { | ||
| if _, err := exec.Command("test", "-f", copilotBridgePath).Output(); err != nil { | ||
| t.Skipf("copilot bridge script not found at %s", copilotBridgePath) | ||
| } | ||
| } |
da94a26 to
ca0f9c1
Compare
- Harness descriptors for codex and copilot with session listing - Bridge scripts for codex (--c instructions) and copilot (custom dirs) - Sandbox briefing injection for both harnesses - Manifest subcommand, binary check, env overrides, doctor improvements - Shebang detection for script binaries - Shared manifest generator extracted to internal/manifest - Docs: README, CREATING_A_SKILL, design specs, implementation plans Signed-off-by: Niclas Hülsmann <niclas.huelsmann@tngtech.com>
ca0f9c1 to
b6fa0a9
Compare
E2E test suite for the omac sandbox: verifies that each harness
(opencode, claude-code, codex, copilot) can start under the omac
sandbox and run an agent that calls bundled skills.
Two test flows:
- TestE2EEchoRest: agent calls echo-rest skill /status endpoint,
asserts {"ok":true} response.
- TestE2ESecurityAudit: agent runs a shell-script audit probe
(scripts/audit.sh) that tests secret isolation, env var filtering,
filesystem confinement, and network egress blocking.
Security audit design:
- AllowanceSpec (internal/e2e/allowance.go) — human-readable Go
struct declaring what the agent IS and IS NOT allowed to see/do.
Drives both the sandbox profile (environment.allow_vars) and the
test assertions (positive + negative).
- self-audit skill: sidecar holds AUDIT_SECRET via env_passthrough;
agent runs probes; test asserts on raw OS denial messages in
captured output — no LLM judgment for pass/fail.
- Negative assertions: AUDIT_SECRET not leaked, denied env vars
stripped, filesystem denied, network denied.
- Positive assertions: sidecar reachable, allow-listed env vars
visible.
- codex on macOS: --no-sandbox (Rust HTTP client incompatible with
Seatbelt); skips negative assertions, runs positive only.
CI artifacts: each test writes agent stdout/stderr, sidecar logs,
sandbox profile, and metadata to E2E_LOG_DIR, uploaded as CI
artifact for post-mortem analysis (14-day retention).
Model: zai-org/GLM-5.2 via SKAINET_EXTERNAL endpoint for all
harnesses except claude-code (claude-sonnet-5 via Anthropic proxy).
Results (run 28590006482): 7/8 SecurityAudit PASS, only
opencode/macOS flaked (LLM didn't run full script). All EchoRest
tests pass on all 8 jobs.
Signed-off-by: Niclas Hülsmann <niclas.huelsmann@tngtech.com>
Signed-off-by: Niclas Hülsmann <niclas.huelsmann@tngtech.com>
staticcheck runs without the e2e build tag, so it flagged all functions/types in the e2e package as unused. Add //go:build e2e to allowance.go, artifacts.go, harnesses.go, versions.go, and harnesses_test.go. Also fix S1011 (replace loop with append). Signed-off-by: Niclas Hülsmann <niclas.huelsmann@tngtech.com>
Summary
Add Codex CLI (
codex) and GitHub Copilot CLI (copilot) as supported harness backends in omac, following the same declarative pattern used for OpenCode and Claude Code. Brings the total to four supported agentic coders.Changes
Go implementation (TDD):
internal/config/harness.go— codex + copilotHarnessdescriptors,SessionListCodex/SessionListCopilotenum valuesinternal/session/session.go—listCodex()(reads~/.codex/sessions/),listCopilot()(reads~/.copilot/session-store.dbvia sqlite3 CLI)internal/manifest/manifest.go— sharedRender()function extracted (handles ready/pending-credentials/broken states + global target_path warning)internal/sandboxprofile/resolve.go—~/.codex+~/.copilotadded to default filesystem allow-listBridge scripts:
.codex/hooks/omac-bridge.sh+.codex/hooks.json— SessionStart only (Stop is turn-scoped, no deactivation; relies on TTL reaper).copilot/hooks/omac-bridge.sh+.copilot/hooks/omac.json— SessionStart + SessionEnd, user-level hooksDocs:
README.md— harness tables, bridge mechanisms, quickstart, continue/resume examples, sandbox profileCREATING_A_SKILL.md— skills dirs, discovery section, sidecar section for all 4 harnessesdocs/superpowers/openspec/changes/support-codex-copilot-harnesses/Key decisions
Stopevent is turn-scoped, not session-scoped. Relies on TTL reaper.SessionStart/SessionEnd) for snake_case payload compatibility.copilot/hooks/omac.json(not repo-level.github/hooks/)co(exec is standalonecopilotbinary, notgh-copilot)Test plan
go build ./...passesgo test ./...passes (all packages green)TestBundleGuideMatchesRepoRoot— bundled guide synced with repo rootDeferred to separate change
omac manifestsubcommand + migration of existing bridgesCOPILOT_HOMEenv override supportexec.LookPath)