Files
oh-my-opencode/packages/omo-codex/plugin/components/ultrawork/hooks/ultrawork-hooks.test.mjs
T
YeonGyu-Kim 0d7c16a042 refactor(omo-codex): install agent TOMLs via symlinks at install time, drop sync-agents.py
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
2026-05-28 13:58:12 +09:00

170 lines
5.7 KiB
JavaScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
const hookDir = dirname(fileURLToPath(import.meta.url));
const pluginRoot = dirname(hookDir);
const detectorPath = join(hookDir, "ultrawork-detector.py");
async function runPython(scriptPath, input, env = {}) {
return new Promise((resolve, reject) => {
const child = spawn("python3", [scriptPath], {
env: {
...process.env,
...env,
},
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.once("error", reject);
child.once("close", (code, signal) => {
resolve({ code, signal, stdout, stderr });
});
child.stdin.end(input);
});
}
test("#given ultrawork prompt #when detector runs #then emits directive", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ulw this change",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.signal, null);
assert.equal(result.stderr, "");
assert.match(result.stdout, /^<ultrawork-mode>/);
assert.match(result.stdout, /First user-visible line this turn MUST be exactly:/);
});
test("#given ultrawork prompt #when detector runs #then directive keeps goal budget unlimited", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ultrawork this change",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.signal, null);
assert.equal(result.stderr, "");
assert.match(result.stdout, /Goals are\s+unlimited/);
assert.match(result.stdout, /exactly `objective` and `status` fields/);
assert.doesNotMatch(result.stdout, /token[_-]?budget/i);
assert.doesNotMatch(result.stdout, /200000/i);
});
test("#given ultrawork prompt #when detector runs #then directive mandates manual-QA-as-scenario for http/tmux/computer-use", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ultrawork",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.stderr, "");
assert.match(result.stdout, /SURFACE-AS-SCENARIO/);
assert.match(result.stdout, /MANUAL QA \u2014 YOU EXECUTE IT, NO STUBS/);
assert.match(result.stdout, /curl -i/);
assert.match(result.stdout, /tmux new-session/);
});
test("#given ultrawork prompt #when detector runs #then directive enumerates 4 manual-QA channels explicitly", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ultrawork",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.stderr, "");
assert.match(result.stdout, /# Manual-QA channels/);
assert.match(result.stdout, /PICK ONE PER CRITERION \u2014 ACTUALLY RUN IT/);
assert.match(result.stdout, /1\. HTTP call/);
assert.match(result.stdout, /2\. tmux/);
assert.match(result.stdout, /3\. Browser use/);
assert.match(result.stdout, /4\. Computer use/);
});
test("#given ultrawork prompt #when detector runs #then directive forbids tests-alone verification", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ultrawork",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.stderr, "");
assert.match(result.stdout, /TESTS ALONE NEVER PROVE DONE/);
assert.match(result.stdout, /Every[\s\n]+criterion needs its own real-usage scenario/);
assert.match(result.stdout, /every time/);
});
test("#given ultrawork prompt #when detector runs #then directive mandates paired cleanup with receipt and leftover-state stop rule", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "please ultrawork",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.stderr, "");
assert.match(result.stdout, /CLEANUP \(PAIRED \u2014 NEVER SKIP\)/);
assert.match(result.stdout, /cleanup receipt/);
assert.match(result.stdout, /tmux kill-session/);
assert.match(result.stdout, /Leftover state from QA/);
assert.match(result.stdout, /means NOT done/);
});
test("#given identifier-like ulw #when detector runs #then does not emit directive", async () => {
const payload = JSON.stringify({
hook_event_name: "UserPromptSubmit",
prompt: "refactor ulw_helper.ts",
});
const result = await runPython(detectorPath, payload);
assert.equal(result.code, 0);
assert.equal(result.signal, null);
assert.equal(result.stdout, "");
assert.equal(result.stderr, "");
});
test("#given hook manifest #when read #then registers only the prompt detector hook", async () => {
const manifest = JSON.parse(await readFile(join(hookDir, "hooks.json"), "utf8"));
assert.match(
manifest.hooks.UserPromptSubmit[0].hooks[0].command,
/ultrawork-detector\.py/,
);
assert.equal(manifest.hooks.SessionStart, undefined);
assert.equal(pluginRoot.endsWith("components/ultrawork"), true);
});
test("#given component package #when inspected #then plugin identity is owned by aggregate root", async () => {
const pkg = JSON.parse(await readFile(join(pluginRoot, "package.json"), "utf8"));
assert.equal(pkg.files.includes(".codex-plugin"), false);
await assert.rejects(
readFile(join(pluginRoot, ".codex-plugin", "plugin.json"), "utf8"),
/code: 'ENOENT'|ENOENT/,
);
});