The Python SessionStart hook (sync-agents.py) was a runtime side-effect
that copied agent TOMLs into CODEX_HOME/agents on every session start.
That design had three problems:
1. It was a Python script invoked via 'python3 ${PLUGIN_ROOT}/...' which
is fragile on Windows where the binary may be 'python', and is the
wrong layer for a one-shot install task.
2. Agent TOMLs landed as regular file copies, with no provenance link to
the plugin cache and no tracking for clean uninstall.
3. An older release shipped TOMLs without the required 'name' field;
because the current bundle no longer ships them, the hook never had
a chance to overwrite the broken copies on disk, leaving Codex
permanently warning at session start.
Replace the runtime hook with an install-time linker:
linkCachedPluginAgents() (src/cli/install-codex/link-cached-plugin-agents.ts).
The omo-codex CLI now calls it right after linkCachedPluginBins(). For
each 'components/*/agents/*.toml' in the plugin cache, it:
- Linux / macOS: creates a symlink at ${CODEX_HOME}/agents/<basename>
pointing at the cache TOML. The cache directory is the single source
of truth; removing the cache cleanly breaks the link.
- Windows: copies the file (symlinks require admin or Developer Mode).
- Both platforms: writes a '.installed-agents.json' manifest under the
plugin cache listing the installed absolute paths, so a future
'omo uninstall --platform=codex' can remove them deterministically.
Stale regular-file copies (from the old sync-agents.py) are removed and
replaced on Unix. On Windows the existing copy is overwritten.
Tests (src/cli/install-codex/link-cached-plugin-agents.test.ts):
9 cross-platform tests that mock the 'platform' parameter to exercise
the Linux, macOS, and Windows code paths in a single 'bun test' run,
matching the existing pattern from linkCachedPluginBins. Covers symlink
creation, Windows copy, stale-file replacement, manifest writing,
idempotency, multi-component discovery, and the empty-bundle edge case.
Removed:
- packages/omo-codex/plugin/components/ultrawork/hooks/sync-agents.py
- packages/omo-codex/plugin/test/bundled-agents.test.mjs
(it tested the Python hook; behaviour is now covered by the TS tests)
- SessionStart hook entry in both ultrawork and aggregate hooks.json
- 2 sync-agents tests + 1 manifest assertion in ultrawork-hooks.test.mjs
- 'hooks/sync-agents.py' in ultrawork/package.json files list
- sync-agents.py reference from aggregate.test.mjs component markers
Updated:
- components/ultrawork/README.md, AGENTS.md: describe the install-time
linker as the source of truth, no more SessionStart agent sync.
Verified end-to-end:
bun run src/cli/index.ts install --no-tui --platform=codex
ls -la ~/.codex/agents/ # all 4 TOMLs are symlinks pointing to cache
cat ~/.codex/plugins/cache/.../omo/0.1.0/.installed-agents.json # manifest present
Promote the four real-usage QA channels (HTTP call, tmux, Browser use, Computer use) into a dedicated top-level `# Manual-QA channels` section in both the Codex ultrawork directive and the ultragoal skill. Each channel ships with the concrete invocation (`curl -i`, `tmux new-session` + `send-keys` + `capture-pane`, Playwright / puppeteer / Chromium, OS-level GUI automation) and the artifact to capture. The Goal section now declares `TESTS ALONE NEVER PROVE DONE` and requires every criterion to build a fresh real-usage scenario and run it through one of the four channels every time. Auxiliary surfaces (CLI stdout / DB diff / parsed config dump) are explicitly demoted: they only satisfy genuinely CLI- or data-shaped criteria, never user-facing behavior.
Bootstrap criterion item 2 and execution step 4 used to repeat the same surface enumeration; they now collapse onto the new channel table (single source of truth, less drift). EXECUTE-AS-SCENARIO in the ultragoal skill follows the same compression. The previous CLEANUP (paired teardown + receipt) + leftover-state stop rule from the prior commit are preserved.
Regression tests in `components/ultrawork/hooks/ultrawork-hooks.test.mjs` pin: the `# Manual-QA channels` heading, all four channel labels (`HTTP call`, `tmux`, `Browser use`, `Computer use`), `TESTS ALONE NEVER PROVE DONE`, `every criterion needs its own real-usage scenario`, and `every time`. README + CHANGELOGs document the restructure and the new size (11,005 chars / 232 lines). All 11 ultrawork hook tests + 7 aggregate tests pass. Installed locally via `runCodexInstaller` into `~/.codex/plugins/cache/code-yeongyu-codex-plugins/omo/0.1.0/`; cache hooks emit the new directive end-to-end and the identifier-like `ulw_helper.ts` keyword-boundary check still suppresses.
Both the Codex ultrawork directive and the ultragoal skill now force the agent to actually invoke the real user-facing surface (HTTP via `curl -i`, terminal/TUI via `tmux new-session` + `send-keys` + `capture-pane`, GUI via computer-use / Playwright, CLI stdout, DB diff) instead of treating evidence as a free-form artifact list. A paired CLEANUP step requires teardown of every QA-spawned process, tmux session, browser context, container, bound port, temp file/dir, and QA-only env var, with a one-line cleanup receipt recorded next to the artifact path (ultrawork) or embedded in the `--evidence` string (ultragoal). Missing receipt keeps the criterion in_progress / records BLOCKED. New Stop rule: leftover state from QA means NOT done.
Regression tests in `components/ultrawork/hooks/ultrawork-hooks.test.mjs` pin SURFACE-AS-SCENARIO, the concrete `curl -i` / `tmux new-session` / `computer-use / Playwright` invocations, the paired CLEANUP block with cleanup receipt + `tmux kill-session`, and the leftover-state Stop rule so the mandates cannot be silently regressed. README and CHANGELOGs refreshed; stale 5,821-char claim replaced with measured 10,037 chars / 213 lines. All 9 ultrawork hook tests + 7 aggregate tests pass.