import assert from "node:assert/strict"; import { execFileSync, spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { afterEach, beforeEach, describe, it } from "node:test"; import { buildManagedCodexHooksConfig } from "../../config/codex-hooks.js"; import { DOCUMENT_REFRESH_EXEMPTION_PREFIX } from "../../document-refresh/enforcer.js"; import { initTeamState, readTeamLeaderAttention, readTeamPhase, writeTeamLeaderAttention, } from "../../team/state.js"; import { dispatchCodexNativeHook, isCodexNativeHookMainModule, looksLikeGoalCompletionPrompt, mapCodexHookEventToOmxEvent, resolveSessionOwnerPidFromAncestry, } from "../codex-native-hook.js"; import { writeSessionStart } from "../../hooks/session.js"; import { resetTriageConfigCache } from "../../hooks/triage-config.js"; import { executeStateOperation } from "../../state/operations.js"; import { HUD_TMUX_HEIGHT_LINES } from "../../hud/constants.js"; import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js"; import { OMX_TMUX_HUD_LEADER_PANE_ENV } from "../../hud/tmux.js"; import { readAllState } from "../../hud/state.js"; import { renderHud } from "../../hud/render.js"; import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js"; import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js"; import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js"; import { getBaseStateDir } from "../../state/paths.js"; import { maybeNudgeLeaderForAllowedWorkerStop } from "../notify-hook/team-worker-stop.js"; import { MAX_NATIVE_STDIN_JSON_BYTES } from "../hook-payload-guard.js"; function nativeHookScriptPath(): string { return join(process.cwd(), "dist", "scripts", "codex-native-hook.js"); } function parseSingleJsonStdout(stdout: string): Record { const trimmed = stdout.trim(); assert.notEqual(trimmed, ""); assert.equal(trimmed.split("\n").length, 1); return JSON.parse(trimmed) as Record; } function runNativeHookCli( payload: Record | string, options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}, ): string { return execFileSync( process.execPath, [nativeHookScriptPath()], { cwd: options.cwd ?? process.cwd(), input: typeof payload === "string" ? payload : JSON.stringify(payload), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], env: options.env ?? process.env, }, ); } function runNativeHookCliResult( payload: Record | string, options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}, ) { return spawnSync( process.execPath, [nativeHookScriptPath()], { cwd: options.cwd ?? process.cwd(), input: typeof payload === "string" ? payload : JSON.stringify(payload), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], env: options.env ?? process.env, }, ); } async function writeJson(path: string, value: unknown): Promise { await mkdir(dirname(path), { recursive: true }).catch(() => {}); await writeFile(path, JSON.stringify(value, null, 2)); } async function writeNativeMappedSessionState( cwd: string, stateDir: string, sessionId: string, nativeSessionId: string, ): Promise { await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: nativeSessionId, cwd, }); } async function writeLiveNativeMappedSessionState( cwd: string, stateDir: string, sessionId: string, nativeSessionId: string, ): Promise { await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); const liveState = await writeSessionStart(cwd, sessionId, { nativeSessionId }); const liveStatePath = join(cwd, ".omx", "state", "session.json"); const targetStatePath = join(stateDir, "session.json"); if (liveStatePath !== targetStatePath) { await writeFile(targetStatePath, JSON.stringify(liveState, null, 2)); } } async function writeSessionSkillActiveState( stateDir: string, sessionId: string, skill: string, phase: string, ): Promise { await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill, phase, session_id: sessionId, active_skills: [{ skill, phase, active: true, session_id: sessionId }], }); } async function setTeamPaneIds( cwd: string, teamName: string, paneIds: { leaderPaneId: string; workerPaneIds: Record }, ): Promise { for (const fileName of ["config.json", "manifest.v2.json"]) { const filePath = join(cwd, ".omx", "state", "team", teamName, fileName); const parsed = JSON.parse(await readFile(filePath, "utf-8")) as { leader_pane_id?: string | null; workers?: Array<{ name?: string; pane_id?: string | null }>; }; parsed.leader_pane_id = paneIds.leaderPaneId; parsed.workers = (parsed.workers ?? []).map((worker) => ({ ...worker, pane_id: worker.name ? paneIds.workerPaneIds[worker.name] ?? worker.pane_id ?? null : worker.pane_id ?? null, })); await writeJson(filePath, parsed); } } async function withIsolatedHome(prefix: string, run: (homeDir: string) => Promise): Promise { const homeDir = await mkdtemp(join(tmpdir(), `omx-native-hook-home-${prefix}-`)); const previousHome = process.env.HOME; try { process.env.HOME = homeDir; return await run(homeDir); } finally { if (typeof previousHome === "string") process.env.HOME = previousHome; else delete process.env.HOME; await rm(homeDir, { recursive: true, force: true }); } } async function withLoreGuardConfig( value: string, prefix: string, run: (cwd: string) => Promise, ): Promise { const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`)); const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`)); const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`)); const originalGuard = process.env.OMX_LORE_COMMIT_GUARD; const originalCodexHome = process.env.CODEX_HOME; const originalHome = process.env.HOME; try { delete process.env.OMX_LORE_COMMIT_GUARD; process.env.CODEX_HOME = codexHome; process.env.HOME = defaultHome; await writeFile( join(codexHome, "config.toml"), `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`, "utf-8", ); return await run(cwd); } finally { if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = originalGuard; if (originalCodexHome === undefined) delete process.env.CODEX_HOME; else process.env.CODEX_HOME = originalCodexHome; if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome; await rm(cwd, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); await rm(defaultHome, { recursive: true, force: true }); } } function buildWorkerStopFakeTmux( tmuxLogPath: string, options: { failSend?: boolean; busyLeader?: boolean; captureText?: string; currentCommand?: string; sendDelayMs?: number; removePathOnSend?: string; removePathOnCapture?: string; } = {}, ): string { const rawCaptureText = options.captureText ?? (options.busyLeader ? "• Working… (esc to interrupt)" : "› ready"); const captureText = `'${rawCaptureText.replace(/'/g, "'\"'\"'")}'`; const currentCommand = `'${(options.currentCommand ?? "codex").replace(/'/g, "'\"'\"'")}'`; const sendDelaySeconds = Math.max(0, options.sendDelayMs ?? 0) / 1000; const removePathOnSend = options.removePathOnSend ? `'${options.removePathOnSend.replace(/'/g, "'\"'\"'")}'` : ""; const removePathOnCapture = options.removePathOnCapture ? `'${options.removePathOnCapture.replace(/'/g, "'\"'\"'")}'` : ""; return `#!/usr/bin/env bash set -eu echo "$@" >> "${tmuxLogPath}" cmd="$1" shift || true if [[ "$cmd" == "display-message" ]]; then fmt="" while [[ "$#" -gt 0 ]]; do case "$1" in -p) ;; -t) shift ;; *) fmt="$1" ;; esac shift || true done case "$fmt" in "#{pane_in_mode}") echo "0" ;; "#{pane_id}") echo "%42" ;; "#{pane_current_path}") pwd ;; "#{pane_start_command}") echo "codex" ;; "#{pane_current_command}") printf '%s\\n' ${currentCommand} ;; "#S") echo "omx-team-worker-stop" ;; *) ;; esac exit 0 fi if [[ "$cmd" == "capture-pane" ]]; then ${removePathOnCapture ? `rm -rf ${removePathOnCapture}` : ""} printf '%s\\n' ${captureText} exit 0 fi if [[ "$cmd" == "set-buffer" ]]; then printf '%s' "\${@: -1}" > "${tmuxLogPath}.buffer" exit 0 fi if [[ "$cmd" == "show-buffer" ]]; then if [[ -f "${tmuxLogPath}.buffer" ]]; then cat "${tmuxLogPath}.buffer"; fi exit 0 fi if [[ "$cmd" == "paste-buffer" ]]; then target="" while [[ "$#" -gt 0 ]]; do case "$1" in -t) target="$2"; shift 2 ;; *) shift ;; esac done if [[ -f "${tmuxLogPath}.buffer" ]]; then echo "send-keys -t \${target} -l $(cat "${tmuxLogPath}.buffer")" >> "${tmuxLogPath}" fi exit 0 fi if [[ "$cmd" == "delete-buffer" ]]; then rm -f "${tmuxLogPath}.buffer" exit 0 fi if [[ "$cmd" == "send-keys" ]]; then ${sendDelaySeconds > 0 ? `sleep ${sendDelaySeconds}` : ""} ${removePathOnSend ? `rm -rf ${removePathOnSend}` : ""} ${options.failSend ? "exit 1" : "exit 0"} fi exit 0 `; } async function initTempGitRepo(prefix: string): Promise { const cwd = await mkdtemp(join(tmpdir(), prefix)); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); return cwd; } async function writeActiveAutopilotSession(cwd: string, sessionId: string): Promise { await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, }); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "autopilot-state.json"), { active: true, current_phase: "execution", }); } async function writeHookCounterPlugin(cwd: string): Promise { const markerPath = join(cwd, ".omx", "stop-hook-counter.json"); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "count-stop-hook.mjs"), `import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; export async function onHookEvent(event) { if (event.event !== "stop") return; const outPath = join(process.cwd(), ".omx", "stop-hook-counter.json"); await mkdir(dirname(outPath), { recursive: true }); let count = 0; try { count = JSON.parse(await readFile(outPath, "utf-8")).count || 0; } catch {} await writeFile(outPath, JSON.stringify({ count: count + 1 }, null, 2)); } `, "utf-8", ); return markerPath; } async function writeReleaseReadinessLeaderAttention( teamName: string, sessionId: string, cwd: string, options: { workRemaining: boolean }, ): Promise { await writeTeamLeaderAttention(teamName, { team_name: teamName, updated_at: "2026-04-12T17:20:00.000Z", source: "notify_hook", leader_decision_state: "done_waiting_on_leader", leader_attention_pending: true, leader_attention_reason: "leader_session_stopped", attention_reasons: ["leader_session_stopped"], leader_stale: true, leader_session_active: false, leader_session_id: sessionId, leader_session_stopped_at: "2026-04-12T17:20:00.000Z", unread_leader_message_count: 0, work_remaining: options.workRemaining, stalled_for_ms: null, }, cwd); } async function writeReleaseReadinessStateMarker( sessionId: string, teamName: string, cwd: string, ): Promise { await writeJson( join(cwd, ".omx", "state", "sessions", sessionId, "release-readiness-state.json"), { active: true, session_id: sessionId, team_name: teamName, stable_final_recommendation_emitted: true, }, ); } const TEAM_STOP_COMMIT_GUIDANCE = " If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization."; const DEFAULT_AUTO_NUDGE_RESPONSE = "continue with the current task only if it is already authorized"; const TEAM_ENV_KEYS = [ "OMX_TEAM_WORKER", "OMX_TEAM_INTERNAL_WORKER", "OMX_TEAM_STATE_ROOT", "OMX_TEAM_LEADER_CWD", "OMX_SESSION_ID", "OMX_ROOT", "OMX_STATE_ROOT", "SESSION_ID", "OMX_QUESTION_RETURN_PANE", "OMX_LEADER_PANE_ID", "TMUX", "TMUX_PANE", "OMX_TMUX_HUD_OWNER", "OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS", "OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS", ] as const; const priorTeamEnv = new Map<(typeof TEAM_ENV_KEYS)[number], string | undefined>(); beforeEach(() => { priorTeamEnv.clear(); for (const key of TEAM_ENV_KEYS) { priorTeamEnv.set(key, process.env[key]); delete process.env[key]; } }); afterEach(() => { for (const key of TEAM_ENV_KEYS) { const value = priorTeamEnv.get(key); if (typeof value === "string") process.env[key] = value; else delete process.env[key]; } priorTeamEnv.clear(); }); describe("codex native hook config", () => { it("builds the expected managed hooks.json shape", () => { const config = buildManagedCodexHooksConfig("/tmp/omx"); assert.deepEqual(Object.keys(config.hooks), [ "SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit", "PreCompact", "PostCompact", "Stop", ]); const sessionStart = config.hooks.SessionStart[0] as { matcher?: string; hooks?: Array>; }; assert.equal(sessionStart.matcher, "startup|resume|clear"); assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined); const preToolUse = config.hooks.PreToolUse[0] as { matcher?: string; hooks?: Array>; }; assert.equal(preToolUse.matcher, undefined); assert.match( String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/, ); assert.equal(preToolUse.hooks?.[0]?.statusMessage, undefined); const postToolUse = config.hooks.PostToolUse[0] as { matcher?: string; hooks?: Array>; }; assert.equal(postToolUse.matcher, undefined); assert.match( String(postToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/, ); assert.equal(postToolUse.hooks?.[0]?.statusMessage, undefined); const userPromptSubmit = config.hooks.UserPromptSubmit[0] as { matcher?: string; hooks?: Array>; }; assert.equal(userPromptSubmit.matcher, undefined); assert.match( String(userPromptSubmit.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/, ); assert.equal(userPromptSubmit.hooks?.[0]?.statusMessage, undefined); const stop = config.hooks.Stop[0] as { hooks?: Array>; }; assert.equal(stop.hooks?.[0]?.timeout, 30); const postCompact = config.hooks.PostCompact[0] as { matcher?: string; hooks?: Array>; }; assert.equal(postCompact.matcher, undefined); assert.match( String(postCompact.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/, ); assert.doesNotMatch( String(postCompact.hooks?.[0]?.command || ""), /PostCompact Nudge|additionalContext|printf/, ); }); }); describe("codex native hook dispatch", () => { it("treats space-containing argv entry paths as the main module", () => { const entryPath = "/tmp/omx native/codex-native-hook.js"; assert.equal( isCodexNativeHookMainModule(pathToFileURL(entryPath).href, entryPath), true, ); }); it("does not treat a different module url as the main module", () => { assert.equal( isCodexNativeHookMainModule( pathToFileURL("/tmp/omx native/other-script.js").href, "/tmp/omx native/codex-native-hook.js", ), false, ); }); it("emits Stop-schema-safe block JSON when unidentifiable malformed stdin has native Stop runtime surface", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-stop-surface-")); try { await mkdir(join(cwd, ".omx"), { recursive: true }); const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: "{", encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); const output = parseSingleJsonStdout(result.stdout) as { decision?: string; continue?: boolean; reason?: string; stopReason?: string; systemMessage?: string; hookSpecificOutput?: unknown; }; assert.equal(output.decision, "block"); assert.equal(output.continue, undefined); assert.equal( output.reason, "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.", ); assert.equal(output.stopReason, "native_hook_stdin_parse_error"); assert.equal(output.hookSpecificOutput, undefined); assert.match( String(output.systemMessage ?? ""), /stdin JSON parsing failed inside codex-native-hook:/, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves non-Stop fail-closed JSON when malformed stdin identifies a non-Stop hook", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-nonstop-")); try { await mkdir(join(cwd, ".omx"), { recursive: true }); const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: '{hook_event_name:"PreToolUse",', encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); const output = parseSingleJsonStdout(result.stdout) as { continue?: boolean; decision?: string; stopReason?: string; systemMessage?: string; hookSpecificOutput?: unknown; }; assert.equal(output.continue, undefined); assert.equal(output.decision, undefined); assert.equal(output.stopReason, undefined); assert.equal(output.hookSpecificOutput, undefined); assert.match( String(output.systemMessage ?? ""), /stdin JSON parsing failed inside codex-native-hook:/, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("redacts unterminated prompt-like malformed stdin fields", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-unterminated-")); try { const privatePrompt = "PRIVATE_UNTERMINATED_PROMPT"; const malformed = `{hook_event_name:"PostToolUse", prompt:"${privatePrompt}`; const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: malformed, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); const output = parseSingleJsonStdout(result.stdout); assert.equal(output.stopReason, "native_hook_stdin_parse_error"); const log = await readFile(join(cwd, ".omx", "logs", `native-hook-${new Date().toISOString().split("T")[0]}.jsonl`), "utf-8"); const entry = JSON.parse(log.trim()) as Record; const prefix = String(entry.raw_input_prefix ?? ""); assert.doesNotMatch(prefix, new RegExp(privatePrompt)); assert.match(prefix, /prompt:"\[REDACTED\]"/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("logs a bounded redacted raw stdin prefix when CLI stdin is malformed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-log-prefix-")); try { const secret = "sk-test-secret123456"; const promptText = "summarize private launch notes"; const malformed = `{hook_event_name:"PostToolUse", access_token:"${secret}", prompt:"${promptText}", text:"${promptText}", bad:"${"x".repeat(400)}"}${String.fromCharCode(10, 0, 7)}`; const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: malformed, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); const output = parseSingleJsonStdout(result.stdout); assert.equal(output.stopReason, "native_hook_stdin_parse_error"); const log = await readFile(join(cwd, ".omx", "logs", `native-hook-${new Date().toISOString().split("T")[0]}.jsonl`), "utf-8"); const entry = JSON.parse(log.trim()) as Record; const prefix = String(entry.raw_input_prefix ?? ""); assert.equal(entry.type, "native_hook_stdin_parse_error"); assert.equal(entry.raw_input_length, Buffer.byteLength(malformed, "utf-8")); assert.ok(prefix.length <= 240, `prefix should be bounded, got ${prefix.length}`); assert.doesNotMatch(prefix, /[\u0000-\u001f\u007f-\u009f]/); assert.doesNotMatch(prefix, new RegExp(secret)); assert.doesNotMatch(prefix, new RegExp(promptText)); assert.match(prefix, /\[REDACTED\]/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits Stop-schema-safe block JSON when malformed stdin still identifies Stop", () => { const stdout = runNativeHookCli('{hook_event_name:"Stop",'); const output = parseSingleJsonStdout(stdout) as { decision?: string; reason?: string; stopReason?: string; systemMessage?: string; hookSpecificOutput?: unknown; }; assert.equal(output.decision, "block"); assert.equal( output.reason, "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.", ); assert.equal(output.stopReason, "native_hook_stdin_parse_error"); assert.equal(output.hookSpecificOutput, undefined); assert.match( String(output.systemMessage ?? ""), /stdin JSON parsing failed inside codex-native-hook:/, ); }); it("emits no-op JSON stdout for PreToolUse non-Bash tools with null output", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-nonbash-noop-")); try { const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: JSON.stringify({ hook_event_name: "PreToolUse", cwd, session_id: "sess-cli-pretool-nonbash-noop", thread_id: "thread-cli-pretool-nonbash-noop", turn_id: "turn-cli-pretool-nonbash-noop", tool_name: "Read", tool_input: { file_path: "package.json" }, }), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); assert.deepEqual(parseSingleJsonStdout(result.stdout), {}); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits PreToolUse CLI advisory JSON with only systemMessage", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-schema-safe-")); try { const output = parseSingleJsonStdout(runNativeHookCli({ hook_event_name: "PreToolUse", cwd, session_id: "sess-cli-pretool-schema-safe", thread_id: "thread-cli-pretool-schema-safe", turn_id: "turn-cli-pretool-schema-safe", tool_name: "Bash", tool_input: { command: "rm -rf dist" }, }, { cwd })); assert.deepEqual(output, { systemMessage: "Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits PreToolUse CLI block JSON as hook-specific deny with preserved systemMessage guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-block-schema-safe-")); try { const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: "sess-cli-pretool-block-schema-safe", thread_id: "thread-cli-pretool-block-schema-safe", turn_id: "turn-cli-pretool-block-schema-safe", tool_name: "Bash", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' }, }, { cwd }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput", "systemMessage"]); assert.match(String(output.systemMessage ?? ""), /Lore protocol/); assert.equal(output.decision, undefined); assert.equal(output.reason, undefined); assert.equal(output.stopReason, undefined); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.equal( hookSpecificOutput.permissionDecisionReason, "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves ralplan PreToolUse planning guard as hook-specific deny JSON", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-ralplan-pretool-boundary-")); const sessionId = "sess-cli-ralplan-pretool-boundary"; const stateDir = join(cwd, ".omx", "state"); try { await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: sessionId, }); const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-cli-ralplan-pretool-boundary", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" }, }, { cwd }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput"]); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /Ralplan is active \(phase: critic-review\)/); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /implementation\/write tools are blocked/); assert.match(String(hookSpecificOutput.additionalContext ?? ""), /Write only planning artifacts/); assert.equal(output.decision, undefined); assert.equal(output.reason, undefined); assert.equal(output.systemMessage, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves team-worker typed subagent PreToolUse exemption without thread spawn provenance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-typed-pretool-exempt-")); const sessionId = "sess-team-worker-typed-pretool-exempt"; const stateDir = join(cwd, ".omx", "state"); const basePayload = { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-team-worker-typed-pretool-exempt", agent_role: "executor", tool_name: "Edit", tool_use_id: "tool-team-worker-typed-pretool-exempt", tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" }, }; try { await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: sessionId, }); const nonTeamWorkerTypedSubagent = await dispatchCodexNativeHook(basePayload, { cwd }); assert.equal( (nonTeamWorkerTypedSubagent.outputJson as { decision?: string } | null)?.decision, "block", "typed/native subagent PreToolUse without trusted thread_spawn provenance must remain protected outside team workers", ); process.env.OMX_TEAM_INTERNAL_WORKER = "typed-pretool-exempt/worker-1"; process.env.OMX_TEAM_WORKER = "typed-pretool-exempt/worker-1"; const teamWorkerTypedSubagent = await dispatchCodexNativeHook(basePayload, { cwd }); assert.equal( teamWorkerTypedSubagent.outputJson, null, "team workers must bypass typed-subagent thread_spawn provenance requirements before planning guards block implementation tools", ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves deep-interview PreToolUse planning guard as hook-specific deny JSON", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-deep-interview-pretool-boundary-")); const sessionId = "sess-cli-deep-interview-pretool-boundary"; const stateDir = join(cwd, ".omx", "state"); try { await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", session_id: sessionId, active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: sessionId, }); const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-cli-deep-interview-pretool-boundary", tool_name: "Write", tool_input: { file_path: "src/runtime.ts", content: "export const changed = true;\n" }, }, { cwd }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput"]); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /Deep-interview is active \(phase: intent-first\)/); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /implementation\/write tools are blocked/); assert.match(String(hookSpecificOutput.additionalContext ?? ""), /requirements\/spec mode/); assert.equal(output.decision, undefined); assert.equal(output.reason, undefined); assert.equal(output.systemMessage, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("synthesizes a deny for malformed explicit PreToolUse blocks instead of downgrading systemMessage to advisory", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-malformed-pretool-block-")); try { for (const malformedBlockShape of ["legacy", "deny"] as const) { const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: `sess-cli-malformed-pretool-${malformedBlockShape}`, thread_id: `thread-cli-malformed-pretool-${malformedBlockShape}`, tool_name: "Bash", tool_input: { command: "pwd" }, }, { cwd, env: { ...process.env, NODE_ENV: "test", OMX_NATIVE_HOOK_TEST_MALFORMED_PRETOOL_BLOCK: malformedBlockShape, }, }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput", "systemMessage"]); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.equal( hookSpecificOutput.permissionDecisionReason, String(output.systemMessage ?? "").trim(), ); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps wrapped ralplan implementation writes blocked at raw classification while allowing planning artifacts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-wrapper-implementation-block-")); const sessionId = "sess-ralplan-wrapper-implementation-block"; const stateDir = join(cwd, ".omx", "state"); try { await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: sessionId, }); const blockedWrapperCommand = "bash -lc \"cat > src/scripts/__tests__/codex-native-hook.test.ts <<'EOF'\nexport const wrappedRalplanMutation = true;\nEOF\""; const blockedWrapper = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-implementation-block", tool_input: { command: blockedWrapperCommand }, }, { cwd }, ); assert.equal(blockedWrapper.outputJson && typeof blockedWrapper.outputJson === "object" ? (blockedWrapper.outputJson as { decision?: string }).decision : undefined, "block"); assert.match(JSON.stringify(blockedWrapper.outputJson), /Ralplan is active \(phase: critic-review\)/); assert.match(JSON.stringify(blockedWrapper.outputJson), /implementation\/write tools are blocked/); const allowedPlanningArtifactWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Write", tool_use_id: "tool-ralplan-wrapper-planning-artifact", tool_input: { file_path: ".omx/context/ralplan-wrapper-notes.md", content: "# Planning notes\n" }, }, { cwd }, ); assert.equal(allowedPlanningArtifactWrite.outputJson, null); const allowedPlanningTmpWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Write", tool_use_id: "tool-ralplan-wrapper-planning-tmp", tool_input: { file_path: ".omx/tmp/sess-ralplan-wrapper/notes.md", content: "# Scratch notes\n", }, }, { cwd }, ); assert.equal(allowedPlanningTmpWrite.outputJson, null); const blockedPlanningTmpScriptWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Write", tool_use_id: "tool-ralplan-wrapper-planning-tmp-script-write", tool_input: { file_path: ".omx/tmp/sess-ralplan-wrapper/run.sh", content: "printf pwned > src/pwned.ts\n", }, }, { cwd }, ); assert.equal((blockedPlanningTmpScriptWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpScriptWrite.outputJson), /\.omx\/tmp|planning artifact paths/); const blockedPlanningTmpScriptExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-planning-tmp-script-exec", tool_input: { command: "sh .omx/tmp/sess-ralplan-wrapper/run.sh" }, }, { cwd }, ); assert.equal((blockedPlanningTmpScriptExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpScriptExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedPlanningTmpExtensionlessExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-planning-tmp-extensionless-exec", tool_input: { command: "./.omx/tmp/sess-ralplan-wrapper/generated" }, }, { cwd }, ); assert.equal((blockedPlanningTmpExtensionlessExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpExtensionlessExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedPlanningTmpVersionedInterpreterExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-planning-tmp-versioned-python-exec", tool_input: { command: "python3.12 .omx/tmp/sess-ralplan-wrapper/generated.txt" }, }, { cwd }, ); assert.equal((blockedPlanningTmpVersionedInterpreterExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpVersionedInterpreterExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedPlanningTmpTsxExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-planning-tmp-tsx-exec", tool_input: { command: "tsx .omx/tmp/sess-ralplan-wrapper/generated.ts" }, }, { cwd }, ); assert.equal((blockedPlanningTmpTsxExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpTsxExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedPlanningTmpTsxWithOptionsExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-planning-tmp-tsx-options-exec", tool_input: { command: "tsx --tsconfig tsconfig.json watch .omx/tmp/sess-ralplan-wrapper/generated.ts" }, }, { cwd }, ); assert.equal((blockedPlanningTmpTsxWithOptionsExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedPlanningTmpTsxWithOptionsExecution.outputJson), /generated-script transport|\.omx\/tmp/); const allowedBeadsMetadataWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Write", tool_use_id: "tool-ralplan-wrapper-beads-metadata", tool_input: { file_path: ".beads/ralplan-wrapper.json", content: "{}\n" }, }, { cwd }, ); assert.equal(allowedBeadsMetadataWrite.outputJson, null); const allowedQuotedMention = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-quoted-mention", tool_input: { command: "printf '%s\\n' 'src/scripts/__tests__/codex-native-hook.test.ts'" }, }, { cwd }, ); assert.equal(allowedQuotedMention.outputJson, null); const allowedPythonPlanningArtifactWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-python-planning-artifact", tool_input: { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans').mkdir(parents=True, exist_ok=True) Path('.omx/plans/rebase-pr3010-ultragoal-fix-plan.md').write_text('planning text') PY`, }, }, { cwd }, ); assert.equal(allowedPythonPlanningArtifactWrite.outputJson, null); const blockedPythonPlanningArtifactExecution = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-python-planning-artifact-exec", tool_input: { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans/run.sh').write_text('echo ran') PY sh .omx/plans/run.sh`, }, }, { cwd }, ); assert.equal(blockedPythonPlanningArtifactExecution.outputJson && typeof blockedPythonPlanningArtifactExecution.outputJson === "object" ? (blockedPythonPlanningArtifactExecution.outputJson as { decision?: string }).decision : undefined, "block"); assert.match(JSON.stringify(blockedPythonPlanningArtifactExecution.outputJson), /same-command|Bash write intent|implementation/i); const blockedPythonAllowedMkdirDynamicSourceWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-python-allowed-mkdir-dynamic-source-write", tool_input: { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans').mkdir(parents=True, exist_ok=True) (Path('src') / 'generated.ts').write_text('implementation') PY`, }, }, { cwd }, ); assert.equal(blockedPythonAllowedMkdirDynamicSourceWrite.outputJson && typeof blockedPythonAllowedMkdirDynamicSourceWrite.outputJson === "object" ? (blockedPythonAllowedMkdirDynamicSourceWrite.outputJson as { decision?: string }).decision : undefined, "block"); assert.match(JSON.stringify(blockedPythonAllowedMkdirDynamicSourceWrite.outputJson), /write intent did not identify an allowed planning artifact path/); const blockedPythonSourceWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-python-source-write", tool_input: { command: `python3 - <<'PY' from pathlib import Path Path('src/generated.ts').write_text('implementation') PY`, }, }, { cwd }, ); assert.equal(blockedPythonSourceWrite.outputJson && typeof blockedPythonSourceWrite.outputJson === "object" ? (blockedPythonSourceWrite.outputJson as { decision?: string }).decision : undefined, "block"); assert.match(JSON.stringify(blockedPythonSourceWrite.outputJson), /Bash .* target src\/generated\.ts/); const blockedPythonMixedWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-wrapper-implementation-block", tool_name: "Bash", tool_use_id: "tool-ralplan-wrapper-python-mixed-write", tool_input: { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans/rebase-pr3010-ultragoal-fix-plan.md').write_text('planning text') Path('src/generated.ts').write_text('implementation') PY`, }, }, { cwd }, ); assert.equal(blockedPythonMixedWrite.outputJson && typeof blockedPythonMixedWrite.outputJson === "object" ? (blockedPythonMixedWrite.outputJson as { decision?: string }).decision : undefined, "block"); assert.match(JSON.stringify(blockedPythonMixedWrite.outputJson), /Bash .* target src\/generated\.ts/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks deep-interview PreToolUse implementation writes when terminal Autopilot run-state shadows stale active state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-terminal-pretool-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-deep-interview-terminal-pretool"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", session_id: sessionId, active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, active: false, mode: "autopilot", outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-30T00:00:00.000Z", updated_at: "2026-05-30T00:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-deep-interview-terminal-pretool", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Deep-interview is active \(phase: intent-first\)/); assert.match(JSON.stringify(result.outputJson), /implementation\/write tools are blocked/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits parseable no-op JSON stdout for inactive Stop CLI runs", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-noop-json-")); try { const stdout = runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-noop-json", thread_id: "thread-cli-stop-noop-json", turn_id: "turn-cli-stop-noop-json", }, { cwd }); const output = parseSingleJsonStdout(stdout); assert.deepEqual(output, {}); assert.equal(existsSync(join(cwd, ".omx", "state")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no-op JSON stdout for Stop payloads with no runtime output", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-null-output-")); try { const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: JSON.stringify({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-null-output", thread_id: "thread-cli-stop-null-output", turn_id: "turn-cli-stop-null-output", stop_hook_active: true, }), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); assert.deepEqual(parseSingleJsonStdout(result.stdout), {}); assert.equal(existsSync(join(cwd, ".omx", "state")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no-op JSON for oversized Stop stdin without parsing or creating inactive state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-oversized-")); try { const oversizedStop = JSON.stringify({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-oversized", transcript: "x".repeat(MAX_NATIVE_STDIN_JSON_BYTES + 1), }); const stdout = runNativeHookCli(oversizedStop, { cwd }); assert.deepEqual(parseSingleJsonStdout(stdout), {}); assert.equal(existsSync(join(cwd, ".omx", "state")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks oversized Stop stdin when current session autopilot is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-oversized-active-")); try { await writeActiveAutopilotSession(cwd, "sess-cli-stop-oversized-active"); const oversizedStop = JSON.stringify({ hook_event_name: "Stop", cwd, session_id: "native-session-hidden-by-oversized-payload", transcript: "x".repeat(MAX_NATIVE_STDIN_JSON_BYTES + 1), }); const output = parseSingleJsonStdout(runNativeHookCli(oversizedStop, { cwd })) as { decision?: string; stopReason?: string; systemMessage?: string; }; assert.equal(output.decision, "block"); assert.equal(output.stopReason, "native_stop_stdin_oversized_active_workflow"); assert.match(String(output.systemMessage ?? ""), /active current-session workflow state/); assert.equal(existsSync(join(cwd, ".omx", "logs")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no-op JSON for oversized Stop stdin for unrelated root autopilot state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-oversized-stale-root-")); try { await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: "sess-current-without-active-autopilot", cwd, }); await writeJson(join(cwd, ".omx", "state", "autopilot-state.json"), { active: true, current_phase: "execution", }); const oversizedStop = JSON.stringify({ hook_event_name: "Stop", cwd, transcript: "x".repeat(MAX_NATIVE_STDIN_JSON_BYTES + 1), }); assert.deepEqual(parseSingleJsonStdout(runNativeHookCli(oversizedStop, { cwd })), {}); assert.equal(existsSync(join(cwd, ".omx", "logs")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no-op JSON for oversized Stop stdin when terminal run-state shadows stale autopilot state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-oversized-terminal-run-")); try { const sessionId = "sess-cli-stop-oversized-terminal-run"; await writeActiveAutopilotSession(cwd, sessionId); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "run-state.json"), { version: 1, active: false, mode: "autopilot", outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-20T11:00:00.000Z", updated_at: "2026-05-20T11:00:00.000Z", }); const oversizedStop = JSON.stringify({ hook_event_name: "Stop", cwd, transcript: "x".repeat(MAX_NATIVE_STDIN_JSON_BYTES + 1), }); assert.deepEqual(parseSingleJsonStdout(runNativeHookCli(oversizedStop, { cwd })), {}); assert.equal(existsSync(join(cwd, ".omx", "logs")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("fails closed for oversized non-Stop stdin before parsing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-nonstop-oversized-")); try { const oversizedPrompt = JSON.stringify({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-cli-prompt-oversized", prompt: "x".repeat(MAX_NATIVE_STDIN_JSON_BYTES + 1), }); const output = parseSingleJsonStdout(runNativeHookCli(oversizedPrompt, { cwd })) as { continue?: boolean; stopReason?: string; systemMessage?: string; }; assert.equal(output.continue, false); assert.equal(output.stopReason, "native_hook_stdin_oversized"); assert.match(String(output.systemMessage ?? ""), /rejected oversized stdin JSON before parsing/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not crash Stop hook dispatch when the exec follow-up queue is malformed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-stop-exec-followup-corrupt-")); try { const session = await writeSessionStart(cwd, "sess-exec-followup-corrupt"); const queuePath = join(cwd, ".omx", "state", "sessions", session.session_id, "exec-followups.json"); await mkdir(dirname(queuePath), { recursive: true }); await writeFile(queuePath, '{"version":1,"records":[', "utf-8"); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: session.session_id, }); assert.equal(result.hookEventName, "Stop"); assert.equal(result.outputJson, null); const queueDirEntries = await readdir(dirname(queuePath)); assert.ok(queueDirEntries.some((entry) => entry.startsWith("exec-followups.json.corrupt-"))); const auditPath = join(cwd, ".omx", "logs", `exec-followups-${new Date().toISOString().slice(0, 10)}.jsonl`); assert.match(await readFile(auditPath, "utf-8"), /exec_followup_queue_corrupt_recovered/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop on stale session autopilot mirror when canonical skill state is inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stale-autopilot-mirror-")); try { const sessionId = "sess-stale-autopilot-mirror"; await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, cwd }); await writeJson(join(cwd, ".omx", "state", "skill-active-state.json"), { version: 1, active: false, skill: "", phase: "cancelled", active_skills: [], }); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ultragoal", session_id: sessionId, }); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ultragoal", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "ultragoal", active: true, session_id: sessionId }], }); const stdout = runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stale-autopilot-mirror", }, { cwd }); assert.deepEqual(parseSingleJsonStdout(stdout), {}); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("includes blocking state source and canonical agreement in Stop diagnostics", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-diagnostic-")); try { const sessionId = "sess-autopilot-diagnostic"; await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, cwd }); await writeJson(join(cwd, ".omx", "state", "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ultragoal", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "ultragoal", active: true, session_id: sessionId }], }); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ultragoal", session_id: sessionId, }); const output = parseSingleJsonStdout(runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-autopilot-diagnostic", }, { cwd })) as { decision?: string; reason?: string; statePath?: string; canonicalDisagreement?: string }; assert.equal(output.decision, "block"); assert.match(String(output.reason ?? ""), /state: \.omx\/state\/sessions\/sess-autopilot-diagnostic\/autopilot-state\.json/); assert.match(String(output.reason ?? ""), /canonical: canonical_agrees/); assert.equal(output.statePath, ".omx/state/sessions/sess-autopilot-diagnostic/autopilot-state.json"); assert.equal(output.canonicalDisagreement, "canonical_agrees"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits exactly one parseable JSON object for active Stop CLI continuation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-json-")); try { await writeActiveAutopilotSession(cwd, "sess-cli-stop-json"); const stdout = runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-json", thread_id: "thread-cli-stop-json", turn_id: "turn-cli-stop-json", }, { cwd }); const output = parseSingleJsonStdout(stdout); assert.equal(output.decision, "block"); assert.equal(output.stopReason, "autopilot_execution"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps noisy Stop hook plugin stdout out of native Stop CLI stdout", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-noisy-plugin-")); try { await writeActiveAutopilotSession(cwd, "sess-cli-stop-noisy-plugin"); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "noisy.mjs"), `export async function onHookEvent(event) { if (event.event === "stop") console.log("PLUGIN_NOISE"); } `, "utf-8", ); const stdout = runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-noisy-plugin", thread_id: "thread-cli-stop-noisy-plugin", turn_id: "turn-cli-stop-noisy-plugin", }, { cwd }); assert.doesNotMatch(stdout, /PLUGIN_NOISE/); const output = parseSingleJsonStdout(stdout); assert.equal(output.decision, "block"); assert.equal(output.stopReason, "autopilot_execution"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits deterministic Stop JSON stdout when Stop dispatch fails", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-dispatch-failure-")); try { const stdout = runNativeHookCli({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-dispatch-failure", thread_id: "thread-cli-stop-dispatch-failure", turn_id: "turn-cli-stop-dispatch-failure", }, { cwd, env: { ...process.env, NODE_ENV: "test", OMX_NATIVE_HOOK_TEST_THROW_STOP_DISPATCH: "1", }, }); const output = parseSingleJsonStdout(stdout); assert.equal(output.decision, "block"); assert.equal(output.stopReason, "native_stop_dispatch_failure"); assert.match(String(output.reason), /failed before normal continuation handling/); assert.match(String(output.systemMessage), /test-induced Stop dispatch failure/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("logs Stop dispatch failures without foreground stderr noise", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-dispatch-silent-")); try { const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: JSON.stringify({ hook_event_name: "Stop", cwd, session_id: "sess-cli-stop-dispatch-silent", thread_id: "thread-cli-stop-dispatch-silent", turn_id: "turn-cli-stop-dispatch-silent", }), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, NODE_ENV: "test", OMX_NATIVE_HOOK_TEST_THROW_STOP_DISPATCH: "1", }, }); assert.equal(result.status, 0, result.stderr || result.stdout); assert.equal(result.stderr, ""); const output = parseSingleJsonStdout(result.stdout); assert.equal(output.stopReason, "native_stop_dispatch_failure"); const logFiles = await readdir(join(cwd, ".omx", "logs")); assert.equal(logFiles.some((name) => /^native-hook-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name)), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps non-Stop dispatch failures fail-closed without foreground stderr noise", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-dispatch-silent-")); try { const result = spawnSync(process.execPath, [nativeHookScriptPath()], { cwd, input: JSON.stringify({ hook_event_name: "PreToolUse", cwd, session_id: "sess-cli-pretool-dispatch-silent", thread_id: "thread-cli-pretool-dispatch-silent", turn_id: "turn-cli-pretool-dispatch-silent", tool_name: "Bash", tool_input: { command: "pwd" }, }), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, NODE_ENV: "test", OMX_NATIVE_HOOK_TEST_THROW_DISPATCH: "1", }, }); assert.equal(result.status, 1); assert.equal(result.stdout, ""); assert.equal(result.stderr, ""); const logFiles = await readdir(join(cwd, ".omx", "logs")); assert.equal(logFiles.some((name) => /^native-hook-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name)), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("maps Codex events onto OMX logical surfaces", () => { assert.equal(mapCodexHookEventToOmxEvent("SessionStart"), "session-start"); assert.equal(mapCodexHookEventToOmxEvent("UserPromptSubmit"), "keyword-detector"); assert.equal(mapCodexHookEventToOmxEvent("PreToolUse"), "pre-tool-use"); assert.equal(mapCodexHookEventToOmxEvent("PostToolUse"), "post-tool-use"); assert.equal(mapCodexHookEventToOmxEvent("PreCompact"), "pre-compact"); assert.equal(mapCodexHookEventToOmxEvent("PostCompact"), "post-compact"); assert.equal(mapCodexHookEventToOmxEvent("Stop"), "stop"); }); it("does not write PreCompact stdout that Codex rejects as hook JSON", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-precompact-")); try { writePage(cwd, { filename: "architecture.md", frontmatter: { title: "Architecture", tags: ["architecture"], created: "2026-05-08T00:00:00.000Z", updated: "2026-05-08T00:00:00.000Z", sources: [], links: [], category: "architecture", confidence: "high", schemaVersion: WIKI_SCHEMA_VERSION, }, content: "\n# Architecture\n\nCompaction-relevant architecture note.\n", }); const result = await dispatchCodexNativeHook({ hook_event_name: "PreCompact", cwd, session_id: "sess-precompact", }); assert.equal(result.hookEventName, "PreCompact"); assert.equal(result.omxEventName, "pre-compact"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no CLI stdout for PreCompact when no Codex action is needed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-precompact-cli-")); try { writePage(cwd, { filename: "architecture.md", frontmatter: { title: "Architecture", tags: ["architecture"], created: "2026-05-08T00:00:00.000Z", updated: "2026-05-08T00:00:00.000Z", sources: [], links: [], category: "architecture", confidence: "high", schemaVersion: WIKI_SCHEMA_VERSION, }, content: "\n# Architecture\n\nCompaction-relevant architecture note.\n", }); const stdout = runNativeHookCli({ hook_event_name: "PreCompact", cwd, session_id: "sess-precompact-cli", }); assert.equal(stdout, ""); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not write PostCompact stdout that Codex rejects as hook JSON", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-postcompact-")); try { const result = await dispatchCodexNativeHook({ hook_event_name: "PostCompact", cwd, session_id: "sess-postcompact", }); assert.equal(result.hookEventName, "PostCompact"); assert.equal(result.omxEventName, "post-compact"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits no CLI stdout for PostCompact when no Codex action is needed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-postcompact-cli-")); try { const stdout = runNativeHookCli({ hook_event_name: "PostCompact", cwd, session_id: "sess-postcompact-cli", }); assert.equal(stdout, ""); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("writes SessionStart state against the long-lived session owner pid and injects environment context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-start-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-start-1", }, { cwd, sessionOwnerPid: 43210, }, ); assert.equal(result.omxEventName, "session-start"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Execution environment\]/); assert.match(additionalContext, /native-hook \/ Codex App outside tmux/); assert.match(additionalContext, /omx team, omx hud, and omx quest(?:ion) need an attached tmux OMX CLI shell|omx team and omx hud need an attached tmux OMX CLI shell/); assert.match(additionalContext, /not available from this outside-tmux surface/); const sessionState = JSON.parse( await readFile(join(cwd, ".omx", "state", "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string; pid?: number }; assert.equal(sessionState.session_id, "sess-start-1"); assert.equal(sessionState.native_session_id, "sess-start-1"); assert.equal(sessionState.pid, 43210); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-launch-1"; const nativeSessionId = "codex-native-1"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId); await writeJson(join(stateDir, "sessions", canonicalSessionId, "hud-state.json"), { last_turn_at: "2026-04-10T00:00:00.000Z", turn_count: 1, }); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: nativeSessionId, }, { cwd, sessionOwnerPid: process.pid, }, ); const sessionState = JSON.parse( await readFile(join(stateDir, "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string; pid?: number }; assert.equal(sessionState.session_id, canonicalSessionId); assert.equal(sessionState.native_session_id, nativeSessionId); assert.equal(sessionState.pid, process.pid); const promptResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: nativeSessionId, thread_id: "thread-1", turn_id: "turn-1", prompt: "$ralplan fix hud scope drift", }, { cwd }, ); assert.equal(promptResult.omxEventName, "keyword-detector"); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), true); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), true); assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps subagent SessionStart from replacing the canonical leader session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "session", telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-leader-session"; const leaderNativeSessionId = "codex-leader-thread"; const childNativeSessionId = "codex-child-thread"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: leaderNativeSessionId, }); await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", iteration: 1, max_iterations: 5, }); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);", "}", ].join("\n"), ); const transcriptPath = join(cwd, "subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, depth: 1, agent_nickname: "Hegel", agent_role: "critic", }, }, }, agent_nickname: "Hegel", agent_role: "critic", }, })}\n`, ); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); const sessionState = JSON.parse( await readFile(join(stateDir, "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string }; assert.equal(sessionState.session_id, canonicalSessionId); assert.equal(sessionState.native_session_id, leaderNativeSessionId); assert.equal( existsSync(join(stateDir, "sessions", childNativeSessionId, "ralph-state.json")), false, ); assert.ok(result.outputJson); const leaderRalph = JSON.parse( await readFile(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), "utf-8"), ) as { active?: boolean; current_phase?: string }; assert.equal(leaderRalph.active, true); assert.equal(leaderRalph.current_phase, "executing"); assert.equal( existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must not independently dispatch session-start hook notifications", ); const tracking = JSON.parse( await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"), ) as { sessions?: Record; }>; }; assert.equal(tracking.sessions?.[canonicalSessionId]?.leader_thread_id, leaderNativeSessionId); assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent"); assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[childNativeSessionId]?.mode, "critic"); assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId); assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent"); assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic"); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "child-stop-turn", }, { cwd }, ); assert.equal( existsSync(join(cwd, "hook-events.jsonl")), false, "subagent Stop must not independently dispatch stop hook notifications", ); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "minimal", telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-leader-session-minimal"; const leaderNativeSessionId = "codex-leader-thread-minimal"; const childNativeSessionId = "codex-child-thread-minimal"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: leaderNativeSessionId, }); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);", "}", ].join("\n"), ); const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, agent_role: "verifier", }, }, }, }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); assert.equal( existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must be suppressed at minimal verbosity", ); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "session", includeChildAgents: true, telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-leader-session-include"; const leaderNativeSessionId = "codex-leader-thread-include"; const childNativeSessionId = "codex-child-thread-include"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: leaderNativeSessionId, }); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);", "}", ].join("\n"), ); const transcriptPath = join(cwd, "included-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, agent_role: "verifier", }, }, }, }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "included-child-stop-turn", }, { cwd }, ); const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8"); assert.match(hookEvents, /"event":"session-start"/); assert.match(hookEvents, /"event":"stop"/); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "agent", telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-leader-session-agent"; const leaderNativeSessionId = "codex-leader-thread-agent"; const childNativeSessionId = "codex-child-thread-agent"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: leaderNativeSessionId, }); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);", "}", ].join("\n"), ); const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, agent_role: "verifier", }, }, }, }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8"); assert.match(hookEvents, /"event":"session-start"/); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("suppresses child-agent SessionStart and Stop before the canonical leader session is reconciled (#2831)", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-no-canonical-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "session", telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); const stateDir = join(cwd, ".omx", "state"); const leaderNativeSessionId = "codex-leader-thread-no-canonical"; const childNativeSessionId = "codex-child-thread-no-canonical"; await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);", "}", ].join("\n"), ); const transcriptPath = join(cwd, "no-canonical-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, agent_role: "explorer", }, }, }, }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); assert.equal( existsSync(join(cwd, "hook-events.jsonl")), false, "child SessionStart must be suppressed even before the canonical leader session is reconciled", ); assert.equal( existsSync(join(stateDir, "session.json")), false, "child SessionStart must not be promoted into a root/leader session", ); const tracking = JSON.parse( await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"), ) as { sessions?: Record; }>; }; assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId); assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent"); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "no-canonical-child-stop-turn", }, { cwd }, ); assert.equal( existsSync(join(cwd, "hook-events.jsonl")), false, "child Stop must be suppressed when the start was recognized as subagent-scoped", ); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("preserves root/leader SessionStart dispatch at session verbosity (#2831)", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-root-session-start-preserved-")); const originalCodexHome = process.env.CODEX_HOME; try { process.env.CODEX_HOME = join(cwd, "codex-home"); await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), { notifications: { enabled: true, verbosity: "session", telegram: { enabled: true, botToken: "123:abc", chatId: "456" }, }, }); await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [ "import { appendFileSync } from 'node:fs';", "export async function onHookEvent(event) {", " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);", "}", ].join("\n"), ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "codex-root-thread-preserved", }, { cwd, sessionOwnerPid: process.pid }, ); const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8"); assert.match( hookEvents, /"event":"session-start"/, "root/leader SessionStart must still dispatch at session verbosity", ); } finally { if (originalCodexHome === undefined) { delete process.env.CODEX_HOME; } else { process.env.CODEX_HOME = originalCodexHome; } await rm(cwd, { recursive: true, force: true }); } }); it("keeps a self-parented native role thread as subagent evidence", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-self-parented-subagent-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-autopilot-session"; const nativeRoleThreadId = "codex-architect-thread"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: nativeRoleThreadId, }); const transcriptPath = join(cwd, "architect-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: nativeRoleThreadId, source: { subagent: { thread_spawn: { parent_thread_id: nativeRoleThreadId, depth: 1, agent_nickname: "Architect", agent_role: "architect", }, }, }, agent_nickname: "Architect", agent_role: "architect", }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: nativeRoleThreadId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); const tracking = JSON.parse( await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"), ) as { sessions?: Record; }>; }; assert.equal(tracking.sessions?.[canonicalSessionId]?.leader_thread_id, undefined); assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[nativeRoleThreadId]?.kind, "subagent"); assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[nativeRoleThreadId]?.mode, "architect"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not attach a subagent SessionStart to an unrelated canonical leader", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-mismatch-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-leader-session-a"; const leaderNativeSessionId = "codex-leader-thread-a"; const unrelatedParentNativeSessionId = "codex-leader-thread-b"; const childNativeSessionId = "codex-child-thread-b"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId, { nativeSessionId: leaderNativeSessionId, }); await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", iteration: 1, max_iterations: 5, }); const transcriptPath = join(cwd, "unrelated-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: unrelatedParentNativeSessionId, depth: 1, agent_nickname: "Spinoza", agent_role: "critic", }, }, }, agent_nickname: "Spinoza", agent_role: "critic", }, })}\n`, ); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); const sessionState = JSON.parse( await readFile(join(stateDir, "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string }; assert.equal(sessionState.session_id, canonicalSessionId); assert.equal(sessionState.native_session_id, leaderNativeSessionId); assert.equal(existsSync(join(stateDir, "subagent-tracking.json")), false); assert.equal(existsSync(join(stateDir, "sessions", childNativeSessionId)), false); assert.equal(result.outputJson, null); const leaderRalph = JSON.parse( await readFile(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), "utf-8"), ) as { active?: boolean; current_phase?: string }; assert.equal(leaderRalph.active, true); assert.equal(leaderRalph.current_phase, "executing"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("describes attached tmux runtime in SessionStart context when TMUX is present", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-start-tmux-")); process.env.TMUX = "/tmp/tmux-attached"; process.env.TMUX_PANE = "%11"; try { const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-start-tmux-1", }, { cwd, sessionOwnerPid: process.pid, }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Execution environment\]/); assert.match(additionalContext, /attached tmux runtime/); assert.match(additionalContext, /omx team, omx hud, and omx quest(?:ion) are directly usable in this session/); assert.match(additionalContext, /visible temporary renderer available from the current pane; primary success JSON is answers\[\]/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("describes direct CLI outside tmux in SessionStart context when the launch source is cli", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-start-cli-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-start-cli-1", source: "cli", }, { cwd, sessionOwnerPid: process.pid, }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Execution environment\]/); assert.match(additionalContext, /direct CLI outside tmux/); assert.doesNotMatch(additionalContext, /native-hook \/ Codex App outside tmux/); assert.match(additionalContext, /omx team, omx hud, and omx quest(?:ion) need an attached tmux OMX CLI shell|omx team and omx hud need an attached tmux OMX CLI shell/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("prefers the OMX owner session id when a native new session revives HUD", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-owner-session-revive-")); try { const stateDir = join(cwd, ".omx", "state"); const ownerSessionId = "omx-launch-owner-hud"; const oldNativeSessionId = "codex-native-hud-old"; const nativeSessionId = "codex-native-hud-new"; await mkdir(stateDir, { recursive: true }); await writeSessionStart(cwd, ownerSessionId, { nativeSessionId: oldNativeSessionId, pid: process.pid, }); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: nativeSessionId, }, { cwd, sessionOwnerPid: process.pid, }, ); const sessionState = JSON.parse(await readFile(join(stateDir, "session.json"), "utf-8")) as { session_id?: string; native_session_id?: string; previous_native_session_id?: string; owner_omx_session_id?: string; }; assert.equal(sessionState.session_id, nativeSessionId); assert.equal(sessionState.native_session_id, nativeSessionId); assert.equal(sessionState.previous_native_session_id, oldNativeSessionId); assert.equal(sessionState.owner_omx_session_id, ownerSessionId); let reconcileCall: { cwd: string; sessionId?: string; sessionIds?: string[] } | null = null; const promptResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: nativeSessionId, thread_id: "thread-hud-owner", turn_id: "turn-hud-owner", prompt: "$ralplan fix native new hud owner handoff", }, { cwd, reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => { reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds }; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(promptResult.omxEventName, "keyword-detector"); assert.deepEqual(reconcileCall, { cwd, sessionId: ownerSessionId, sessionIds: [ownerSessionId, nativeSessionId], }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("falls back to the canonical session id for malformed HUD owner ids", async () => { for (const [index, invalidOwnerSessionId] of ["codex-native-hud-owner", "omx-../../stale"].entries()) { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-invalid-owner-revive-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-launch-hud-safe"; const nativeSessionId = "codex-native-hud-safe"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId); const sessionStatePath = join(stateDir, "session.json"); const sessionState = JSON.parse(await readFile(sessionStatePath, "utf-8")) as Record; sessionState.owner_omx_session_id = invalidOwnerSessionId; await writeJson(sessionStatePath, sessionState); let reconcileCall: { cwd: string; sessionId?: string; sessionIds?: string[] } | null = null; const promptResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: nativeSessionId, thread_id: `thread-hud-invalid-owner-${index}`, turn_id: "turn-hud-invalid-owner", prompt: "$ralplan fix malformed hud owner handoff", }, { cwd, reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => { reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds }; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(promptResult.omxEventName, "keyword-detector"); assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId, sessionIds: [canonicalSessionId, nativeSessionId], }); } finally { await rm(cwd, { recursive: true, force: true }); } } }); it("passes the canonical OMX session id when UserPromptSubmit revives HUD", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-session-revive-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "omx-launch-hud"; const nativeSessionId = "codex-native-hud"; await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId); let reconcileCall: { cwd: string; sessionId?: string } | null = null; const promptResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: nativeSessionId, thread_id: "thread-hud", turn_id: "turn-hud", prompt: "$ralplan fix orphaned hud session handoff", }, { cwd, reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => { reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId }; return { status: 'recreated', paneId: '%9', desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(promptResult.omxEventName, "keyword-detector"); assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId }); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), true); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), true); assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("adds .omx/ to git info/exclude during SessionStart instead of mutating repo .gitignore", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-gitignore-")); try { await writeFile(join(cwd, ".gitignore"), "node_modules/\n"); execFileSync("git", ["init"], { cwd, stdio: "pipe" }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-gitignore-1", }, { cwd, sessionOwnerPid: 43210 }, ); assert.equal(result.omxEventName, "session-start"); const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8"); assert.equal(gitignore, "node_modules/\n"); const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8"); assert.match(exclude, /(?:^|\n)\.omx\/\n/); assert.match( JSON.stringify(result.outputJson), /Added \.omx\/ to .*\.git[\/]info[\/]exclude/, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps SessionStart quiet when .omx/ is already ignored by repo-level gitignore", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-existing-ignore-")); try { await writeFile(join(cwd, ".gitignore"), "node_modules/\n.omx/\n"); execFileSync("git", ["init"], { cwd, stdio: "pipe" }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-gitignore-existing", }, { cwd, sessionOwnerPid: 43210 }, ); assert.equal(result.omxEventName, "session-start"); const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8"); assert.equal(gitignore, "node_modules/\n.omx/\n"); const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8"); assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/); assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("respects existing Git ignore resolution before writing local excludes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-global-ignore-")); const excludesFile = join(cwd, "global-ignore"); try { await writeFile(join(cwd, ".gitignore"), "node_modules/\n"); await writeFile(excludesFile, ".omx/\n"); execFileSync("git", ["init"], { cwd, stdio: "pipe" }); execFileSync("git", ["config", "core.excludesfile", excludesFile], { cwd, stdio: "pipe" }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-gitignore-global", }, { cwd, sessionOwnerPid: 43210 }, ); assert.equal(result.omxEventName, "session-start"); const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8"); assert.equal(gitignore, "node_modules/\n"); const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8"); assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/); assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("includes persisted project-memory summary in SessionStart context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-memory-")); try { await writeJson(join(cwd, ".omx", "project-memory.json"), { techStack: "TypeScript + Node.js", build: "npm test", conventions: "small diffs, verify before claim", directives: [ { directive: "Keep native Stop bounded to real continuation decisions.", priority: "high" }, ], notes: [ { category: "env", content: "Requires LOCAL_API_BASE for smoke tests", timestamp: new Date().toISOString() }, ], }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-memory-1", }, { cwd, sessionOwnerPid: 43210 }, ); const serialized = JSON.stringify(result.outputJson); assert.match(serialized, /\[Project memory\]/); assert.match(serialized, /TypeScript \+ Node\.js/); assert.match(serialized, /small diffs, verify before claim/); assert.match(serialized, /Keep native Stop bounded to real continuation decisions\./); assert.match(serialized, /Requires LOCAL_API_BASE for smoke tests/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("includes repo-local .omx project-memory during SessionStart when OMX_ROOT is boxed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-boxed-memory-")); const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-root-")); const previousOmxRoot = process.env.OMX_ROOT; try { process.env.OMX_ROOT = boxedRoot; await writeJson(join(cwd, ".omx", "project-memory.json"), { techStack: "Repo-local CLI memory", conventions: "SessionStart should load CLI-written project memory", directives: [ { directive: "Prefer repo-local .omx project memory over boxed runtime fallback.", priority: "high" }, ], }); await writeJson(join(boxedRoot, ".omx", "project-memory.json"), { techStack: "Boxed runtime memory should not win", notes: [{ category: "runtime", content: "stale boxed runtime note", timestamp: new Date().toISOString() }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-boxed-memory-1", }, { cwd, sessionOwnerPid: 43210 }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Project memory\]/); assert.match(additionalContext, /source: \.omx\/project-memory\.json/); assert.match(additionalContext, /Repo-local CLI memory/); assert.match(additionalContext, /SessionStart should load CLI-written project memory/); assert.match(additionalContext, /Prefer repo-local \.omx project memory over boxed runtime fallback\./); assert.doesNotMatch(additionalContext, /Boxed runtime memory should not win/); assert.doesNotMatch(additionalContext, /stale boxed runtime note/); } finally { if (previousOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = previousOmxRoot; await rm(cwd, { recursive: true, force: true }); await rm(boxedRoot, { recursive: true, force: true }); } }); it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-")); try { const now = new Date().toISOString(); const legacyWikiDir = getLegacyWikiDir(cwd); await mkdir(legacyWikiDir, { recursive: true }); await writeFile(join(legacyWikiDir, "legacy.md"), serializePage({ filename: "legacy.md", frontmatter: { title: "Legacy", tags: ["legacy"], created: now, updated: now, sources: [], links: [], category: "reference", confidence: "medium", schemaVersion: WIKI_SCHEMA_VERSION, }, content: "\n# Legacy\n\nLegacy wiki context must remain visible.\n", })); await writeJson(join(cwd, ".omx", "project-memory.json"), { techStack: "Legacy runtime memory should not win", notes: [{ category: "legacy", content: "stale legacy note", timestamp: now }], }); await writeJson(join(cwd, "project-memory.json"), { techStack: "Canonical root memory", build: "npm run build && node --test dist/scripts/__tests__/codex-native-hook.test.js", conventions: "prefer repository-visible project memory at startup", directives: [ { directive: "Load root project-memory.json before legacy .omx memory.", priority: "high", timestamp: now }, ], notes: [ { category: "issue", content: "Regression fixture for issue #2273.", timestamp: now }, ], }); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "sess-root-memory-legacy-wiki", }, { cwd, sessionOwnerPid: 43210 }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Project memory\]/); assert.match(additionalContext, /source: project-memory\.json/); assert.match(additionalContext, /Canonical root memory/); assert.match(additionalContext, /Load root project-memory\.json before legacy \.omx memory\./); assert.match(additionalContext, /Regression fixture for issue #2273\./); assert.doesNotMatch(additionalContext, /Legacy runtime memory should not win/); assert.match(additionalContext, /legacy pages at \.omx\/wiki\//); assert.match(additionalContext, /Legacy wiki fallback is read-only/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("starts a fresh native session without inheriting stale task-scoped context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-isolation-")); try { const stateDir = join(cwd, ".omx", "state"); const priorSessionId = "omx-old-session"; await mkdir(join(stateDir, "sessions", priorSessionId), { recursive: true }); await writeSessionStart(cwd, priorSessionId, { nativeSessionId: "codex-native-old", }); await writeJson(join(stateDir, "sessions", priorSessionId, "ralph-state.json"), { active: true, current_phase: "executing", }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [priorSessionId]: { session_id: priorSessionId, leader_thread_id: "leader-1", updated_at: new Date().toISOString(), threads: { "leader-1": { thread_id: "leader-1", kind: "leader", first_seen_at: new Date().toISOString(), last_seen_at: new Date().toISOString(), turn_count: 1, }, "sub-1": { thread_id: "sub-1", kind: "subagent", first_seen_at: new Date().toISOString(), last_seen_at: new Date().toISOString(), turn_count: 1, }, }, }, }, }); await writeFile( join(cwd, ".omx", "notepad.md"), [ "# OMX Notepad", "", "## PRIORITY", "Preserve durable project guidance.", "", "## WORKING MEMORY", "[2026-04-06T00:33:44Z] stale UI rework context snapshot .omx/context/ui-rework-plan-01-20260406T003344Z.md", ].join("\n"), ); const result = await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "codex-native-new", }, { cwd, sessionOwnerPid: process.pid, }, ); const sessionState = JSON.parse( await readFile(join(stateDir, "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string }; assert.equal(sessionState.session_id, "codex-native-new"); assert.equal(sessionState.native_session_id, "codex-native-new"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /\[Execution environment\]/); assert.match(additionalContext, /native-hook \/ Codex App outside tmux/); assert.match(additionalContext, /\[Priority notes\]/); assert.match(additionalContext, /Preserve durable project guidance/); assert.doesNotMatch(additionalContext, /stale UI rework context snapshot/); assert.doesNotMatch(additionalContext, /\[Subagents\]/); assert.doesNotMatch(additionalContext, /ralph phase: executing/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows same-session/current-thread Ralph Stop continuation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-current-session-")); try { const stateDir = join(cwd, ".omx", "state"); await writeNativeMappedSessionState(cwd, stateDir, "omx-current-ralph", "codex-current-ralph"); await writeJson(join(stateDir, "sessions", "omx-current-ralph", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_omx_session_id: "omx-current-ralph", owner_codex_session_id: "codex-current-ralph", owner_codex_thread_id: "thread-current-ralph", task_description: "Finish issue 2974 stale Ralph Stop-hook guard", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "codex-current-ralph", thread_id: "thread-current-ralph", last_user_message: "continue the current ralph issue 2974 task", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "ralph_executing"); assert.match(String(result.outputJson?.systemMessage), /Ralph is still active/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not resume stale different-session global Ralph Stop state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-stale-global-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_codex_session_id: "codex-old-ralph", owner_codex_thread_id: "thread-old-ralph", task_description: "Finish issue 2974 stale Ralph Stop-hook guard", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-new-ralph", last_user_message: "continue the current ralph issue 2974 task", }, { cwd }, ); assert.equal(result.outputJson?.stopReason, undefined); assert.notEqual(result.outputJson?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not resume global Ralph Stop state for low-overlap unrelated tasks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-low-overlap-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", task_description: "Refactor documentation navigation and README examples", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, last_user_message: "continue auditing billing webhooks and payment retries", }, { cwd }, ); assert.equal(result.outputJson?.stopReason, undefined); assert.notEqual(result.outputJson?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows explicit current user continuation for live-risk Ralph tasks when owner context is current", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-live-current-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_codex_thread_id: "thread-live-ralph", task_description: "Deploy billing migration rollback guard for production payments", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-live-ralph", last_user_message: "continue the current ralph deploy billing migration rollback guard", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "ralph_executing"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows current-owner global Ralph Stop continuation with generic current-task wording", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-current-owner-generic-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_codex_thread_id: "thread-current-owner-generic", task_description: "Refactor documentation navigation and README examples", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-current-owner-generic", last_user_message: "continue the current ralph task", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "ralph_executing"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows same-thread global Ralph Stop continuation without payload text for non-live tasks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-same-thread-no-text-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_codex_thread_id: "thread-same-thread-no-text", task_description: "Refactor documentation navigation and README examples", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-same-thread-no-text", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "ralph_executing"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks no-text global Ralph Stop continuation for live-risk tasks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-live-no-text-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", owner_codex_thread_id: "thread-live-no-text", task_description: "Deploy billing migration rollback guard for production payments", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-live-no-text", }, { cwd }, ); assert.equal(result.outputJson?.stopReason, undefined); assert.notEqual(result.outputJson?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks no-text global Ralph Stop continuation for issue 2974 operational live-risk tasks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-stop-issue-2974-live-no-text-")); try { await writeJson(join(cwd, ".omx", "state", "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", task_description: "Implement Telegram assistant GPT switch plan on VPS service with cron restart send notify workflow", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, }, { cwd }, ); assert.equal(result.outputJson?.stopReason, undefined); assert.notEqual(result.outputJson?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("resolves the Codex owner from ancestry without mistaking codex-native-hook wrappers for Codex", () => { const commands = new Map([ [2100, 'sh -c node "/repo/dist/scripts/codex-native-hook.js"'], [1100, 'node /usr/local/bin/codex.js'], [900, 'bash'], ]); const parents = new Map([ [2100, 1100], [1100, 900], [900, 1], ]); const resolved = resolveSessionOwnerPidFromAncestry(2100, { readParentPid: (pid) => parents.get(pid) ?? null, readProcessCommand: (pid) => commands.get(pid) ?? "", }); assert.equal(resolved, 1100); }); it("records keyword activation from UserPromptSubmit payloads", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-1", thread_id: "thread-1", turn_id: "turn-1", prompt: "$ralplan implement issue #1307", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralplan"); assert.ok(result.outputJson, "UserPromptSubmit should emit developer context"); assert.match(JSON.stringify(result.outputJson), /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.equal( existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false, "session-scoped keyword activation should not write root skill-active-state.json", ); const statePath = join(cwd, ".omx", "state", "sessions", "sess-1", "skill-active-state.json"); assert.equal(existsSync(statePath), true); const state = JSON.parse(await readFile(statePath, "utf-8")) as { skill?: string; active?: boolean; initialized_mode?: string; }; assert.equal(state.skill, "ralplan"); assert.equal(state.active, true); assert.equal(state.initialized_mode, "ralplan"); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-1", "ralplan-state.json")), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("injects deep-interview config overrides into UserPromptSubmit developer context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "standard" standardThreshold = 0.05 standardMaxRounds = 15 enableChallengeModes = false `, ); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-deep-interview-config", thread_id: "thread-1", turn_id: "turn-1", prompt: "$deep-interview prove config reflection", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const serializedOutput = JSON.stringify(result.outputJson); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /threshold=0\.05/); assert.match(serializedOutput, /max_rounds=15/); assert.match(serializedOutput, /enableChallengeModes=false/); const modeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", "sess-deep-interview-config", "deep-interview-state.json"), "utf-8"), ) as { threshold?: number; max_rounds?: number; profile?: string }; assert.equal(modeState.profile, "standard"); assert.equal(modeState.threshold, 0.05); assert.equal(modeState.max_rounds, 15); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("proves UserPromptSubmit context changes before and after adding deep-interview config", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-before-after-")); const sessionId = "sess-deep-interview-config-before-after"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const before = await withIsolatedHome("deep-interview-config-before-after", async () => ( dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-before-after", turn_id: "turn-before", prompt: "$deep-interview prove before config context", }, { cwd }, ) )); const beforeOutput = JSON.stringify(before.outputJson); const beforeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { deep_interview_config?: unknown; threshold?: number; max_rounds?: number; }; assert.equal(before.skillState?.skill, "deep-interview"); assert.doesNotMatch(beforeOutput, /Deep-interview config override active/); assert.equal(before.skillState?.deep_interview_config, undefined); assert.equal(beforeState.deep_interview_config, undefined); assert.equal(beforeState.threshold, undefined); assert.equal(beforeState.max_rounds, undefined); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "standard" standardThreshold = 0.05 standardMaxRounds = 15 `, ); const after = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-before-after", turn_id: "turn-after", prompt: "$deep-interview prove after config context", }, { cwd }, ); const afterOutput = JSON.stringify(after.outputJson); const afterState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number }; threshold?: number; max_rounds?: number; }; assert.equal(after.skillState?.deep_interview_config?.profile, "standard"); assert.match(afterOutput, /Deep-interview config override active/); assert.match(afterOutput, /threshold=0\.05/); assert.match(afterOutput, /max_rounds=15/); assert.equal(afterState.deep_interview_config?.profile, "standard"); assert.equal(afterState.threshold, 0.05); assert.equal(afterState.max_rounds, 15); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("injects deep-interview config for mixed workflow prompts that defer execution modes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-mixed-")); const sessionId = "sess-deep-interview-config-mixed"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "deep" deepThreshold = 0.13 deepMaxRounds = 21 enableChallengeModes = false `, ); const result = await withIsolatedHome("deep-interview-config-mixed", async () => ( dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-mixed-config", turn_id: "turn-mixed-config", prompt: "$autopilot $deep-interview prove mixed config context", }, { cwd }, ) )); const serializedOutput = JSON.stringify(result.outputJson); const modeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number; enableChallengeModes?: boolean }; profile?: string; threshold?: number; max_rounds?: number; enable_challenge_modes?: boolean; }; assert.equal(result.skillState?.skill, "deep-interview"); assert.deepEqual(result.skillState?.deferred_skills, ["autopilot"]); assert.equal(result.skillState?.deep_interview_config?.profile, "deep"); assert.equal(result.skillState?.deep_interview_config?.threshold, 0.13); assert.equal(result.skillState?.deep_interview_config?.maxRounds, 21); assert.equal(result.skillState?.deep_interview_config?.enableChallengeModes, false); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /profile=deep/); assert.match(serializedOutput, /threshold=0\.13/); assert.match(serializedOutput, /max_rounds=21/); assert.match(serializedOutput, /enableChallengeModes=false/); assert.equal(modeState.deep_interview_config?.profile, "deep"); assert.equal(modeState.profile, "deep"); assert.equal(modeState.threshold, 0.13); assert.equal(modeState.max_rounds, 21); assert.equal(modeState.enable_challenge_modes, false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps deep-interview config override context on continuation prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-continuation-")); const sessionId = "sess-deep-interview-config-continuation"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "standard" standardThreshold = 0.05 standardMaxRounds = 15 `, ); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-continuation", turn_id: "turn-start", prompt: "$deep-interview prove config continuation", }, { cwd }, ); const continued = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-continuation", turn_id: "turn-continue", prompt: "continue", }, { cwd }, ); const serializedOutput = JSON.stringify(continued.outputJson); const modeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { threshold?: number; max_rounds?: number; profile?: string }; assert.equal(continued.skillState?.skill, "deep-interview"); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /threshold=0\.05/); assert.match(serializedOutput, /max_rounds=15/); assert.equal(modeState.profile, "standard"); assert.equal(modeState.threshold, 0.05); assert.equal(modeState.max_rounds, 15); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps explicit deep-interview profile flags reflected on continuation prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-profile-continuation-")); const sessionId = "sess-deep-interview-config-profile-continuation"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "standard" standardThreshold = 0.22 standardMaxRounds = 13 deepThreshold = 0.13 deepMaxRounds = 21 `, ); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-profile-continuation", turn_id: "turn-start", prompt: "$deep-interview --deep prove explicit profile continuation", }, { cwd }, ); const continued = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-profile-continuation", turn_id: "turn-continue", prompt: "continue", }, { cwd }, ); const serializedOutput = JSON.stringify(continued.outputJson); const modeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { threshold?: number; max_rounds?: number; profile?: string; deep_interview_config?: { profile?: string } }; assert.equal(continued.skillState?.skill, "deep-interview"); assert.equal(continued.skillState?.deep_interview_config?.profile, "deep"); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /profile=deep/); assert.match(serializedOutput, /threshold=0\.13/); assert.match(serializedOutput, /max_rounds=21/); assert.equal(modeState.deep_interview_config?.profile, "deep"); assert.equal(modeState.profile, "deep"); assert.equal(modeState.threshold, 0.13); assert.equal(modeState.max_rounds, 21); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps the documented deep-interview Suggested Config reflected in UserPromptSubmit context", async () => { const skillDoc = await readFile(join(process.cwd(), "skills", "deep-interview", "SKILL.md"), "utf-8"); const markerIndex = skillDoc.indexOf("## Suggested Config (optional)"); assert.notEqual(markerIndex, -1); const configMatch = skillDoc.slice(markerIndex).match(/```toml\n([\s\S]*?)\n```/); assert.ok(configMatch); const documentedConfig = configMatch[1]?.trimEnd(); assert.ok(documentedConfig); assert.match(documentedConfig, /standardThreshold = 0\.20/); assert.match(documentedConfig, /standardMaxRounds = 12/); const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-doc-config-")); const sessionId = "sess-deep-interview-doc-config"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile(join(cwd, ".omx", "config.toml"), `${documentedConfig}\n`); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-doc-config", turn_id: "turn-doc-config", prompt: "$deep-interview prove documented config context", }, { cwd }, ); const serializedOutput = JSON.stringify(result.outputJson); const modeState = JSON.parse( await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"), ) as { deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number }; profile?: string; threshold?: number; max_rounds?: number; }; assert.equal(result.skillState?.deep_interview_config?.profile, "standard"); assert.equal(result.skillState?.deep_interview_config?.threshold, 0.2); assert.equal(result.skillState?.deep_interview_config?.maxRounds, 12); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /profile=standard/); assert.match(serializedOutput, /threshold=0\.2/); assert.match(serializedOutput, /max_rounds=12/); assert.equal(modeState.deep_interview_config?.profile, "standard"); assert.equal(modeState.profile, "standard"); assert.equal(modeState.threshold, 0.2); assert.equal(modeState.max_rounds, 12); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("injects deep-interview config overrides when state is boxed under OMX_ROOT", async () => { const root = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-boxed-")); const cwd = join(root, "source"); const omxRoot = join(root, "box"); const sessionId = "sess-boxed-deep-interview-config"; const previousOmxRoot = process.env.OMX_ROOT; const previousOmxStateRoot = process.env.OMX_STATE_ROOT; const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "config.toml"), `[omx.deepInterview] defaultProfile = "standard" standardThreshold = 0.05 standardMaxRounds = 15 `, ); process.env.OMX_ROOT = omxRoot; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-boxed", turn_id: "turn-boxed", prompt: "$deep-interview prove boxed config reflection", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.initialized_state_path, `.omx/state/sessions/${sessionId}/deep-interview-state.json`); const boxedStatePath = join(omxRoot, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"); assert.equal(existsSync(boxedStatePath), true); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json")), false); const serializedOutput = JSON.stringify(result.outputJson); assert.match(serializedOutput, /Deep-interview config override active/); assert.match(serializedOutput, /threshold=0\.05/); assert.match(serializedOutput, /max_rounds=15/); } finally { if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot; else delete process.env.OMX_ROOT; if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot; else delete process.env.OMX_STATE_ROOT; if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(root, { recursive: true, force: true }); } }); it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => { const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-")); const cwd = join(root, "source"); const omxRoot = join(root, "box"); const sessionId = "sess-boxed-ralplan"; const previousOmxRoot = process.env.OMX_ROOT; const previousOmxStateRoot = process.env.OMX_STATE_ROOT; const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const previousOmxSessionId = process.env.OMX_SESSION_ID; try { await mkdir(cwd, { recursive: true }); process.env.OMX_ROOT = omxRoot; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; process.env.OMX_SESSION_ID = sessionId; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-boxed", turn_id: "turn-boxed", prompt: "$ralplan implement issue #1307", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralplan"); const boxedSessionDir = join(omxRoot, ".omx", "state", "sessions", sessionId); assert.equal(existsSync(join(boxedSessionDir, "skill-active-state.json")), true); assert.equal(existsSync(join(boxedSessionDir, "ralplan-state.json")), true); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false); const hudState = await readAllState(cwd); assert.equal(hudState.ralplan?.active, true); assert.equal(hudState.ralplan?.current_phase, "planning"); } finally { if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot; else delete process.env.OMX_ROOT; if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot; else delete process.env.OMX_STATE_ROOT; if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId; else delete process.env.OMX_SESSION_ID; await rm(root, { recursive: true, force: true }); } }); it("records native keyword activation mode detail and skill state under OMX_TEAM_STATE_ROOT", async () => { const root = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-")); const cwd = join(root, "source"); const teamStateRoot = join(root, "team-state"); const sessionId = "sess-team-root-ralplan"; const previousOmxRoot = process.env.OMX_ROOT; const previousOmxStateRoot = process.env.OMX_STATE_ROOT; const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const previousOmxSessionId = process.env.OMX_SESSION_ID; try { await mkdir(cwd, { recursive: true }); delete process.env.OMX_ROOT; delete process.env.OMX_STATE_ROOT; process.env.OMX_TEAM_STATE_ROOT = teamStateRoot; process.env.OMX_SESSION_ID = sessionId; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-team-root", turn_id: "turn-team-root", prompt: "$ralplan implement issue #1307", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralplan"); const teamSessionDir = join(teamStateRoot, "sessions", sessionId); assert.equal(existsSync(join(teamSessionDir, "skill-active-state.json")), true); assert.equal(existsSync(join(teamSessionDir, "ralplan-state.json")), true); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false); const hudState = await readAllState(cwd); assert.equal(hudState.ralplan?.active, true); assert.equal(hudState.ralplan?.current_phase, "planning"); } finally { if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot; else delete process.env.OMX_ROOT; if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot; else delete process.env.OMX_STATE_ROOT; if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId; else delete process.env.OMX_SESSION_ID; await rm(root, { recursive: true, force: true }); } }); it("classifies only actionable goal completion wording", () => { const actionable = [ "complete this goal now", "Performance goal complete; next call update_goal({status: \"complete\"}).", "get_goal returned a completed legacy goal, so ultragoal complete failed; marking complete now.", "omx ultragoal checkpoint --goal-id G001-demo --status complete --codex-goal-json goal.json", "Call update_goal({status: \"complete\"}) after verification.", "Goal complete.", "The goal is complete.", "Goal complete: verified with tests.", "Goal complete — verified with tests.", "The goal is complete: verified.", "The goal is complete — verified.", ]; const ordinary = [ "my goal is to complete the migration without regressions", "Our goal is to finish this carefully after tests pass.", "The goal of this patch is to close a review gap.", "A goal can be complete only after a human review.", ]; for (const text of actionable) { assert.equal(looksLikeGoalCompletionPrompt(text), true, text); } for (const text of ordinary) { assert.equal(looksLikeGoalCompletionPrompt(text), false, text); } }); it("warns completion-like prompts when active goal workflows need Codex snapshot reconciliation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-warning-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, activeGoalId: "G001-demo", goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-goal-warning", thread_id: "thread-goal-warning", prompt: "complete this goal now", }, { cwd }); assert.match(JSON.stringify(result.outputJson), /requires Codex goal snapshot reconciliation/); assert.match(JSON.stringify(result.outputJson), /get_goal/); assert.match(JSON.stringify(result.outputJson), /--codex-goal-json/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop when a completion-like final answer skips active goal snapshot reconciliation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), { version: 1, workflow: "performance-goal", slug: "latency", objective: "Reduce latency", status: "validation_passed", }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-goal-stop", thread_id: "thread-goal-stop", last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.equal(result.outputJson?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); assert.match(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/); assert.match(JSON.stringify(result.outputJson), /Hooks must not mutate Codex goal state/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), { version: 1, workflow: "performance-goal", slug: "latency", objective: "Reduce latency", status: "blocked", lastValidation: { status: "blocked", evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".", recordedAt: "2026-05-20T00:00:00.000Z", }, }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-performance-mismatch-blocked-stop", thread_id: "thread-performance-mismatch-blocked-stop", last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.notEqual(result.outputJson?.decision, "block"); assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/); assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop for an already complete performance-goal state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), { version: 1, workflow: "performance-goal", slug: "latency", objective: "Reduce latency", status: "complete", completedAt: "2026-05-20T00:00:00.000Z", }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-performance-complete-stop", thread_id: "thread-performance-complete-stop", last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.notEqual(result.outputJson?.decision, "block"); assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/); assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("nudges next-goal prompts when a completed Codex goal cleanup step remains", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-prompt-")); try { await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), { version: 1, workflow: "performance-goal", slug: "latency", objective: "Reduce latency", status: "complete", completedAt: "2026-05-20T00:00:00.000Z", }); const result = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-completed-goal-prompt", thread_id: "thread-completed-goal-prompt", prompt: "Start the next performance goal now", }, { cwd }); const output = JSON.stringify(result.outputJson); assert.match(output, /run \/goal clear/); assert.match(output, /before calling create_goal/); assert.match(output, /hooks only nudge and must not mutate Codex goal state/); assert.doesNotMatch(output, /cleared Codex goal state/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block ordinary Stop after completed Ultragoal cleanup remains", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-ultragoal-ordinary-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" }, goals: [{ id: "G001-done", status: "complete", objective: "Done" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-completed-ultragoal-ordinary-stop", thread_id: "thread-completed-ultragoal-ordinary-stop", last_assistant_message: "Implemented the requested fix and verified the focused tests.", }, { cwd }); const output = JSON.stringify(result.outputJson); assert.notEqual(result.outputJson?.decision, "block"); assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required"); assert.doesNotMatch(output, /run \/goal clear/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat explanatory create_goal warnings as completed-goal cleanup attempts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-explainer-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" }, goals: [{ id: "G001-done", status: "complete", objective: "Done" }], }); for (const [index, last_assistant_message] of [ "Do not call create_goal until the user has explicitly cleared the old goal state.", "No create_goal attempt was made; this is only the final summary.", "I am not calling create_goal; the completed work is summarized above.", "I am not starting another goal; cleanup is already documented.", "Do not start another goal until cleanup is explicit.", "Do not create a new ultragoal; this is only a final summary.", ].entries()) { const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: `sess-completed-goal-explainer-stop-${index}`, thread_id: `thread-completed-goal-explainer-stop-${index}`, last_assistant_message, }, { cwd }); const output = JSON.stringify(result.outputJson); assert.notEqual(result.outputJson?.decision, "block"); assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required"); assert.doesNotMatch(output, /run \/goal clear/); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks explicit create_goal attempts when fresh native-goal cleanup evidence remains", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-mixed-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" }, goals: [{ id: "G001-done", status: "complete", objective: "Done" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-completed-goal-mixed-stop", thread_id: "thread-completed-goal-mixed-stop", last_user_message: "get_goal reports a completed Codex goal still attached to this thread; do not call create_goal until cleanup is explicit.", last_assistant_message: "I am starting another run now; create_goal payload follows.", }, { cwd }); const output = JSON.stringify(result.outputJson); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required"); assert.match(output, /run \/goal clear/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop when completed durable history alone precedes another goal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" }, goals: [{ id: "G001-done", status: "complete", objective: "Done" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-completed-goal-stop", thread_id: "thread-completed-goal-stop", last_assistant_message: "Starting another ultragoal now; create_goal payload follows after /goal clear already cleared native state.", }, { cwd }); const output = JSON.stringify(result.outputJson); assert.notEqual(result.outputJson?.decision, "block"); assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required"); assert.doesNotMatch(output, /run \/goal clear/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks ultragoal Stop for concise generic goal completion claims", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, activeGoalId: "G001-demo", goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-ultragoal-generic-complete-stop", thread_id: "thread-ultragoal-generic-complete-stop", last_assistant_message: "Goal complete.", }, { cwd }); assert.equal(result.outputJson?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block ultragoal Stop for ordinary prose about a goal to complete work", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-ordinary-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, activeGoalId: "G001-demo", goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-ultragoal-ordinary-stop", thread_id: "thread-ultragoal-ordinary-stop", last_assistant_message: "My goal is to complete the migration without regressions, so I will keep testing.", }, { cwd }); assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required"); assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, activeGoalId: "G001-demo", goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-ultragoal-legacy-stop", thread_id: "thread-ultragoal-legacy-stop", last_assistant_message: "get_goal returned a completed legacy goal, so ultragoal complete failed; marking complete now.", }, { cwd }); const output = JSON.stringify(result.outputJson); assert.equal(result.outputJson?.decision, "block"); assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/); assert.match(output, /--status blocked/); assert.match(output, /Codex goal context/); assert.match(output, /no such table: thread_goals/); assert.match(output, /unavailable get_goal error JSON or path/); assert.match(output, /safe-recovery blocker/); assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i); assert.match(output, /Hooks must not mutate Codex goal state/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not repeat ultragoal Stop recovery after a safe completed-aggregate microgoal blocker is recorded", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-aggregate-blocked-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, codexGoalMode: "aggregate", activeGoalId: "G001-demo", goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal", failureReason: "aggregate Codex goal already complete and unreconcilable while repo-native .omx/ultragoal/goals.json still has an in-progress microgoal; stop the recovery loop", }], }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-ultragoal-aggregate-blocked-stop", thread_id: "thread-ultragoal-aggregate-blocked-stop", stop_hook_active: true, last_assistant_message: "Goal complete.", }, { cwd }); assert.notEqual(result.outputJson?.decision, "block"); assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required"); assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block ultragoal Stop after task-scoped reconciliation finishes exploded bookkeeping", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-reconciled-stop-")); try { await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), { version: 1, codexGoalMode: "aggregate", codexObjective: "Complete the durable ultragoal plan in .omx/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omx/ultragoal/ledger.jsonl as the audit trail.", activeGoalId: "G001-micro", aggregateCompletion: { status: "complete", completedAt: "2026-05-04T10:04:00.000Z", evidence: "planned work done; validation complete; reviews clean", }, goals: Array.from({ length: 136 }, (_, index) => ({ id: `G${String(index + 1).padStart(3, "0")}-micro`, status: index === 0 ? "in_progress" : "pending", objective: `Synthetic slice ${index + 1}.`, })), }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-ultragoal-reconciled-stop", thread_id: "thread-ultragoal-reconciled-stop", last_assistant_message: "Yes — planned implementation work is done; ultragoal bookkeeping reconciled complete.", }, { cwd }); assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required"); assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop for non-passing autoresearch-goal professor-critic verdicts", async () => { for (const verdict of ["blocked", "fail", "failed"]) { const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-autoresearch-${verdict}-stop-`)); const slug = `${verdict}-mission`; try { await writeJson(join(cwd, ".omx", "goals", "autoresearch", slug, "mission.json"), { version: 1, workflow: "autoresearch-goal", slug, topic: "Blocked research", status: verdict === "blocked" ? "blocked" : "failed", }); await writeJson(join(cwd, ".omx", "goals", "autoresearch", slug, "completion.json"), { verdict, passed: false, }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: `sess-autoresearch-${verdict}-stop`, thread_id: `thread-autoresearch-${verdict}-stop`, last_assistant_message: "Autoresearch goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.notEqual(result.outputJson?.decision, "block"); assert.doesNotMatch(JSON.stringify(result.outputJson), new RegExp(`autoresearch-goal complete --slug ${slug}`)); assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); } finally { await rm(cwd, { recursive: true, force: true }); } } }); it("blocks Stop for passing autoresearch-goal professor-critic verdicts that need reconciliation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-pass-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "autoresearch", "passing-mission", "mission.json"), { version: 1, workflow: "autoresearch-goal", slug: "passing-mission", topic: "Passing research", status: "validation_passed", }); await writeJson(join(cwd, ".omx", "goals", "autoresearch", "passing-mission", "completion.json"), { verdict: "fail", passed: true, }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-autoresearch-pass-stop", thread_id: "thread-autoresearch-pass-stop", last_assistant_message: "Autoresearch goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.equal(result.outputJson?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); assert.match(JSON.stringify(result.outputJson), /omx autoresearch-goal complete --slug passing-mission/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop for autoresearch-goal verdict=pass even when passed is omitted", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-verdict-pass-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "autoresearch", "verdict-pass-mission", "mission.json"), { version: 1, workflow: "autoresearch-goal", slug: "verdict-pass-mission", topic: "Passing research", status: "validation_passed", }); await writeJson(join(cwd, ".omx", "goals", "autoresearch", "verdict-pass-mission", "completion.json"), { verdict: "pass", }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-autoresearch-verdict-pass-stop", thread_id: "thread-autoresearch-verdict-pass-stop", last_assistant_message: "Autoresearch goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.equal(result.outputJson?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /omx autoresearch-goal complete --slug verdict-pass-mission/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not repeat Stop block when the last autoresearch-goal completion attempt reported objective mismatch", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-mismatch-reported-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "autoresearch", "mismatched-mission", "mission.json"), { version: 1, workflow: "autoresearch-goal", slug: "mismatched-mission", topic: "Passing research bound to another Codex goal", status: "passed", }); await writeJson(join(cwd, ".omx", "goals", "autoresearch", "mismatched-mission", "completion.json"), { verdict: "pass", passed: true, }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-autoresearch-mismatch-reported-stop", thread_id: "thread-autoresearch-mismatch-reported-stop", last_assistant_message: [ "I called get_goal and ran omx autoresearch-goal complete --slug mismatched-mission --codex-goal-json /tmp/snapshot.json.", "The autoresearch-goal completion failed with Codex goal objective mismatch, so I will not repeat the same complete command blindly in this thread.", ].join("\n"), }, { cwd }); assert.notEqual(result.outputJson?.decision, "block"); assert.doesNotMatch(JSON.stringify(result.outputJson), /autoresearch-goal complete --slug mismatched-mission/); assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("still blocks later autoresearch-goal completion claims after an objective mismatch if no mismatch is reported in the final answer", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-mismatch-later-retry-stop-")); try { await writeJson(join(cwd, ".omx", "goals", "autoresearch", "retryable-mission", "mission.json"), { version: 1, workflow: "autoresearch-goal", slug: "retryable-mission", topic: "Passing research that can still retry with the correct snapshot", status: "passed", }); await writeJson(join(cwd, ".omx", "goals", "autoresearch", "retryable-mission", "completion.json"), { verdict: "pass", passed: true, }); const result = await dispatchCodexNativeHook({ hook_event_name: "Stop", cwd, session_id: "sess-autoresearch-mismatch-later-retry-stop", thread_id: "thread-autoresearch-mismatch-later-retry-stop", last_assistant_message: "Autoresearch goal complete; next call update_goal({status: \"complete\"}).", }, { cwd }); assert.equal(result.outputJson?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/); assert.match(JSON.stringify(result.outputJson), /omx autoresearch-goal complete --slug retryable-mission/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats workflow keywords in native subagent prompt text as literal delegation text", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-keyword-literal-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-parent"; const leaderNativeSessionId = "native-parent-thread"; const childNativeSessionId = "native-child-thread"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [childNativeSessionId]: { thread_id: childNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "architect", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "turn-child-1", prompt: "$ralplan Architect review step. Review the draft plan and return APPROVE or ITERATE.", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal(existsSync(join(stateDir, "skill-active-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", childNativeSessionId, "ralplan-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not activate Conductor guidance for typed agent-role prompts without native subagent tracking", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-typed-agent-role-autopilot-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-typed-executor", thread_id: "thread-typed-executor", agent_role: "executor", turn_id: "turn-typed-executor", prompt: "$autopilot continue the current implementation lane", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-typed-executor", "autopilot-state.json")), false, ); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-typed-executor", "skill-active-state.json")), false, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats installed custom native agent roles as typed lanes for prompt submit", async () => { await withIsolatedHome("custom-native-agent-role", async (homeDir) => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-custom-agent-role-autopilot-")); try { const agentsDir = join(homeDir, ".codex", "agents"); await mkdir(agentsDir, { recursive: true }); await writeFile( join(agentsDir, "custom-executor.toml"), [ '# oh-my-codex agent: custom-executor', 'name = "custom-executor"', 'description = "Custom executor lane"', ].join("\n"), "utf-8", ); await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-custom-executor", thread_id: "thread-custom-executor", agent_type: "custom-executor", turn_id: "turn-custom-executor", prompt: "$autopilot continue the current implementation lane", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-custom-executor", "autopilot-state.json")), false, ); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-custom-executor", "skill-active-state.json")), false, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); }); it("does not inject Conductor guidance into typed agent-role autopilot continuations", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-typed-agent-role-active-autopilot-")); try { const sessionId = "sess-typed-executor-active"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", keyword: "$autopilot", phase: "planning", initialized_mode: "autopilot", initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`, session_id: sessionId, active_skills: [ { skill: "autopilot", phase: "planning", active: true, session_id: sessionId }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "execution", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-typed-executor-active", turn_id: "turn-typed-executor-active", agent_role: "executor", prompt: "keep going now", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.equal(result.omxEventName, "keyword-detector"); assert.doesNotMatch(message, /Conductor mode contract:/); assert.doesNotMatch(message, /Golden Rule: When the Main agent is acting in Conductor mode/); assert.doesNotMatch(message, /Conductor reuse and ledger guidance:/); assert.doesNotMatch(message, /typed subagents never receive this block/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat a corrupt leader kind=subagent tracker entry as native subagent prompt scope", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-corrupt-leader-subagent-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-corrupt-leader"; const leaderNativeSessionId = "native-corrupt-leader"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 2, }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: leaderNativeSessionId, thread_id: leaderNativeSessionId, turn_id: "turn-corrupt-leader", prompt: "$autopilot continue this review blocker fix", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); assert.equal( existsSync(join(stateDir, "sessions", canonicalSessionId, "autopilot-state.json")), true, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("lets the current canonical leader boundary beat stale global subagent tracking with a distinct prompt thread id", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-current-leader-stale-global-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-current-leader"; const leaderNativeSessionId = "native-current-leader"; const staleSessionId = "sess-stale-subagent"; const staleLeaderNativeSessionId = "native-stale-leader"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, }, }, [staleSessionId]: { session_id: staleSessionId, leader_thread_id: staleLeaderNativeSessionId, updated_at: nowIso, threads: { [staleLeaderNativeSessionId]: { thread_id: staleLeaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "architect", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: leaderNativeSessionId, thread_id: "thread-current-turn-not-native-session", turn_id: "turn-current-leader", prompt: "$autopilot continue", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); assert.equal( existsSync(join(stateDir, "sessions", canonicalSessionId, "autopilot-state.json")), true, ); assert.equal( existsSync(join(stateDir, "sessions", staleSessionId, "autopilot-state.json")), false, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("lets the current session native leader beat stale global subagent tracking without a canonical summary and with a distinct prompt thread id", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-current-native-leader-stale-global-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-current-native-leader"; const leaderNativeSessionId = "native-current-leader-no-summary"; const staleSessionId = "sess-stale-native-subagent"; const staleLeaderNativeSessionId = "native-stale-parent"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [staleSessionId]: { session_id: staleSessionId, leader_thread_id: staleLeaderNativeSessionId, updated_at: nowIso, threads: { [staleLeaderNativeSessionId]: { thread_id: staleLeaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "critic", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: leaderNativeSessionId, thread_id: "thread-current-turn-not-native-session", turn_id: "turn-current-native-leader", prompt: "$autopilot continue", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); assert.equal( existsSync(join(stateDir, "sessions", canonicalSessionId, "autopilot-state.json")), true, ); assert.equal( existsSync(join(stateDir, "sessions", staleSessionId, "autopilot-state.json")), false, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("lets the current session native leader beat a malformed canonical subagent entry", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-current-native-leader-malformed-canonical-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-current-native-leader-malformed"; const leaderNativeSessionId = "native-current-leader-malformed"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "architect", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: leaderNativeSessionId, thread_id: leaderNativeSessionId, turn_id: "turn-current-native-leader-malformed", prompt: "$autopilot continue", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); assert.equal( existsSync(join(stateDir, "sessions", canonicalSessionId, "autopilot-state.json")), true, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("still treats mixed child and leader payload identities as native subagent scope", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-mixed-child-leader-identity-")); try { const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-mixed-child-leader"; const leaderNativeSessionId = "native-mixed-leader"; const childNativeSessionId = "native-mixed-child"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [childNativeSessionId]: { thread_id: childNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "critic", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: childNativeSessionId, thread_id: leaderNativeSessionId, turn_id: "turn-mixed-child-leader", prompt: "$ralplan review this as delegated text", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", childNativeSessionId, "ralplan-state.json")), false); const reversedResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: leaderNativeSessionId, thread_id: childNativeSessionId, turn_id: "turn-mixed-leader-child", prompt: "$autopilot review this as delegated text", }, { cwd }, ); assert.equal(reversedResult.omxEventName, "keyword-detector"); assert.equal(reversedResult.skillState, null); assert.equal(reversedResult.outputJson, null); assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "autopilot-state.json")), false); assert.equal(existsSync(join(stateDir, "sessions", childNativeSessionId, "autopilot-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("records plugin-prefixed keyword activation from UserPromptSubmit payloads", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-plugin-prefixed-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-plugin-1", thread_id: "thread-plugin-1", turn_id: "turn-plugin-1", prompt: "$oh-my-codex:ralplan implement issue #1307", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralplan"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-autopilot-ralplan-gate", thread_id: "thread-autopilot-ralplan-gate", turn_id: "turn-autopilot-ralplan-gate", prompt: "$autopilot implement issue #2430", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /Autopilot protocol:/); assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/); assert.match(message, /structured question chain, not a one-question gate/); assert.match(message, /re-score ambiguity against the active threshold/); assert.match(message, /max_rounds as a cap/); assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/); assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/); assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/); assert.match(message, /Conductor mode contract:/); assert.match(message, /Golden Rule: When the Main agent is acting in Conductor mode/); assert.match(message, /Conductor reuse and ledger guidance:/); assert.match(message, /typed subagents never receive this block/); const autopilotState = JSON.parse(await readFile( join(cwd, ".omx", "state", "sessions", "sess-autopilot-ralplan-gate", "autopilot-state.json"), "utf-8", )) as { state?: { handoff_artifacts?: { context_snapshot_path?: string } } }; const snapshotPath = autopilotState.state?.handoff_artifacts?.context_snapshot_path ?? ""; assert.match(snapshotPath, /^\.omx\/context\/implement-issue-2430-\d{8}T\d{6}Z\.md$/); const snapshot = await readFile(join(cwd, snapshotPath), "utf-8"); assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement issue #2430/); assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/); assert.match(snapshot, /constraints: follow deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultragoal-1", thread_id: "thread-ultragoal-1", turn_id: "turn-ultragoal-1", prompt: "$ultragoal split this launch into durable goals", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ultragoal"); assert.equal(result.skillState?.initialized_mode, "ultragoal"); assert.equal( result.skillState?.initialized_state_path, ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json", ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /"\$ultragoal" -> ultragoal/); assert.match(message, /Ultragoal protocol:/); assert.match(message, /get_goal/); assert.match(message, /create_goal/); assert.match(message, /update_goal/); assert.match(message, /does not call `\/goal clear`/); assert.match(message, /multiple sequential ultragoal runs/); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("deactivates active deep-interview state on explicit ultragoal handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-ultragoal-handoff", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-ultragoal-handoff", question_enforcement: { obligation_id: "obligation-ultragoal-handoff", source: "omx-question", status: "pending", requested_at: "2026-05-21T03:00:00.000Z", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultragoal-handoff", thread_id: "thread-ultragoal-handoff", turn_id: "turn-ultragoal-handoff", prompt: "$ultragoal turn the clarified spec into goals", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ultragoal"); assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/); const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean; current_phase?: string; question_enforcement?: { status?: string; clear_reason?: string }; }; assert.equal(completed.active, false); assert.equal(completed.current_phase, "completed"); assert.equal(completed.question_enforcement?.status, "cleared"); assert.equal(completed.question_enforcement?.clear_reason, "handoff"); assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true); const edit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ultragoal-handoff", thread_id: "thread-ultragoal-handoff", tool_name: "Edit", tool_use_id: "tool-ultragoal-post-handoff-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(edit.outputJson?.decision, "block"); assert.match(String(edit.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-")); try { await createUltragoalPlan(cwd, { brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture", goals: [{ title: "First", objective: "Complete first milestone with tests." }], }); const prose = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultragoal-steer-1", prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.", }, { cwd }, ); assert.equal(prose.outputJson, null); assert.equal((await readUltragoalPlan(cwd)).goals.length, 1); const jsonExample = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultragoal-steer-1", prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({ kind: "add_subgoal", source: "user_prompt_submit", evidence: "Example JSON should not mutate .omx/ultragoal.", rationale: "Only explicit steering fences or labels are executable.", title: "Inert JSON example", objective: "This example must not be added.", })}\n\`\`\``, }, { cwd }, ); assert.equal(jsonExample.outputJson, null); assert.equal((await readUltragoalPlan(cwd)).goals.length, 1); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultragoal-steer-1", prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({ kind: "add_subgoal", source: "user_prompt_submit", evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.", rationale: "Add bounded hook regression work while preserving all completion gates.", title: "Prompt bridge regression", objective: "Verify UserPromptSubmit bounded steering bridge with tests.", })}`, }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /bounded \.omx\/ultragoal steering/); assert.match(message, /G002-cli-and-prompt-submit-bridge/); assert.match(message, /accepted/); const plan = await readUltragoalPlan(cwd); assert.equal(plan.goals.length, 2); assert.equal(plan.goals[1]?.title, "Prompt bridge regression"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-")); try { await createUltragoalPlan(cwd, { brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture", goals: [{ title: "First", objective: "Complete first milestone with tests." }], }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-ultragoal-parent"; const leaderNativeSessionId = "native-ultragoal-parent"; const childNativeSessionId = "native-ultragoal-child"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [childNativeSessionId]: { thread_id: childNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "architect", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "turn-ultragoal-child-1", prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({ kind: "add_subgoal", source: "user_prompt_submit", evidence: "Subagent prompt text must be literal delegated context.", rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.", title: "Subagent should not add this", objective: "This must remain literal prompt text.", })}`, }, { cwd }, ); assert.equal(result.outputJson, null); const plan = await readUltragoalPlan(cwd); assert.equal(plan.goals.length, 1); const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8"); assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0); assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-")); try { await createUltragoalPlan(cwd, { brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture", goals: [{ title: "First", objective: "Complete first milestone with tests." }], }); const prompt = `\`\`\`omx-ultragoal-steer ${JSON.stringify({ kind: "add_subgoal", source: "user_prompt_submit", evidence: "Structured prompt-submit directive adds exactly one deduped goal.", rationale: "Use idempotent bridge semantics for repeated hook delivery.", title: "Deduped bridge regression", objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.", })} \`\`\``; await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd }); const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd }); const message = String( (second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /deduped/); const plan = await readUltragoalPlan(cwd); assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1); const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8"); assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ulw-ko", thread_id: "thread-ulw-ko", turn_id: "turn-ulw-ko", prompt: "ㅕㅣㅈ로 병렬 처리해줘", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ultrawork"); assert.equal(result.skillState?.keyword, "ulw"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(additionalContext, /workflow keyword \"ulw\" -> ultrawork/); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ulw-ko", "ultrawork-state.json")), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("adds ultrawork-specific activation guidance only for true ultrawork workflow activation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultrawork-routing-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ultrawork-msg", thread_id: "thread-ultrawork-msg", turn_id: "turn-ultrawork-msg", prompt: "$ultrawork fan out the regression checks", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ultrawork"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /\$ultrawork" -> ultrawork/); assert.match(message, /ground the task before editing/i); assert.match(message, /define pass\/fail acceptance criteria/i); assert.match(message, /direct-tool plus background evidence lanes/i); assert.match(message, /Ralph owns persistence and the full verified-completion promise/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not activate Ralph workflow state from a plain conversational mention", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-plain-text-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ralph-plain-text", thread_id: "thread-ralph-plain-text", turn_id: "turn-ralph-plain-text", prompt: "why does ralph keep blocking stop?", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); // Triage may inject advisory LIGHT/explore context for the question-shaped // prompt, but the invariant this test guards is that no Ralph workflow state // is seeded and no Ralph-activation message is emitted. const advisoryContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.doesNotMatch(advisoryContext, /skill:\s*ralph/i); assert.doesNotMatch(advisoryContext, /ralph-state\.json/i); assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ralph-plain-text", "skill-active-state.json")), false); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ralph-plain-text", "ralph-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("adds execution handoff context for non-keyword prompts that authorize implementation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-execution-handoff-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const prompts = [ "按照这个plan开始执行优化", "开始执行", "继续优化", "直接修复", ]; for (const [index, prompt] of prompts.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: `sess-exec-handoff-${index}`, thread_id: `thread-exec-handoff-${index}`, turn_id: `turn-exec-handoff-${index}`, prompt, }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /execution handoff/i, prompt); assert.match(message, /Do not restate the prior plan/i, prompt); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("adds latest-followup priority context for short same-thread follow-up prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-followup-priority-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-followup-priority", thread_id: "thread-followup-priority", turn_id: "turn-followup-priority", prompt: "这些优化都做了么", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /same-thread follow-up/i); assert.match(message, /prefer it over older unresolved prompts/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("clarifies that prompt-side $ralph activation does not invoke the PRD-gated CLI path", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-routing-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-ralph-msg", thread_id: "thread-ralph-msg", turn_id: "turn-ralph-msg", prompt: "$ralph continue verification", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralph"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /\$ralph" -> ralph/); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./); assert.match(message, /Use `omx ralph --prd \.\.\.` only when you explicitly want the PRD-gated CLI startup path\./); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("clarifies that plugin-prefixed prompt-side $ralph activation does not invoke the PRD-gated CLI path", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-plugin-ralph-routing-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-plugin-ralph-msg", thread_id: "thread-plugin-ralph-msg", turn_id: "turn-plugin-ralph-msg", prompt: "$oh-my-codex:ralph continue verification", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralph"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /\$oh-my-codex:ralph" -> ralph/); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps bare keep-going continuation on the active autopilot skill instead of denying with generic ralph overlap", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-bare-continuation-")); try { const sessionId = "sess-autopilot-cont"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", keyword: "$autopilot", phase: "planning", session_id: sessionId, active_skills: [ { skill: "autopilot", phase: "planning", active: true, session_id: sessionId }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "execution", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-autopilot-cont", turn_id: "turn-autopilot-cont", prompt: "\ keep going now", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /"keep going" -> ralph/); assert.match(message, /Autopilot protocol:/); assert.match(message, /structured question chain, not a one-question gate/); assert.match(message, /re-score ambiguity against the active threshold/); assert.match(message, /max_rounds as a cap/); assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/); assert.doesNotMatch(message, /denied workflow keyword/i); assert.doesNotMatch(message, /Unsupported workflow overlap: autopilot \+ ralph\./); assert.doesNotMatch(message, /Prompt-side `\$ralph` activation/); assert.equal(existsSync(join(sessionDir, "ralph-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps omx question answers on the active autopilot skill so the interview chain guidance is injected", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-question-answer-continuation-")); try { const sessionId = "sess-autopilot-question-answer"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", keyword: "$autopilot", phase: "deep-interview", initialized_mode: "autopilot", initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`, session_id: sessionId, active_skills: [ { skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "deep-interview", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-autopilot-question-answer", turn_id: "turn-autopilot-question-answer", prompt: "[omx question answered] semantic_marker_expansion $ralplan", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "autopilot"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /continued active workflow skill "autopilot"/); assert.match(message, /Autopilot protocol:/); assert.match(message, /structured question chain, not a one-question gate/); assert.match(message, /This turn is a marked omx question answer/); assert.match(message, /then re-score/); assert.match(message, /write interview_complete evidence and hand off/); assert.match(message, /readiness gate remains unresolved and the answer would materially change execution/); assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/); assert.doesNotMatch(message, /denied workflow keyword/i); assert.equal(existsSync(join(sessionDir, "ralplan-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps deep-interview bridge guidance on marked question answers with workflow-like tokens", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-question-answer-continuation-")); try { const sessionId = "sess-deep-interview-question-answer"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", keyword: "$deep-interview", phase: "planning", initialized_mode: "deep-interview", initialized_state_path: `.omx/state/sessions/${sessionId}/deep-interview-state.json`, session_id: sessionId, active_skills: [ { skill: "deep-interview", phase: "planning", active: true, session_id: sessionId }, ], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", started_at: "2026-04-21T10:00:00.000Z", updated_at: "2026-04-21T10:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-deep-interview-question-answer", turn_id: "turn-deep-interview-question-answer", prompt: "[omx question answered] answer text $ralplan", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /continued active workflow skill "deep-interview"/); assert.match(message, /workflow-like tokens inside the marked omx question answer are treated as answer text/); assert.match(message, /Deep-interview is active, but this session is not attached to tmux/); assert.match(message, /native structured question tool when available/); assert.doesNotMatch(message, /detected workflow keyword "\$ralplan" -> ralplan/); assert.equal(existsSync(join(sessionDir, "ralplan-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("clarifies outside-tmux prompt-side deep-interview activation without pretending omx question is directly available", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-routing-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-deep-interview-msg", thread_id: "thread-deep-interview-msg", turn_id: "turn-deep-interview-msg", prompt: "$deep-interview gather requirements", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /\$deep-interview" -> deep-interview/); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.match(message, /Deep-interview is active, but this session is not attached to tmux/); assert.match(message, /Do not invoke `omx question`, `omx hud`, or `omx team`/); assert.match(message, /native structured question tool when available/); assert.match(message, /ask exactly one concise plain-text question/); assert.match(message, /no tmux question obligation should be created outside tmux/); assert.doesNotMatch(message, /OMX_QUESTION_RETURN_PANE=/); assert.doesNotMatch(message, /preserve the leader pane/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("uses native fallback deep-interview guidance on Windows outside tmux", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-routing-win32-")); const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); try { Object.defineProperty(process, "platform", { value: "win32", configurable: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-deep-interview-msg-win32", thread_id: "thread-deep-interview-msg-win32", turn_id: "turn-deep-interview-msg-win32", prompt: "$deep-interview gather requirements", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /Deep-interview is active, but this session is not attached to tmux/); assert.match(message, /native structured question tool when available/); assert.doesNotMatch(message, /OMX_QUESTION_RETURN_PANE=/); assert.doesNotMatch(message, /current-session CLI bridge command/); } finally { if (originalPlatform) Object.defineProperty(process, "platform", originalPlatform); await rm(cwd, { recursive: true, force: true }); } }); it("includes leader-pane preservation guidance when a pane hint is available", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-pane-hint-")); try { const sessionId = "sess-deep-interview-pane-hint"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", started_at: "2026-04-21T10:00:00.000Z", updated_at: "2026-04-21T10:00:00.000Z", tmux_pane_id: "%77", }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-deep-interview-pane-hint", turn_id: "turn-deep-interview-pane-hint", prompt: "$deep-interview gather requirements", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /not attached to tmux/); assert.match(message, /native structured question tool when available/); assert.match(message, /tmux return bridge \(%77\) is recorded/); assert.doesNotMatch(message, /current-session CLI bridge command/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("uses native fallback guidance on Windows when a pane hint is available", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-pane-hint-win32-")); const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform"); try { Object.defineProperty(process, "platform", { value: "win32", configurable: true }); const sessionId = "sess-deep-interview-pane-hint-win32"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", started_at: "2026-04-21T10:00:00.000Z", updated_at: "2026-04-21T10:00:00.000Z", tmux_pane_id: "%77", }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-deep-interview-pane-hint-win32", turn_id: "turn-deep-interview-pane-hint-win32", prompt: "$deep-interview gather requirements", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "deep-interview"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /not attached to tmux/); assert.match(message, /native structured question tool when available/); assert.match(message, /tmux return bridge \(%77\) is recorded/); assert.doesNotMatch(message, /OMX_QUESTION_RETURN_PANE=/); assert.doesNotMatch(message, /PowerShell\/background-terminal/); } finally { if (originalPlatform) Object.defineProperty(process, "platform", originalPlatform); await rm(cwd, { recursive: true, force: true }); } }); it("keeps bare keep-going continuation on the active ralph skill without resetting through generic keep-going routing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-bare-continuation-")); try { const sessionId = "sess-ralph-cont"; const sessionDir = join(cwd, ".omx", "state", "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "ralph", keyword: "$ralph", phase: "executing", session_id: sessionId, active_skills: [ { skill: "ralph", phase: "executing", active: true, session_id: sessionId }, ], }); await writeJson(join(sessionDir, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "verifying", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", iteration: 4, max_iterations: 50, session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-ralph-cont", turn_id: "turn-ralph-cont", prompt: "keep going now", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "ralph"); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "", ); assert.match(message, /"keep going" -> ralph/); assert.doesNotMatch(message, /denied workflow keyword/i); assert.doesNotMatch(message, /mode transiting:/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("ignores generic wrapper fields so metadata cannot trigger workflow routing or Stop blocking", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-wrapper-metadata-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const promptResult = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-wrapper-meta-1", thread_id: "thread-wrapper-meta-1", turn_id: "turn-wrapper-meta-1", input: "$ralplan hidden wrapper text should stay non-routing", text: JSON.stringify({ hook_run_id: "native-stop-wrapper-1", note: "cancel stop wrapper metadata must not be treated like user intent", }), }, { cwd }, ); assert.equal(promptResult.omxEventName, "keyword-detector"); assert.equal(promptResult.skillState, null); assert.equal(promptResult.outputJson, null); assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); const stopResult = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-wrapper-meta-1", thread_id: "thread-wrapper-meta-1", turn_id: "turn-wrapper-meta-2", }, { cwd }, ); assert.equal(stopResult.omxEventName, "stop"); assert.equal(stopResult.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not expose submitted prompt text to keyword-detector hook plugins", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-prompt-sanitized-")); try { await mkdir(join(cwd, ".omx", "hooks"), { recursive: true }); await writeFile( join(cwd, ".omx", "hooks", "capture-keyword-context.mjs"), `import { mkdir, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; export async function onHookEvent(event) { if (event.event !== "keyword-detector") return; const outPath = join(process.cwd(), ".omx", "captured-keyword-context.json"); await mkdir(dirname(outPath), { recursive: true }); await writeFile(outPath, JSON.stringify(event.context, null, 2)); } `, "utf-8", ); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-sanitized-1", thread_id: "thread-sanitized-1", turn_id: "turn-sanitized-1", prompt: "$ralplan approve this blocker-sensitive request", }, { cwd }, ); const captured = JSON.parse( await readFile(join(cwd, ".omx", "captured-keyword-context.json"), "utf-8"), ) as { prompt?: string; payload?: Record }; assert.equal(captured.prompt, undefined); assert.equal(captured.payload?.prompt, undefined); assert.equal(captured.payload?.input, undefined); assert.equal(captured.payload?.user_prompt, undefined); assert.equal(captured.payload?.userPrompt, undefined); assert.equal(captured.payload?.text, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-unknown-1", thread_id: "thread-unknown-1", turn_id: "turn-unknown-1", prompt: "$maer-thinking 다시 설명해봐", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not emit UserPromptSubmit routing context for unknown plugin-prefixed $tokens", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-plugin-token-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-unknown-plugin-1", thread_id: "thread-unknown-plugin-1", turn_id: "turn-unknown-plugin-1", prompt: "$oh-my-codex:maer-thinking 다시 설명해봐", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState, null); assert.equal(result.outputJson, null); assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("denies direct $team prompt activation from Codex App/native outside tmux", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-native-block-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, source: "codex-app", session_id: "sess-team-1", thread_id: "thread-team-1", turn_id: "turn-team-1", prompt: "$team ship this fix with verification", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "team"); assert.equal(result.skillState?.active, false); assert.match(String(result.skillState?.transition_error || ""), /cannot activate the tmux-only `team` workflow directly/); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null)?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(message, /denied workflow keyword "\$team" -> team/); assert.match(message, /attached tmux shell first/); assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("still denies direct $team prompt activation from Codex App/native outside tmux when a tmux return bridge exists", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-native-bridge-block-")); try { await mkdir(join(cwd, ".omx", "state", "sessions", "sess-team-bridge"), { recursive: true }); await writeJson(join(cwd, ".omx", "state", "sessions", "sess-team-bridge", "ralph-state.json"), { mode: "ralph", active: true, tmux_pane_id: "%42", }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, source: "codex-app", session_id: "sess-team-bridge", thread_id: "thread-team-bridge", turn_id: "turn-team-bridge", prompt: "$team ship this fix with verification", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(result.skillState?.skill, "team"); assert.equal(result.skillState?.active, false); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null)?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(message, /attached tmux shell first/); assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps direct CLI outside-tmux $team prompt guidance compatible with manual shell launch", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-cli-guidance-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, source: "cli", session_id: "sess-team-cli-guidance", thread_id: "thread-team-cli-guidance", turn_id: "turn-team-cli-guidance", prompt: "$team ship this fix with verification", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } } | null)?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(message, /run `omx team \.\.\.` yourself from shell/); assert.doesNotMatch(message, /not directly available here/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps $team prompt-submit routing directly tmux-capable when already inside tmux", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-tmux-")); process.env.TMUX = "/tmp/tmux-live"; process.env.TMUX_PANE = "%5"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-team-tmux-1", thread_id: "thread-team-tmux-1", turn_id: "turn-team-tmux-1", prompt: "$team ship this fix with verification", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(message, /Use the durable OMX team runtime via `omx team \.\.\.`/); assert.match(message, /run `omx team --help` yourself/); assert.doesNotMatch(message, /not directly available here/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns actionable denial guidance for unsupported workflow overlaps on prompt submit", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-deny-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-deny-1", thread_id: "thread-deny-1", turn_id: "turn-deny-1", prompt: "$team ship this fix", }, { cwd }, ); const denied = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-deny-1", thread_id: "thread-deny-1", turn_id: "turn-deny-2", prompt: "$autopilot also run this", }, { cwd }, ); assert.match(JSON.stringify(denied.outputJson), /denied workflow keyword/i); assert.match(JSON.stringify(denied.outputJson), /Unsupported workflow overlap: team \+ autopilot\./); assert.match(JSON.stringify(denied.outputJson), /omx state clear --input/); assert.match(JSON.stringify(denied.outputJson), /mode\\":\\"/); assert.match(JSON.stringify(denied.outputJson), /--json/); assert.match(JSON.stringify(denied.outputJson), /explicit MCP compatibility is enabled/); assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` tools/); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-deny-1", "autopilot-state.json")), false, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("surfaces transition success output for allowlisted prompt-submit handoffs", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-success-")); try { const sessionDir = join(cwd, ".omx", "state", "sessions", "sess-handoff-1"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", deep_interview_gate: { status: "complete", rationale: "Requirements are clarified and ready for ralplan consensus.", }, }); await writeJson(join(sessionDir, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", session_id: "sess-handoff-1", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-handoff-1" }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-handoff-1", thread_id: "thread-handoff-1", turn_id: "turn-handoff-1", prompt: "$ralplan implement the approved contract", }, { cwd }, ); assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ralplan/); const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean; current_phase?: string; }; assert.equal(completed.active, false); assert.equal(completed.current_phase, "completed"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps the planning skill active when planning and execution workflows are invoked together", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-planning-precedence-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-multi-1", thread_id: "thread-multi-1", turn_id: "turn-multi-1", prompt: "$ralplan $team $ralph ship this fix", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || '', ); assert.match(message, /\$ralplan" -> ralplan/); assert.match(message, /\$team" -> team/); assert.match(message, /\$ralph" -> ralph/); assert.doesNotMatch(message, /mode transiting:/); assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); assert.doesNotMatch(message, /Use the durable OMX team runtime via `omx team \.\.\.`/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps the planning skill active for mixed plugin-prefixed and bare workflow invocations together", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-plugin-planning-precedence-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-plugin-multi-1", thread_id: "thread-plugin-multi-1", turn_id: "turn-plugin-multi-1", prompt: "$oh-my-codex:ralplan $team $oh-my-codex:ralph ship this fix", }, { cwd }, ); const message = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || '', ); assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/); assert.match(message, /\$team" -> team/); assert.match(message, /\$oh-my-codex:ralph" -> ralph/); assert.doesNotMatch(message, /mode transiting:/); assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./); assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '' --json`/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("skips prompt-submit HUD reconciliation for confirmed team worker panes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-worker-skip-")); try { const teamName = "hud-worker-skip"; await initTeamState(teamName, "skip worker HUD reconcile", "executor", 1, cwd); await setTeamPaneIds(cwd, teamName, { leaderPaneId: "%42", workerPaneIds: { "worker-1": "%10" }, }); process.env.TMUX = "1"; process.env.TMUX_PANE = "%10"; process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`; process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; let reconcileCalls = 0; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-hud-team-worker", prompt: "$ralplan prepare plan", }, { cwd, reconcileHudForPromptSubmitFn: async () => { reconcileCalls += 1; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(reconcileCalls, 0); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves prompt-submit HUD reconciliation for team leader panes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-leader-preserve-")); try { const teamName = "hud-leader-keep"; await initTeamState(teamName, "preserve leader HUD reconcile", "executor", 1, cwd); await setTeamPaneIds(cwd, teamName, { leaderPaneId: "%42", workerPaneIds: { "worker-1": "%10" }, }); process.env.TMUX = "1"; process.env.TMUX_PANE = "%42"; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; let reconcileCall: { cwd: string; sessionId?: string } | null = null; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-hud-team-leader", prompt: "$ralplan prepare plan", }, { cwd, reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => { reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId }; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.deepEqual(reconcileCall, { cwd, sessionId: "sess-hud-team-leader" }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves prompt-submit HUD reconciliation when worker pane detection is ambiguous", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-worker-ambiguous-")); try { const teamName = "hud-worker-ambiguous"; await initTeamState(teamName, "fail closed for ambiguous worker HUD reconcile", "executor", 1, cwd); await setTeamPaneIds(cwd, teamName, { leaderPaneId: "%42", workerPaneIds: { "worker-1": "%10" }, }); process.env.TMUX = "1"; process.env.TMUX_PANE = "%99"; process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`; process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; let reconcileCalls = 0; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-hud-team-worker-ambiguous", prompt: "$ralplan prepare plan", }, { cwd, reconcileHudForPromptSubmitFn: async () => { reconcileCalls += 1; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(reconcileCalls, 1); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves prompt-submit HUD reconciliation for native subagents even with worker pane env", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-subagent-worker-preserve-")); try { const teamName = "hud-subagent-keep"; await initTeamState(teamName, "preserve subagent HUD reconcile", "executor", 1, cwd); await setTeamPaneIds(cwd, teamName, { leaderPaneId: "%42", workerPaneIds: { "worker-1": "%10" }, }); const stateDir = join(cwd, ".omx", "state"); const canonicalSessionId = "sess-subagent-hud-parent"; const leaderNativeSessionId = "native-subagent-hud-parent"; const childNativeSessionId = "native-subagent-hud-child"; const nowIso = new Date().toISOString(); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, native_session_id: leaderNativeSessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [canonicalSessionId]: { session_id: canonicalSessionId, leader_thread_id: leaderNativeSessionId, updated_at: nowIso, threads: { [leaderNativeSessionId]: { thread_id: leaderNativeSessionId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, }, [childNativeSessionId]: { thread_id: childNativeSessionId, kind: "subagent", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1, mode: "verifier", }, }, }, }, }); process.env.TMUX = "1"; process.env.TMUX_PANE = "%10"; process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`; process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; let reconcileCall: { cwd: string; sessionId?: string } | null = null; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, turn_id: "turn-subagent-hud-child", prompt: "Review the worker patch literally; do not activate $ralplan.", }, { cwd, reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => { reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId }; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(result.outputJson, null); assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-")); const originalTmux = process.env.TMUX; const originalTmuxPane = process.env.TMUX_PANE; const originalPath = process.env.PATH; const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV]; const originalArgv = process.argv; try { process.env.TMUX = "1"; process.env.TMUX_PANE = "%1"; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeFile( join(cwd, ".omx", "hud-config.json"), JSON.stringify({ preset: "focused", git: { display: "branch" } }, null, 2), ); const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-bin-")); const tmuxLog = join(cwd, "tmux.log"); await writeFile( join(binDir, "tmux"), `#!/usr/bin/env bash set -euo pipefail printf '%s\\n' "$*" >> ${JSON.stringify(tmuxLog)} case "$1" in list-panes) printf '%%1\\tcodex\\tcodex\\n' ;; display-message) printf '200\\t60\\n' ;; split-window) printf '%%9\\n' ;; resize-pane) ;; esac `, ); await chmod(join(binDir, "tmux"), 0o755); process.env.PATH = `${binDir}:${originalPath}`; process.argv = [originalArgv[0] || 'node', '/tmp/codex-host-binary']; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-hud-1", prompt: "$ralplan prepare plan", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); const tmuxCalls = await readFile(tmuxLog, "utf-8"); assert.match(tmuxCalls, /list-panes -t %1 -F/); assert.match(tmuxCalls, new RegExp(`split-window -v -l ${HUD_TMUX_HEIGHT_LINES} -d -t %1 -c`)); assert.match(tmuxCalls, new RegExp(`resize-pane -t %9 -y ${HUD_TMUX_HEIGHT_LINES}`)); assert.match(tmuxCalls, /dist\/cli\/omx\.js' hud --watch --preset=focused/); assert.doesNotMatch(tmuxCalls, /\/tmp\/codex-host-binary' hud --watch/); } finally { if (originalTmux === undefined) { delete process.env.TMUX; } else { process.env.TMUX = originalTmux; } if (originalTmuxPane === undefined) { delete process.env.TMUX_PANE; } else { process.env.TMUX_PANE = originalTmuxPane; } if (originalHudOwner === undefined) { delete process.env[OMX_TMUX_HUD_OWNER_ENV]; } else { process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner; } process.env.PATH = originalPath; process.argv = originalArgv; await rm(cwd, { recursive: true, force: true }); } }); it("skips prompt-submit HUD reconciliation during doctor smoke validation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-doctor-smoke-hud-")); const originalTmux = process.env.TMUX; const originalTmuxPane = process.env.TMUX_PANE; const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV]; const originalDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE; try { process.env.TMUX = "1"; process.env.TMUX_PANE = "%1"; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = "1"; let reconcileCalled = false; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "omx-doctor-plugin-hook-smoke", prompt: "$ralplan doctor plugin hook smoke test", }, { cwd, reconcileHudForPromptSubmitFn: async () => { reconcileCalled = true; return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 }; }, }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(reconcileCalled, false); } finally { if (originalTmux === undefined) delete process.env.TMUX; else process.env.TMUX = originalTmux; if (originalTmuxPane === undefined) delete process.env.TMUX_PANE; else process.env.TMUX_PANE = originalTmuxPane; if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV]; else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner; if (originalDoctorSmoke === undefined) delete process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE; else process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = originalDoctorSmoke; await rm(cwd, { recursive: true, force: true }); } }); it("recreates a leader-only HUD pane when UserPromptSubmit revives with the canonical session id", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-")); const originalTmux = process.env.TMUX; const originalTmuxPane = process.env.TMUX_PANE; const originalPath = process.env.PATH; const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV]; try { process.env.TMUX = "1"; process.env.TMUX_PANE = "%1"; process.env[OMX_TMUX_HUD_OWNER_ENV] = "1"; const canonicalSessionId = "omx-canonical-hud-reuse"; const nativeSessionId = "codex-native-hud-reuse"; await mkdir(join(cwd, ".omx", "state", "sessions", canonicalSessionId), { recursive: true }); await writeSessionStart(cwd, canonicalSessionId); const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-bin-")); const tmuxLog = join(cwd, "tmux.log"); await writeFile( join(binDir, "tmux"), `#!/usr/bin/env bash set -euo pipefail printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)} case "$1" in list-panes) printf '%%1\tcodex\tcodex\n' printf '%%2\tnode\texec env OMX_TMUX_HUD_OWNER='"'"'1'"'"' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='"'"'%%1'"'"' /node /omx.js hud --watch\n' ;; display-message) printf '200\t60\n' ;; resize-pane) ;; split-window) printf '%%9\n' ;; esac `, ); await chmod(join(binDir, "tmux"), 0o755); process.env.PATH = `${binDir}:${originalPath}`; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: nativeSessionId, thread_id: "thread-hud-reuse", turn_id: "turn-hud-reuse", prompt: "$ralplan prepare plan", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); const tmuxCalls = await readFile(tmuxLog, "utf-8"); assert.match(tmuxCalls, /list-panes -t %1 -F/); assert.match(tmuxCalls, /split-window/); assert.match(tmuxCalls, new RegExp(`resize-pane -t %9 -y ${HUD_TMUX_HEIGHT_LINES}`)); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true); assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false); } finally { if (originalTmux === undefined) delete process.env.TMUX; else process.env.TMUX = originalTmux; if (originalTmuxPane === undefined) delete process.env.TMUX_PANE; else process.env.TMUX_PANE = originalTmuxPane; if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV]; else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner; process.env.PATH = originalPath; await rm(cwd, { recursive: true, force: true }); } }); it("skips prompt-submit HUD reconciliation inside unowned tmux panes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-")); const originalTmux = process.env.TMUX; const originalTmuxPane = process.env.TMUX_PANE; const originalPath = process.env.PATH; const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV]; try { process.env.TMUX = "1"; process.env.TMUX_PANE = "%claude"; delete process.env[OMX_TMUX_HUD_OWNER_ENV]; const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-bin-")); const tmuxLog = join(cwd, "tmux.log"); await writeFile( join(binDir, "tmux"), `#!/usr/bin/env bash printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)} exit 0 `, ); await chmod(join(binDir, "tmux"), 0o755); process.env.PATH = `${binDir}:${originalPath}`; const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-hud-unowned", prompt: "$ralplan prepare plan", }, { cwd }, ); assert.equal(result.omxEventName, "keyword-detector"); assert.equal(existsSync(tmuxLog), false); } finally { if (originalTmux === undefined) delete process.env.TMUX; else process.env.TMUX = originalTmux; if (originalTmuxPane === undefined) delete process.env.TMUX_PANE; else process.env.TMUX_PANE = originalTmuxPane; if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV]; else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner; process.env.PATH = originalPath; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Bash omx question when no leader-pane return hint is preserved", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-enforce-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-block", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /OMX_QUESTION_RETURN_PANE=\$TMUX_PANE/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Bash commands that only mention omx question in quoted arguments", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-quoted-mention-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-quoted-mention", tool_input: { command: `omx ultragoal create-goals --brief "Deep interview says omx question failed in tmux"`, }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Bash heredocs that only document omx question text", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-heredoc-mention-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-heredoc-mention", tool_input: { command: `cat > issue-notes.md <<'EOF'\nomx question failed in the attached tmux pane\nEOF`, }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Bash omx question when the command preserves the leader-pane return hint", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-allow-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-allow", tool_input: { command: `OMX_QUESTION_RETURN_PANE=$TMUX_PANE omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows the quoted pane env assignment emitted by the deep-interview bridge command", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-quoted-allow-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-quoted-allow", tool_input: { command: `OMX_QUESTION_RETURN_PANE='%42' node ./dist/cli/omx.js question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows PowerShell env bridge forms for omx question return panes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-powershell-allow-")); try { const commands = [ `$env:OMX_QUESTION_RETURN_PANE=$env:TMUX_PANE; omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'`, `$env:OMX_QUESTION_RETURN_PANE='%42'; node ./dist/cli/omx.js question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'`, `$env:OMX_LEADER_PANE_ID="%43"; omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'`, ]; for (const [index, command] of commands.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: `tool-question-powershell-allow-${index}`, tool_input: { command }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Bash omx question when a valid inherited OMX_QUESTION_RETURN_PANE bridge is already exported", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-env-allow-")); const originalReturnPane = process.env.OMX_QUESTION_RETURN_PANE; try { process.env.OMX_QUESTION_RETURN_PANE = "%42"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-env-allow", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (originalReturnPane === undefined) delete process.env.OMX_QUESTION_RETURN_PANE; else process.env.OMX_QUESTION_RETURN_PANE = originalReturnPane; await rm(cwd, { recursive: true, force: true }); } }); it("allows Bash omx question when a valid inherited OMX_LEADER_PANE_ID bridge is already exported", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-leader-env-allow-")); const originalLeaderPane = process.env.OMX_LEADER_PANE_ID; try { process.env.OMX_LEADER_PANE_ID = "%43"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-leader-env-allow", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (originalLeaderPane === undefined) delete process.env.OMX_LEADER_PANE_ID; else process.env.OMX_LEADER_PANE_ID = originalLeaderPane; await rm(cwd, { recursive: true, force: true }); } }); it("still blocks Bash omx question when an inherited OMX_QUESTION_RETURN_PANE value is malformed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-env-malformed-")); const originalReturnPane = process.env.OMX_QUESTION_RETURN_PANE; try { process.env.OMX_QUESTION_RETURN_PANE = "not-a-pane"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-env-malformed", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); } finally { if (originalReturnPane === undefined) delete process.env.OMX_QUESTION_RETURN_PANE; else process.env.OMX_QUESTION_RETURN_PANE = originalReturnPane; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Bash node omx.js question when the command does not preserve the leader-pane return hint", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-node-block-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-question-node-block", tool_input: { command: `node ./dist/cli/omx.js question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks native/App Bash omx question with bridge-specific outside-tmux guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-native-block-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-question-native-block", tool_name: "Bash", tool_use_id: "tool-question-native-block", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((result.outputJson as { hookSpecificOutput?: unknown } | null)?.hookSpecificOutput, undefined); assert.match(String((result.outputJson as { reason?: string } | null)?.reason || ""), /Codex App\/native outside-tmux Bash sessions/); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /native structured question tool/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks native/App Bash omx question even when the command preserves a tmux return bridge", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-native-allow-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-question-native-bridge-block", tool_name: "Bash", tool_use_id: "tool-question-native-bridge-block", tool_input: { command: `OMX_QUESTION_RETURN_PANE=$TMUX_PANE omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /native structured question tool/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks native/App Bash omx question when a valid inherited OMX_QUESTION_RETURN_PANE bridge is already exported", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-native-env-allow-")); const originalReturnPane = process.env.OMX_QUESTION_RETURN_PANE; try { process.env.OMX_QUESTION_RETURN_PANE = "%42"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-question-native-env-allow", tool_name: "Bash", tool_use_id: "tool-question-native-env-allow", tool_input: { command: `omx question --json --input '{"question":"Q?","options":["A"],"allow_other":true}'` }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); } finally { if (originalReturnPane === undefined) delete process.env.OMX_QUESTION_RETURN_PANE; else process.env.OMX_QUESTION_RETURN_PANE = originalReturnPane; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Bash omx hud from Codex App/native outside tmux without PreToolUse additionalContext", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-hud-native-block-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-hud-native-block", tool_name: "Bash", tool_use_id: "tool-hud-native-block", tool_input: { command: "omx hud --tmux" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((result.outputJson as { hookSpecificOutput?: unknown } | null)?.hookSpecificOutput, undefined); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /attached tmux shell first/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Bash omx team from Codex App/native outside tmux", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-native-block-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-team-native-block", tool_name: "Bash", tool_use_id: "tool-team-native-block", tool_input: { command: "omx team status my-team" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((result.outputJson as { hookSpecificOutput?: unknown } | null)?.hookSpecificOutput, undefined); assert.match(String((result.outputJson as { reason?: string } | null)?.reason || ""), /cannot be launched directly from Codex App\/native outside-tmux Bash sessions/); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /launch OMX CLI from an attached tmux shell first/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Bash node omx.js team from Codex App/native outside tmux", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-node-native-block-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "codex-app", session_id: "sess-team-node-native-block", tool_name: "Bash", tool_use_id: "tool-team-node-native-block", tool_input: { command: "node ./dist/cli/omx.js team status my-team" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((result.outputJson as { systemMessage?: string } | null)?.systemMessage || ""), /Codex App\/native outside-tmux sessions/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves direct CLI outside-tmux omx team Bash behavior", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-outside-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, source: "cli", session_id: "sess-team-cli-outside", tool_name: "Bash", tool_use_id: "tool-team-cli-outside", tool_input: { command: "omx team status my-team" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves source-less outside-tmux omx team Bash behavior when no native session evidence exists", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-nosource-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-team-cli-nosource", tool_name: "Bash", tool_use_id: "tool-team-cli-nosource", tool_input: { command: "omx team status my-team" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-edit-block"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block", rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block", tool_name: "Edit", tool_use_id: "tool-di-edit-block", tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Deep-interview is active/); assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/); assert.match(JSON.stringify(result.outputJson), /\$ralplan/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not self-lock PreToolUse after completed deep-interview handoff leaves Autopilot in deep-interview phase", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-completed-autopilot-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-di-completed-autopilot"; const threadId = "thread-di-completed-autopilot"; const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", keyword: "$autopilot", phase: "deep-interview", initialized_mode: "autopilot", initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`, session_id: sessionId, thread_id: threadId, active_skills: [{ skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId, thread_id: threadId }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: false, mode: "deep-interview", current_phase: "completed", session_id: sessionId, thread_id: threadId, deep_interview_gate: { status: "complete", handoff_summary: "Requirements were clarified before the Autopilot handoff.", }, }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "deep-interview", session_id: sessionId, thread_id: threadId, }); const implementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: threadId, tool_name: "Edit", tool_use_id: "tool-di-completed-autopilot-edit", tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(implementationEdit.omxEventName, "pre-tool-use"); assert.equal(implementationEdit.outputJson, null); const stateRepair = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: threadId, tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-di-completed-autopilot-state-repair", tool_input: { mode: "autopilot", current_phase: "ralplan", active: true }, }, { cwd }, ); assert.equal(stateRepair.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-artifact"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd }); const threadId = "thread-di-artifact"; await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-artifact", thread_id: threadId, active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact", thread_id: threadId }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-artifact", thread_id: threadId, rounds: [{ answer: "Use CLI wrapper normalization for protected planning state commands." }], }); const preToolUse = ( payload: Parameters[0], _options?: { cwd?: string }, ) => dispatchCodexNativeHook({ ...payload, hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", thread_id: threadId, }, { cwd }); const allowedWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Write", tool_use_id: "tool-di-spec-write", tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" }, }, { cwd }, ); assert.equal(allowedWrite.outputJson, null); const allowedBash = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-context-bash", tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" }, }, { cwd }, ); assert.equal(allowedBash.outputJson, null); const allowedTmpBash = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-tmp-bash", tool_input: { command: [ "mkdir -p .omx/tmp/sess-di-artifact", "cat > .omx/tmp/sess-di-artifact/demo.md <<'EOF'", "# Scratch", "EOF", ].join("\n"), }, }, { cwd }, ); assert.equal(allowedTmpBash.outputJson, null); const blockedTmpScriptWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Write", tool_use_id: "tool-di-tmp-script-write", tool_input: { file_path: ".omx/tmp/sess-di-artifact/run.sh", content: "printf pwned > src/pwned.ts\n", }, }, { cwd }, ); assert.equal((blockedTmpScriptWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedTmpScriptWrite.outputJson), /\.omx\/tmp|planning artifact paths/); const blockedTmpScriptExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-tmp-script-exec", tool_input: { command: "sh .omx/tmp/sess-di-artifact/run.sh" }, }, { cwd }, ); assert.equal((blockedTmpScriptExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedTmpScriptExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedTmpInterpreterExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-tmp-python-exec", tool_input: { command: "python3.12 .omx/tmp/sess-di-artifact/generated.txt" }, }, { cwd }, ); assert.equal((blockedTmpInterpreterExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedTmpInterpreterExecution.outputJson), /generated-script transport|\.omx\/tmp/); const blockedTmpTsxExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-tmp-tsx-exec", tool_input: { command: "tsx --tsconfig tsconfig.json watch .omx/tmp/sess-di-artifact/generated.ts" }, }, { cwd }, ); assert.equal((blockedTmpTsxExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(blockedTmpTsxExecution.outputJson), /generated-script transport|\.omx\/tmp/); for (const [toolUseId, command] of [ ["tool-di-tmp-python-option-txt-exec", "python -X dev .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-node-require-load", "node --require .omx/tmp/sess-di-artifact/preload -e ''"], ["tool-di-tmp-node-import-load", "node --import .omx/tmp/sess-di-artifact/preload -e ''"], ["tool-di-tmp-bun-require-load", "bun --require .omx/tmp/sess-di-artifact/preload -e ''"], ["tool-di-tmp-bash-rcfile-load", "bash --rcfile .omx/tmp/sess-di-artifact/rc -i -c true"], ["tool-di-tmp-go-run-exec", "go run .omx/tmp/sess-di-artifact/probe.go"], ["tool-di-tmp-deno-run-exec", "deno run .omx/tmp/sess-di-artifact/generated.ts"], ["tool-di-tmp-python-stdin-exec", "python < .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-node-stdin-exec", "node < .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-ruby-stdin-exec", "ruby < .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-perl-stdin-exec", "perl < .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-sh-stdin-exec", "sh < .omx/tmp/sess-di-artifact/run.txt"], ["tool-di-tmp-bash-stdin-exec", "bash < .omx/tmp/sess-di-artifact/run.txt"], [ "tool-di-tmp-same-command-stdin-exec", [ "python3 - <<'PY'", "from pathlib import Path", "Path('.omx/tmp/sess-di-artifact/run.txt').write_text('print(1)')", "PY", "python < .omx/tmp/sess-di-artifact/run.txt", ].join("\n"), ], ] as const) { const blockedTmpTransport = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: toolUseId, tool_input: { command }, }, { cwd }, ); assert.equal((blockedTmpTransport.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match(JSON.stringify(blockedTmpTransport.outputJson), /generated-script transport|\.omx\/tmp/); } const allowedAppendBash = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-context-append-bash", tool_input: { command: "echo more context >> .omx/context/demo.md" }, }, { cwd }, ); assert.equal(allowedAppendBash.outputJson, null); const deepInterviewRalplanHandoffState = JSON.stringify({ mode: "autopilot", active: true, current_phase: "ralplan", state: { deep_interview_gate: { status: "complete", rationale: "Requirements are clarified and ready for ralplan handoff.", }, }, }); const reportedHandoffShape = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-context-spec-handoff", tool_input: { command: [ "mkdir -p .omx/context .omx/specs", "cat > .omx/context/deep-interview-demo.md <<'EOF'", "# Context", "EOF", "cat > .omx/specs/deep-interview-demo.md <<'EOF'", "# Spec", "EOF", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal(reportedHandoffShape.outputJson, null); const blockedTmpOnlyHandoffShape = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-tmp-only-handoff", tool_input: { command: [ "mkdir -p .omx/tmp/sess-di-artifact", "cat > .omx/tmp/sess-di-artifact/only.md <<'EOF'", "# Tmp-only scratch", "EOF", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedTmpOnlyHandoffShape.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedTmpOnlyHandoffShape.outputJson as { reason?: string } | null)?.reason ?? ""), /handoff|Bash write intent|deep-interview/i); const blockedHandoffWithSameCommandArtifactExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-handoff-with-artifact-exec", tool_input: { command: [ "mkdir -p .omx/context", "cat > .omx/context/run.sh <<'EOF'", "printf '%s\\n' same-command-artifact-executed", "EOF", "sh .omx/context/run.sh", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedHandoffWithSameCommandArtifactExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedHandoffWithSameCommandArtifactExecution.outputJson as { reason?: string } | null)?.reason ?? ""), /same-command|Bash write intent|handoff/i); const blockedHandoffWithPythonSameCommandArtifactExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-handoff-with-python-artifact-exec", tool_input: { command: [ "python3 - <<'PY'", "from pathlib import Path", "Path('.omx/context/run.sh').write_text('echo ran')", "PY", "sh .omx/context/run.sh", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedHandoffWithPythonSameCommandArtifactExecution.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedHandoffWithPythonSameCommandArtifactExecution.outputJson as { reason?: string } | null)?.reason ?? ""), /same-command|Bash write intent|handoff/i); for (const [toolUseId, artifactDir, scriptName, executionLine] of [ [ "tool-di-reported-handoff-with-cd-relative-sh-exec", ".omx/context", "run.sh", "cd .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-command-cd-relative-sh-exec", ".omx/context", "run.sh", "command cd .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-builtin-cd-relative-sh-exec", ".omx/context", "run.sh", "builtin cd .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-cd-double-dash-relative-sh-exec", ".omx/context", "run.sh", "cd -- .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-cd-physical-relative-sh-exec", ".omx/context", "run.sh", "cd -P .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-cd-logical-relative-sh-exec", ".omx/context", "run.sh", "cd -L .omx/context && sh run.sh", ], [ "tool-di-reported-handoff-with-cd-relative-dot-source", ".omx/specs", "env.sh", "cd .omx/specs && . env.sh", ], [ "tool-di-reported-handoff-with-cd-relative-direct-exec", ".omx/context", "run-relative.sh", "cd .omx/context && ./run-relative.sh", ], [ "tool-di-reported-handoff-with-setsid-direct-exec", ".omx/context", "run.sh", "setsid ./.omx/context/run.sh", ], [ "tool-di-reported-handoff-with-env-chdir-relative-sh-exec", ".omx/context", "run.sh", "env -C .omx/context sh run.sh", ], [ "tool-di-reported-handoff-with-env-long-chdir-relative-dot-source", ".omx/specs", "env.sh", "env --chdir .omx/specs . env.sh", ], ] as const) { const blockedCwdRelativeArtifactExecution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: toolUseId, tool_input: { command: [ `mkdir -p ${artifactDir}`, `cat > ${artifactDir}/${scriptName} <<'EOF'`, "printf '%s\\n' same-command-cwd-relative-artifact-executed", "EOF", executionLine, `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedCwdRelativeArtifactExecution.outputJson as { decision?: string } | null)?.decision, "block", executionLine); assert.match(String((blockedCwdRelativeArtifactExecution.outputJson as { reason?: string } | null)?.reason ?? ""), /same-command|Bash write intent|handoff/i, executionLine); } const blockedHandoffWithImplementationWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-handoff-with-source-write", tool_input: { command: [ "mkdir -p .omx/context .omx/specs src", "cat > .omx/context/deep-interview-demo.md <<'EOF'", "# Context", "EOF", "cat > src/runtime.ts <<'EOF'", "export const changed = true;", "EOF", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedHandoffWithImplementationWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedHandoffWithImplementationWrite.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/runtime\.ts/); const blockedHandoffWithUnredirectedSourceMutation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-reported-handoff-with-source-mkdir", tool_input: { command: [ "mkdir -p .omx/context .omx/specs src/generated", "cat > .omx/context/deep-interview-demo.md <<'EOF'", "# Context", "EOF", `omx state write --input '${deepInterviewRalplanHandoffState}' --json`, ].join("\n"), }, }, { cwd }, ); assert.equal((blockedHandoffWithUnredirectedSourceMutation.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedHandoffWithUnredirectedSourceMutation.outputJson as { reason?: string } | null)?.reason ?? ""), /Bash/); const allowedReadOnlyEditors = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-read-only-editors", tool_input: { command: "sed -n '1,20p' src/runtime.ts; perl -ne 'print if $. < 3' src/runtime.ts" }, }, { cwd }, ); assert.equal(allowedReadOnlyEditors.outputJson, null); const blockedCombinedSedSourceEdit = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-sed-combined-source-edit", tool_input: { command: "sed -Ei 's/old/new/' src/runtime.ts" }, }, { cwd }, ); assert.equal((blockedCombinedSedSourceEdit.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedCombinedSedSourceEdit.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/runtime\.ts/); const allowedCombinedSedArtifactEdit = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-sed-combined-artifact-edit", tool_input: { command: "sed -Ei 's/old/new/' .omx/specs/deep-interview-demo.md" }, }, { cwd }, ); assert.equal(allowedCombinedSedArtifactEdit.outputJson, null); const allowedPlanningStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Write", tool_use_id: "tool-di-planning-state-write", tool_input: { file_path: ".omx/state/deep-interview-notes.json", content: "{}\n" }, }, { cwd }, ); assert.equal(allowedPlanningStateWrite.outputJson, null); const protectedStateFiles = [ ".omx/state/sessions/sess-di-artifact/autopilot-state.json", ".omx/state/sessions/sess-di-artifact/deep-interview-state.json", ".omx/state/sessions/sess-di-artifact/skill-active-state.json", ".omx/state/deep-interview-state.json", ]; for (const [index, filePath] of protectedStateFiles.entries()) { const protectedWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Write", tool_use_id: `tool-di-protected-state-${index}`, tool_input: { file_path: filePath, content: "{}\n" }, }, { cwd }, ); assert.equal( (protectedWrite.outputJson as { decision?: string } | null)?.decision, "block", `${filePath} should not be model-writable during deep-interview`, ); } // Cross-mode non-terminal `omx state write` payloads are activations, // because state_write normalizes them to active=true after the hook. const blockedStateCliMutation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-write", tool_input: { command: "omx state write --input '{\"mode\":\"ralph\",\"current_phase\":\"executing\"}' --json" }, }, { cwd }, ); assert.equal((blockedStateCliMutation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedMcpStateMutation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-di-mcp-state-write-execute", tool_input: { mode: "ralph", current_phase: "executing" }, }, { cwd }, ); assert.equal((blockedMcpStateMutation.outputJson as { decision?: string } | null)?.decision, "block"); for (const phase of ["planning", "replan", "autopilot:ralplan"] as const) { const allowedAutopilotPlanningAlias = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: `tool-di-mcp-state-write-autopilot-alias-${phase}`, tool_input: { mode: "autopilot", current_phase: phase }, }, { cwd }, ); assert.equal( allowedAutopilotPlanningAlias.outputJson, null, `${phase} autopilot planning handoff should not be classified as deactivation`, ); } const allowedQuotedModeMentionInPayload = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-mode-mention-in-json", tool_input: { command: "omx state write --input '{\"mode\":\"ralph\",\"note\":\"--mode deep-interview\",\"active\":false}' --json" }, }, { cwd }, ); assert.equal(allowedQuotedModeMentionInPayload.outputJson, null); const allowedStateInputFile = join(cwd, "allowed-state-input.json"); await writeJson(allowedStateInputFile, { mode: "deep-interview", current_phase: "intent-first", active: true }); const allowedStateInputFileMutation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-input-file-allowed", tool_input: { command: `omx state write --input-file ${allowedStateInputFile} --json` }, }, { cwd }, ); assert.equal(allowedStateInputFileMutation.outputJson, null); const standaloneAutopilotRalplanHandoffPayload = { mode: "autopilot", active: true, current_phase: "ralplan", state: { deep_interview_gate: { status: "complete", rationale: "The bounded PMO catalog task is clear and ready for ralplan handoff.", handoff_summary: "Create the root-level pmo catalog from the captured spec.", }, }, }; const standaloneAutopilotRalplanHandoffInputFile = join(cwd, ".omx", "tmp", "pmo-autopilot-state.json"); await mkdir(dirname(standaloneAutopilotRalplanHandoffInputFile), { recursive: true }); await writeJson(standaloneAutopilotRalplanHandoffInputFile, standaloneAutopilotRalplanHandoffPayload); const blockedStandaloneAutopilotRalplanHandoffWithoutEvidence = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-standalone-autopilot-ralplan-handoff-without-evidence", tool_input: { command: `omx state write --input-file ${standaloneAutopilotRalplanHandoffInputFile} --json` }, }, { cwd }, ); assert.equal( (blockedStandaloneAutopilotRalplanHandoffWithoutEvidence.outputJson as { decision?: string } | null)?.decision, "block", ); const priorDeepInterviewSpec = join(cwd, ".omx", "specs", "pmo-catalog-spec.md"); await mkdir(dirname(priorDeepInterviewSpec), { recursive: true }); await writeFile(priorDeepInterviewSpec, "# PMO catalog spec\n"); const allowedStandaloneAutopilotRalplanHandoffWithEvidence = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-standalone-autopilot-ralplan-handoff-with-evidence", tool_input: { command: `omx state write --input-file ${standaloneAutopilotRalplanHandoffInputFile} --json` }, }, { cwd }, ); assert.equal( allowedStandaloneAutopilotRalplanHandoffWithEvidence.outputJson, null, "a valid autopilot deep-interview -> ralplan state write may use prior durable .omx/specs evidence", ); // A deactivating `omx state write` (or `omx state clear`) ends the planning // phase, which the backend does not gate for standalone modes; the hook // rejects these deactivation vectors at the transport boundary. const blockedStateDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-deactivate", tool_input: { command: "omx state write --input '{\"mode\":\"deep-interview\",\"active\":false}' --json" }, }, { cwd }, ); assert.equal((blockedStateDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedStateInputFile = join(cwd, "blocked-state-input.json"); await writeJson(blockedStateInputFile, { mode: "deep-interview", active: false }); const blockedStateDeactivationFile = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-input-file-blocked", tool_input: { command: `omx state write --input-file ${blockedStateInputFile} --json` }, }, { cwd }, ); assert.equal((blockedStateDeactivationFile.outputJson as { decision?: string } | null)?.decision, "block"); const blockedModeFlagDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-mode-flag-deactivate", tool_input: { command: "omx state write --mode deep-interview --input '{\"active\":false}' --json" }, }, { cwd }, ); assert.equal((blockedModeFlagDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const conflictingModeFlagPayload = join(cwd, "conflicting-mode-flag-state.json"); await writeJson(conflictingModeFlagPayload, { mode: "ralph", active: false }); const blockedModeFlagFileDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-mode-flag-file-deactivate", tool_input: { command: `omx state write --mode deep-interview --input-file ${conflictingModeFlagPayload} --json` }, }, { cwd }, ); assert.equal((blockedModeFlagFileDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedRepeatedModeFlagDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-repeated-mode-flag-deactivate", tool_input: { command: "omx state write --mode ralph --mode deep-interview --input '{\"active\":false}' --json" }, }, { cwd }, ); assert.equal((blockedRepeatedModeFlagDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedRepeatedInputDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-repeated-input-deactivate", tool_input: { command: "omx state write --input '{\"mode\":\"deep-interview\",\"active\":true}' " + "--input '{\"mode\":\"deep-interview\",\"active\":false}' --json", }, }, { cwd }, ); assert.equal((blockedRepeatedInputDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const repeatedInputFileSafe = join(cwd, "repeated-input-file-safe.json"); const repeatedInputFileBlocked = join(cwd, "repeated-input-file-blocked.json"); await writeJson(repeatedInputFileSafe, { mode: "deep-interview", current_phase: "intent-first", active: true }); await writeJson(repeatedInputFileBlocked, { mode: "deep-interview", active: false }); const blockedRepeatedInputFileDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-repeated-input-file-deactivate", tool_input: { command: `omx state write --input-file ${repeatedInputFileSafe} --input-file ${repeatedInputFileBlocked} --json` }, }, { cwd }, ); assert.equal((blockedRepeatedInputFileDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const terminalCurrentPhaseAliases = ["finish", "finished", "complete", "completed", "done", "blocked", "blocked-on-user", "blocked_on_user", "failed", "fail", "error", "cancelled", "canceled", "cancel", "aborted", "abort", "userinterlude", "user-interlude", "interrupted", "interrupt", "askuserquestion", "ask-user-question", "askuser", "question"] as const; for (const alias of terminalCurrentPhaseAliases) { const blockedStateWriteCurrentPhaseAlias = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-current-phase-alias-${alias}`, tool_input: { command: `omx state write --input '${JSON.stringify({ mode: "deep-interview", current_phase: alias })}' --json`, }, }, { cwd }, ); assert.equal( (blockedStateWriteCurrentPhaseAlias.outputJson as { decision?: string } | null)?.decision, "block", `${alias} current_phase should deactivate protected deep-interview planning`, ); } const blockedCurrentPhaseAliasInputFile = join(cwd, "blocked-current-phase-alias-input-file.json"); await writeJson(blockedCurrentPhaseAliasInputFile, { mode: "deep-interview", current_phase: "done" }); const blockedStateWriteCurrentPhaseAliasFile = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-current-phase-alias-input-file", tool_input: { command: `omx state write --input-file ${blockedCurrentPhaseAliasInputFile} --json` }, }, { cwd }, ); assert.equal((blockedStateWriteCurrentPhaseAliasFile.outputJson as { decision?: string } | null)?.decision, "block"); const terminalOutcomeAliasPayloads = [ { runOutcome: "done" }, { lifecycleOutcome: "blocked" }, { terminalOutcome: "aborted" }, ] as const; for (const [index, aliasPayload] of terminalOutcomeAliasPayloads.entries()) { const blockedCamelCaseOutcomeAlias = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-camel-terminal-outcome-${index}`, tool_input: { command: `omx state write --input '${JSON.stringify({ mode: "deep-interview", ...aliasPayload })}' --json`, }, }, { cwd }, ); assert.equal( (blockedCamelCaseOutcomeAlias.outputJson as { decision?: string } | null)?.decision, "block", `${Object.keys(aliasPayload)[0]} should deactivate protected deep-interview planning`, ); } const blockedCamelCaseOutcomeInputFile = join(cwd, "blocked-camel-case-outcome-input-file.json"); await writeJson(blockedCamelCaseOutcomeInputFile, { mode: "deep-interview", lifecycleOutcome: "finished" }); const blockedStateWriteCamelCaseOutcomeFile = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-camel-terminal-outcome-input-file", tool_input: { command: `omx state write --input-file ${blockedCamelCaseOutcomeInputFile} --json` }, }, { cwd }, ); assert.equal((blockedStateWriteCamelCaseOutcomeFile.outputJson as { decision?: string } | null)?.decision, "block"); const blockedCrossModeActivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-cross-mode-activation", tool_input: { command: "omx state write --input '{\"mode\":\"ralph\",\"active\":true}' --json" }, }, { cwd }, ); assert.equal((blockedCrossModeActivation.outputJson as { decision?: string } | null)?.decision, "block"); const cwdRelativeInputFileRootSafe = join(cwd, "cwd-relative-input-file-safe.json"); const cwdRelativeInputFileSubdir = join(cwd, "cwd-relative-input-file-subdir"); await mkdir(cwdRelativeInputFileSubdir, { recursive: true }); await writeJson(cwdRelativeInputFileRootSafe, { mode: "deep-interview", active: true }); await writeJson(join(cwdRelativeInputFileSubdir, "payload.json"), { mode: "deep-interview", active: false }); const blockedCwdRelativeInputFileAfterCd = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-cwd-relative-input-file-after-cd", tool_input: { command: "cd cwd-relative-input-file-subdir && omx state write --input-file payload.json --json" }, }, { cwd }, ); assert.equal((blockedCwdRelativeInputFileAfterCd.outputJson as { decision?: string } | null)?.decision, "block"); const blockedRewrittenInputFile = join(cwd, "rewritten-input-file.json"); await writeJson(blockedRewrittenInputFile, { mode: "deep-interview", active: true }); const blockedRewrittenInputFileMutation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-rewritten-input-file", tool_input: { command: "printf '{\"mode\":\"deep-interview\",\"active\":false}' > rewritten-input-file.json && " + "omx state write --input-file rewritten-input-file.json --json", }, }, { cwd }, ); assert.equal((blockedRewrittenInputFileMutation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedNestedStateDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-nested-state-deactivate", tool_input: { command: "omx state write --mode deep-interview --input '{\"state\":{\"active\":false}}' --json" }, }, { cwd }, ); assert.equal((blockedNestedStateDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); const blockedMultipleStateWrites = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-multiple-writes", tool_input: { command: "omx state write --input '{\"mode\":\"deep-interview\",\"active\":true}' --json && " + "omx state write --input '{\"mode\":\"deep-interview\",\"active\":false}' --json", }, }, { cwd }, ); assert.equal((blockedMultipleStateWrites.outputJson as { decision?: string } | null)?.decision, "block"); const allowedDecoyBeforeStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-decoy-before-real-write", tool_input: { command: "printf '%s\\n' \"--input '{\\\"mode\\\":\\\"deep-interview\\\",\\\"active\\\":false}'\" && " + "omx state write --input '{\"mode\":\"deep-interview\",\"active\":true}' --json", }, }, { cwd }, ); assert.equal(allowedDecoyBeforeStateWrite.outputJson, null); const blockedFileWriteWithLaterSafeDecoy = join(cwd, "blocked-file-before-later-decoy.json"); await writeJson(blockedFileWriteWithLaterSafeDecoy, { mode: "deep-interview", active: false }); const blockedSegmentedStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-bounded-segment", tool_input: { command: `omx state write --input-file ${blockedFileWriteWithLaterSafeDecoy} --json && ` + "printf '%s\\n' \"--input '{\\\"mode\\\":\\\"deep-interview\\\",\\\"active\\\":true}'\"", }, }, { cwd }, ); assert.equal((blockedSegmentedStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); const allowedMcpStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-di-mcp-state-write-allowed", tool_input: { mode: "deep-interview", current_phase: "intent-first", active: true }, }, { cwd }, ); assert.equal(allowedMcpStateWrite.outputJson, null); const blockedMcpStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-di-mcp-state-write-deactivate", tool_input: { mode: "deep-interview", active: false }, }, { cwd }, ); assert.equal((blockedMcpStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); const blockedNestedMcpStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-di-mcp-state-write-nested-deactivate", tool_input: { mode: "deep-interview", state: { active: false } }, }, { cwd }, ); assert.equal((blockedNestedMcpStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); for (const alias of terminalCurrentPhaseAliases) { const blockedMcpCurrentPhaseAlias = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: `tool-di-mcp-state-write-current-phase-alias-${alias}`, tool_input: { mode: "deep-interview", state: { currentPhase: alias } }, }, { cwd }, ); assert.equal( (blockedMcpCurrentPhaseAlias.outputJson as { decision?: string } | null)?.decision, "block", `${alias} currentPhase should deactivate protected deep-interview planning`, ); } for (const [index, aliasPayload] of terminalOutcomeAliasPayloads.entries()) { const blockedMcpCamelCaseOutcomeAlias = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_write", tool_use_id: `tool-di-mcp-state-write-camel-terminal-outcome-${index}`, tool_input: { mode: "deep-interview", state: aliasPayload }, }, { cwd }, ); assert.equal( (blockedMcpCamelCaseOutcomeAlias.outputJson as { decision?: string } | null)?.decision, "block", `${Object.keys(aliasPayload)[0]} MCP write should deactivate protected deep-interview planning`, ); } const blockedMcpStateClear = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "mcp__omx_state__state_clear", tool_use_id: "tool-di-mcp-state-clear", tool_input: { mode: "deep-interview" }, }, { cwd }, ); assert.equal((blockedMcpStateClear.outputJson as { decision?: string } | null)?.decision, "block"); const blockedStateClear = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-clear", tool_input: { command: "omx state clear --json" }, }, { cwd }, ); assert.equal((blockedStateClear.outputJson as { decision?: string } | null)?.decision, "block"); const stateDeactivationInput = "'{\"mode\":\"deep-interview\",\"active\":false}'"; const absolutePathQualifiedNpm = resolve(cwd, "bin", "npm"); const cliWrapperPlanningDeactivationCommands = [ ["node-wrapper-clear", "node dist/cli/omx.js state clear --json"], ["node-wrapper-write", `node dist/cli/omx.js state write --input ${stateDeactivationInput} --json`], ["node-wrapper-option-clear", "node --enable-source-maps dist/cli/omx.js state clear --json"], ["node-wrapper-require-clear", "node --require tsx/cjs dist/cli/omx.js state clear --json"], ["bun-wrapper-path-variant", "bun ./dist/cli/omx.js state clear --json"], ["tsx-wrapper-write", `tsx dist/cli/omx.js state write --input ${stateDeactivationInput} --json`], ["direct-wrapper-clear", "./dist/cli/omx.js state clear --json"], ["path-qualified-omx-clear", "./node_modules/.bin/omx state clear --json"], ["quoted-node-wrapper-clear", 'node "dist/cli/omx.js" state clear --json'], ["quoted-direct-wrapper-clear", '"./dist/cli/omx.js" state clear --json'], ["quoted-node-wrapper-write", `node "dist/cli/omx.js" state write --input ${stateDeactivationInput} --json`], ["env-wrapper-clear", "env FOO=bar node dist/cli/omx.js state clear --json"], ["env-argv0-short-wrapper-clear", "env -a fake node dist/cli/omx.js state clear --json"], ["env-argv0-long-wrapper-clear", "env --argv0 fake node dist/cli/omx.js state clear --json"], ["node-title-wrapper-clear", "node --title foo dist/cli/omx.js state clear --json"], ["time-wrapper-clear", "time omx state clear --json"], ["time-format-wrapper-clear", "/usr/bin/time -f x omx state clear --json"], ["time-output-wrapper-clear", "time -o out omx state clear --json"], ["time-cluster-output-wrapper-clear", "/usr/bin/time -ao out omx state clear --json"], ["time-cluster-format-wrapper-clear", "/usr/bin/time -af fmt omx state clear --json"], ["nice-wrapper-clear", "nice -n 5 omx state clear --json"], ["nice-wrapper-write", `nice -n 5 omx state write --input ${stateDeactivationInput} --json`], ["stdbuf-wrapper-clear", "stdbuf -o0 omx state clear --json"], ["stdbuf-wrapper-write", `stdbuf -o0 omx state write --input ${stateDeactivationInput} --json`], ["timeout-wrapper-clear", "timeout 5 omx state clear --json"], ["timeout-wrapper-write", `timeout 5 omx state write --input ${stateDeactivationInput} --json`], ["setsid-wrapper-clear", "setsid omx state clear --json"], ["setsid-wrapper-write", `setsid omx state write --input ${stateDeactivationInput} --json`], ["setsid-wait-wrapper-clear", "setsid -w omx state clear --json"], ["setsid-wait-wrapper-write", `setsid -w omx state write --input ${stateDeactivationInput} --json`], ["time-brace-group-clear", "time { omx state clear --json; }"], ["time-if-condition-clear", "time if omx state clear --json; then :; fi"], ["time-subshell-clear", "time ( omx state clear --json )"], ["time-command-env-node-wrapper-clear", "time command env node dist/cli/omx.js state clear --json"], ["command-time-subshell-clear", "command time ( omx state clear --json )"], ["coproc-wrapper-clear", "coproc omx state clear --json"], ["coproc-wrapper-write", `coproc omx state write --input ${stateDeactivationInput} --json`], ["coproc-name-brace-wrapper-clear", "coproc worker { omx state clear --json; }"], ["coproc-name-brace-wrapper-write", `coproc worker { omx state write --input ${stateDeactivationInput} --json; }`], ["xargs-wrapper-clear", "xargs omx state clear --json /dev/null omx state clear --json"], ["leading-redirection-write", `>/dev/null omx state write --input ${stateDeactivationInput} --json`], ["env-split-trailing-clear", "env -S FOO=bar omx state clear --json"], ["env-split-string-trailing-write", `env --split-string 'FOO=bar' omx state write --input ${stateDeactivationInput} --json`], ["brace-group-clear", "{ omx state clear --json; }"], ["brace-group-write", `{ omx state write --input ${stateDeactivationInput} --json; }`], ["pipeline-to-subshell-clear", "printf 'x' | (omx state clear --json)"], ["pipeline-stderr-to-subshell-write", `printf 'x' |& (node dist/cli/omx.js state write --input ${stateDeactivationInput} --json)`], ["if-condition-clear", "if omx state clear --json; then :; fi"], ["if-condition-write", `if node dist/cli/omx.js state write --input ${stateDeactivationInput} --json; then :; fi`], ["background-clear", "sleep 0 & omx state clear --json"], ["background-node-wrapper-write", `sleep 0 & node dist/cli/omx.js state write --input ${stateDeactivationInput} --json`], ["nested-command-env-wrapper-clear", "command env node dist/cli/omx.js state clear --json"], ["nested-exec-env-wrapper-clear", "exec env node dist/cli/omx.js state clear --json"], ["nested-env-option-wrapper-clear", "env -i env node dist/cli/omx.js state clear --json"], ] as const; for (const [name, command] of cliWrapperPlanningDeactivationCommands) { const blockedCliWrapperPlanningDeactivation = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-${name}`, tool_input: { command }, }, { cwd }, ); assert.equal( (blockedCliWrapperPlanningDeactivation.outputJson as { decision?: string } | null)?.decision, "block", `${command} should be normalized to a protected omx state operation`, ); } const safeStateWriteInput = "'{\"mode\":\"deep-interview\",\"active\":true,\"current_phase\":\"intent-first\"}'"; const safeCliWrapperStateWriteCommands = [ ["env-wrapper-safe-write", `env FOO=bar node dist/cli/omx.js state write --input ${safeStateWriteInput} --json`], ["command-wrapper-safe-write", `command node dist/cli/omx.js state write --input ${safeStateWriteInput} --json`], ["exec-wrapper-safe-write", `exec node dist/cli/omx.js state write --input ${safeStateWriteInput} --json`], ["nested-command-env-wrapper-safe-write", `command env node dist/cli/omx.js state write --input ${safeStateWriteInput} --json`], ["nested-env-command-wrapper-safe-write", `env -i command node dist/cli/omx.js state write --input ${safeStateWriteInput} --json`], ] as const; for (const [name, command] of safeCliWrapperStateWriteCommands) { const allowedCliWrapperStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-${name}`, tool_input: { command }, }, { cwd }, ); assert.equal(allowedCliWrapperStateWrite.outputJson, null, `${command} should defer to backend validation`); } const safeArtifactInputFile = join(cwd, ".omx", "context", "safe-state-input-file.json"); await mkdir(dirname(safeArtifactInputFile), { recursive: true }); await writeJson(safeArtifactInputFile, { mode: "deep-interview", active: true, current_phase: "intent-first" }); const safeCliWrapperInputFileStateWriteCommands = [ ["env-unset-wrapper-safe-input-file", `env -u FOO node dist/cli/omx.js state write --input-file ${safeArtifactInputFile} --json`], ["env-chdir-wrapper-safe-input-file", `env -C ${cwd} node dist/cli/omx.js state write --input-file ${safeArtifactInputFile} --json`], ["command-wrapper-safe-input-file", `command node dist/cli/omx.js state write --input-file ${safeArtifactInputFile} --json`], ["exec-wrapper-safe-input-file", `exec node dist/cli/omx.js state write --input-file ${safeArtifactInputFile} --json`], ] as const; for (const [name, command] of safeCliWrapperInputFileStateWriteCommands) { const allowedCliWrapperInputFileStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-${name}`, tool_input: { command }, }, { cwd }, ); assert.equal(allowedCliWrapperInputFileStateWrite.outputJson, null, `${command} should defer to backend validation`); } const envChdirRelativeInputFileRoot = join(cwd, "payload.json"); const envChdirRelativeInputFileSubdir = join(cwd, "env-chdir-input-file-subdir"); await mkdir(envChdirRelativeInputFileSubdir, { recursive: true }); await writeJson(envChdirRelativeInputFileRoot, { mode: "deep-interview", active: true, current_phase: "intent-first" }); await writeJson(join(envChdirRelativeInputFileSubdir, "payload.json"), { mode: "deep-interview", active: false, current_phase: "intent-first", }); const blockedEnvChdirRelativeInputFileWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-env-chdir-relative-input-file-write", tool_input: { command: `env -C ${envChdirRelativeInputFileSubdir} node ${resolve(cwd, "dist/cli/omx.js")} state write --input-file payload.json --json`, }, }, { cwd }, ); assert.equal( (blockedEnvChdirRelativeInputFileWrite.outputJson as { decision?: string } | null)?.decision, "block", "env -C should resolve --input-file relative to the wrapper cwd, not the hook cwd", ); const blockedEnvChdirLongFlagRelativeInputFileWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-env-chdir-long-relative-input-file-write", tool_input: { command: `env --chdir ${envChdirRelativeInputFileSubdir} node ${resolve(cwd, "dist/cli/omx.js")} state write --input-file payload.json --json`, }, }, { cwd }, ); assert.equal( (blockedEnvChdirLongFlagRelativeInputFileWrite.outputJson as { decision?: string } | null)?.decision, "block", "env --chdir should resolve --input-file relative to the wrapper cwd, not the hook cwd", ); const pnpmChdirRelativeInputFileSubdir = join(cwd, "pnpm-chdir-input-file-subdir"); await mkdir(pnpmChdirRelativeInputFileSubdir, { recursive: true }); await writeJson(join(cwd, "payload.json"), { mode: "deep-interview", active: true, current_phase: "intent-first" }); await writeJson(join(pnpmChdirRelativeInputFileSubdir, "payload.json"), { mode: "deep-interview", active: false, current_phase: "intent-first", }); const blockedPnpmChdirRelativeInputFileWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-pnpm-chdir-relative-input-file-write", tool_input: { command: `pnpm -C ${pnpmChdirRelativeInputFileSubdir} exec omx state write --input-file payload.json --json`, }, }, { cwd }, ); assert.equal( (blockedPnpmChdirRelativeInputFileWrite.outputJson as { decision?: string } | null)?.decision, "block", "pnpm -C should resolve --input-file relative to the wrapper cwd, not the hook cwd", ); const rewrittenArtifactInputFile = join(cwd, ".omx", "context", "rewritten-state-input-file.json"); await mkdir(dirname(rewrittenArtifactInputFile), { recursive: true }); await writeJson(rewrittenArtifactInputFile, { mode: "deep-interview", active: true }); const blockedNestedArtifactInputFileRewriteBeforeStateWriteCommands = [ ["bash-c", `printf '{"mode":"deep-interview","active":false}' > ${rewrittenArtifactInputFile} && bash -c 'omx state write --input-file ${rewrittenArtifactInputFile} --json'`], ["env-split", `printf '{"mode":"deep-interview","active":false}' > ${rewrittenArtifactInputFile} && env -S 'omx state write --input-file ${rewrittenArtifactInputFile} --json'`], ["command-substitution", `printf '{"mode":"deep-interview","active":false}' > ${rewrittenArtifactInputFile} && echo $(omx state write --input-file ${rewrittenArtifactInputFile} --json)`], ["backtick-substitution", "printf '{\"mode\":\"deep-interview\",\"active\":false}' > " + rewrittenArtifactInputFile + " && echo `omx state write --input-file " + rewrittenArtifactInputFile + " --json`"], ] as const; for (const [name, command] of blockedNestedArtifactInputFileRewriteBeforeStateWriteCommands) { const blockedNestedArtifactInputFileRewriteBeforeStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-nested-artifact-input-file-rewrite-${name}`, tool_input: { command }, }, { cwd }, ); assert.equal( (blockedNestedArtifactInputFileRewriteBeforeStateWrite.outputJson as { decision?: string } | null)?.decision, "block", `${command} should fail closed because nested --input-file writes cannot be read safely before execution`, ); } const blockedArtifactInputFileRewriteBeforeStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-artifact-input-file-rewrite-before-write", tool_input: { command: `printf '{"mode":"deep-interview","active":false}' > ${rewrittenArtifactInputFile} && ` + `omx state write --input-file ${rewrittenArtifactInputFile} --json`, }, }, { cwd }, ); assert.equal( (blockedArtifactInputFileRewriteBeforeStateWrite.outputJson as { decision?: string } | null)?.decision, "block", ); const repeatedArtifactInputFile = join(cwd, ".omx", "context", "repeated-state-input-file.json"); await writeJson(repeatedArtifactInputFile, { mode: "deep-interview", active: true }); const blockedRepeatedArtifactInputFileRewriteBeforeSecondStateWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-repeated-artifact-input-file-rewrite", tool_input: { command: `omx state write --input-file ${repeatedArtifactInputFile} --json && ` + `printf '{"mode":"deep-interview","active":false}' > ${repeatedArtifactInputFile} && ` + `omx state write --input-file ${repeatedArtifactInputFile} --json`, }, }, { cwd }, ); assert.equal( (blockedRepeatedArtifactInputFileRewriteBeforeSecondStateWrite.outputJson as { decision?: string } | null)?.decision, "block", ); // An implementation write smuggled alongside an allowed `omx state` command // must not be short-circuited through the allowance. const blockedChainedWrite = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-chained", tool_input: { command: "printf 'x' > src/evil.ts && omx state read --json" }, }, { cwd }, ); assert.equal((blockedChainedWrite.outputJson as { decision?: string } | null)?.decision, "block"); const blockedSourceGeneratedScriptCommands = [ ["source-redirect", "printf 'omx state clear --json\n' > .omx/context/x.sh && source .omx/context/x.sh"], ["bash-redirect", "printf 'omx state clear --json\n' > .omx/context/x.sh && bash .omx/context/x.sh"], ["direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && ./.omx/context/x.sh"], ["time-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && time ./.omx/context/x.sh"], ["command-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && command ./.omx/context/x.sh"], ["env-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && env FOO=bar ./.omx/context/x.sh"], ["timeout-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && timeout 5 ./.omx/context/x.sh"], ["nohup-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && nohup ./.omx/context/x.sh"], ["xargs-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && xargs ./.omx/context/x.sh"], ["coproc-direct-exec", "printf '#!/bin/sh\nomx state clear --json\n' > .omx/context/x.sh && chmod +x .omx/context/x.sh && coproc ./.omx/context/x.sh"], ["sh-c-generated-script", "printf 'omx state clear --json\n' > .omx/context/x.sh && sh -c '. .omx/context/x.sh'"], ["sh-c-at-generated-script", "printf 'omx state clear --json\n' > .omx/context/x.sh && sh -c '. \"$@\"' ignored .omx/context/x.sh"], ["sh-c-positional-generated-script", "printf 'omx state clear --json\n' > .omx/context/x.sh && sh -c '. \"$0\"' .omx/context/x.sh"], ["source-tee", "printf 'omx state clear --json\n' | tee .omx/context/x.sh >/dev/null && source .omx/context/x.sh"], ["source-variable", "tmp=.omx/context/x.sh; printf 'omx state clear --json\n' > \"$tmp\"; source \"$tmp\""], ["source-tee-second-target", "printf 'omx state clear --json\n' | tee .omx/context/x.sh .omx/context/y.sh >/dev/null && source .omx/context/y.sh"], ["bash-tee-append-second-target", "printf 'omx state clear --json\n' | tee -a .omx/context/x.sh .omx/context/y.sh >/dev/null && bash .omx/context/y.sh"], ] as const; for (const [name, command] of blockedSourceGeneratedScriptCommands) { const blockedSourceGeneratedScript = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: `tool-di-state-cli-generated-script-${name}`, tool_input: { command }, }, { cwd }, ); assert.equal( (blockedSourceGeneratedScript.outputJson as { decision?: string } | null)?.decision, "block", `${command} should fail closed because it executes a same-command generated script`, ); } const allowedStateRead = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-read", tool_input: { command: "omx state read --json" }, }, { cwd }, ); assert.equal(allowedStateRead.outputJson, null); const allowedQuotedStateWriteMention = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-quoted-mention", tool_input: { command: "printf '%s\\n' \"omx state write --input '{\\\"mode\\\":\\\"deep-interview\\\",\\\"active\\\":false}'\"" }, }, { cwd }, ); assert.equal(allowedQuotedStateWriteMention.outputJson, null); const allowedHeredocStateWriteMention = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-heredoc-mention", tool_input: { command: "cat > .omx/context/state-example.md <<'EOF'\nomx state write --input '{\"mode\":\"deep-interview\",\"active\":false}'\nEOF" }, }, { cwd }, ); assert.equal(allowedHeredocStateWriteMention.outputJson, null); const blockedUnquotedHeredocSubstitution = await preToolUse( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-artifact", tool_name: "Bash", tool_use_id: "tool-di-state-cli-unquoted-heredoc-substitution", tool_input: { command: "cat > .omx/context/state-example.md < .omx/context/state-example.md < src/scripts/__tests__/codex-native-hook.test.ts <<'EOF'\nexport const x = 1;\nEOF" }, }, { cwd }, ); assert.equal((blockedBash.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows complete ralplan terminal state writes while blocking partial deactivation writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-ralplan-state-input-file-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-ralplan-input-file"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-ralplan-input-file", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "ralplan", phase: "planning", session_id: "sess-ralplan-input-file", active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: "sess-ralplan-input-file" }], }); await writeJson(join(sessionDir, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: "sess-ralplan-input-file", }); const allowedPayload = join(cwd, "ralplan-allowed-state.json"); await writeJson(allowedPayload, { mode: "ralplan", current_phase: "critic-review", active: true }); const allowed = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-input-file-allowed", tool_input: { command: `omx state write --input-file ${allowedPayload} --json` }, }, { cwd }, ); assert.equal(allowed.outputJson, null); const blockedPayload = join(cwd, "ralplan-blocked-state.json"); await writeJson(blockedPayload, { mode: "ralplan", current_phase: "complete" }); const blocked = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-input-file-blocked", tool_input: { command: `omx state write --input-file ${blockedPayload} --json` }, }, { cwd }, ); assert.equal((blocked.outputJson as { decision?: string } | null)?.decision, "block"); const terminalPayload = join(cwd, "ralplan-terminal-state.json"); await writeJson(terminalPayload, { mode: "ralplan", active: false, current_phase: "complete", session_id: "sess-ralplan-input-file", terminal_reason: "consensus approved bounded no-op", }); const allowedTerminalFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-input-file-terminal-allowed", tool_input: { command: `omx state write --input-file ${terminalPayload} --json` }, }, { cwd }, ); assert.equal(allowedTerminalFile.outputJson, null); const mismatchedTerminalPayload = join(cwd, "ralplan-terminal-state-mismatched.json"); await writeJson(mismatchedTerminalPayload, { mode: "ralplan", active: false, current_phase: "complete", session_id: "other-session", }); const blockedMismatchedTerminalFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-input-file-terminal-mismatched", tool_input: { command: `omx state write --input-file ${mismatchedTerminalPayload} --json` }, }, { cwd }, ); assert.equal((blockedMismatchedTerminalFile.outputJson as { decision?: string } | null)?.decision, "block"); const terminalCurrentPhaseAliases = ["finish", "finished", "complete", "completed", "done", "blocked", "blocked-on-user", "blocked_on_user", "failed", "fail", "error", "cancelled", "canceled", "cancel", "aborted", "abort", "userinterlude", "user-interlude", "interrupted", "interrupt", "askuserquestion", "ask-user-question", "askuser", "question"] as const; for (const alias of terminalCurrentPhaseAliases) { const blockedAliasPayload = join(cwd, `ralplan-blocked-state-${alias}.json`); await writeJson(blockedAliasPayload, { mode: "ralplan", current_phase: alias }); const blockedAliasFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: `tool-ralplan-state-input-file-blocked-alias-${alias}`, tool_input: { command: `omx state write --input-file ${blockedAliasPayload} --json` }, }, { cwd }, ); assert.equal( (blockedAliasFile.outputJson as { decision?: string } | null)?.decision, "block", `${alias} current_phase input-file write should deactivate protected ralplan planning`, ); } const blockedModeFlagTerminal = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-mode-flag-terminal", tool_input: { command: "omx state write --mode ralplan --input '{\"current_phase\":\"complete\"}' --json" }, }, { cwd }, ); assert.equal((blockedModeFlagTerminal.outputJson as { decision?: string } | null)?.decision, "block"); const allowedModeFlagTerminal = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-mode-flag-terminal-allowed", tool_input: { command: "omx state write --mode ralplan --input '{\"active\":false,\"current_phase\":\"complete\"}' --json", }, }, { cwd }, ); assert.equal(allowedModeFlagTerminal.outputJson, null); const blockedTerminalThenImplementationWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-terminal-then-implementation-write", tool_input: { command: "omx state write --mode ralplan --input '{\"active\":false,\"current_phase\":\"complete\"}' --json && printf bad > src/leak.ts", }, }, { cwd }, ); assert.equal( (blockedTerminalThenImplementationWrite.outputJson as { decision?: string } | null)?.decision, "block", ); for (const [toolUseId, suffix] of [ ["tool-ralplan-state-terminal-then-touch", "&& touch src/leak.ts"], ["tool-ralplan-state-terminal-then-rm", "; rm src/leak.ts"], ["tool-ralplan-state-terminal-then-readonly", "&& pwd"], ["tool-ralplan-state-terminal-command-substitution", "$(touch src/leak.ts)"], ["tool-ralplan-state-terminal-backtick-substitution", "`touch src/leak.ts`"], ["tool-ralplan-state-terminal-process-substitution", "<(touch src/leak.ts)"], ] as const) { const blockedTerminalThenSuffix = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: toolUseId, tool_input: { command: `omx state write --mode ralplan --input '{"active":false,"current_phase":"complete"}' --json ${suffix}`, }, }, { cwd }, ); assert.equal( (blockedTerminalThenSuffix.outputJson as { decision?: string } | null)?.decision, "block", `${suffix} suffix should keep ralplan terminal state writes standalone`, ); } for (const alias of terminalCurrentPhaseAliases) { const blockedModeFlagAlias = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: `tool-ralplan-state-mode-flag-terminal-alias-${alias}`, tool_input: { command: `omx state write --mode ralplan --input '${JSON.stringify({ current_phase: alias })}' --json` }, }, { cwd }, ); assert.equal( (blockedModeFlagAlias.outputJson as { decision?: string } | null)?.decision, "block", `${alias} current_phase --input write should deactivate protected ralplan planning`, ); } const terminalOutcomeAliasPayloads = [ { runOutcome: "done" }, { lifecycleOutcome: "blocked" }, { terminalOutcome: "aborted" }, ] as const; for (const [index, aliasPayload] of terminalOutcomeAliasPayloads.entries()) { const blockedModeFlagOutcomeAlias = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: `tool-ralplan-state-mode-flag-terminal-outcome-${index}`, tool_input: { command: `omx state write --mode ralplan --input '${JSON.stringify(aliasPayload)}' --json` }, }, { cwd }, ); assert.equal( (blockedModeFlagOutcomeAlias.outputJson as { decision?: string } | null)?.decision, "block", `${Object.keys(aliasPayload)[0]} --input write should deactivate protected ralplan planning`, ); } const blockedOutcomeAliasPayload = join(cwd, "ralplan-blocked-state-camel-terminal-outcome.json"); await writeJson(blockedOutcomeAliasPayload, { mode: "ralplan", terminalOutcome: "finished" }); const blockedOutcomeAliasFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-input-file-blocked-camel-terminal-outcome", tool_input: { command: `omx state write --input-file ${blockedOutcomeAliasPayload} --json` }, }, { cwd }, ); assert.equal((blockedOutcomeAliasFile.outputJson as { decision?: string } | null)?.decision, "block"); const allowedModeFlagSafe = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "Bash", tool_use_id: "tool-ralplan-state-mode-flag-safe", tool_input: { command: "omx state write --mode ralplan --input '{\"current_phase\":\"critic-review\",\"active\":true}' --json" }, }, { cwd }, ); assert.equal(allowedModeFlagSafe.outputJson, null); const blockedRalplanMcpStateWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-ralplan-mcp-state-write-terminal", tool_input: { mode: "ralplan", lifecycle_outcome: "finished" }, }, { cwd }, ); assert.equal((blockedRalplanMcpStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); const blockedNestedRalplanMcpStateWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-ralplan-mcp-state-write-nested-terminal", tool_input: { mode: "ralplan", state: { current_phase: "complete" } }, }, { cwd }, ); assert.equal((blockedNestedRalplanMcpStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); for (const alias of terminalCurrentPhaseAliases) { const blockedNestedRalplanMcpAlias = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_write", tool_use_id: `tool-ralplan-mcp-state-write-nested-terminal-alias-${alias}`, tool_input: { mode: "ralplan", state: { currentPhase: alias } }, }, { cwd }, ); assert.equal( (blockedNestedRalplanMcpAlias.outputJson as { decision?: string } | null)?.decision, "block", `${alias} currentPhase MCP write should deactivate protected ralplan planning`, ); } for (const [index, aliasPayload] of terminalOutcomeAliasPayloads.entries()) { const blockedNestedRalplanMcpOutcomeAlias = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_write", tool_use_id: `tool-ralplan-mcp-state-write-nested-terminal-outcome-${index}`, tool_input: { mode: "ralplan", state: aliasPayload }, }, { cwd }, ); assert.equal( (blockedNestedRalplanMcpOutcomeAlias.outputJson as { decision?: string } | null)?.decision, "block", `${Object.keys(aliasPayload)[0]} MCP write should deactivate protected ralplan planning`, ); } const allowedNestedRalplanMcpStateWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-ralplan-mcp-state-write-nested-safe", tool_input: { mode: "ralplan", state: { current_phase: "critic-review", active: true } }, }, { cwd }, ); assert.equal(allowedNestedRalplanMcpStateWrite.outputJson, null); const blockedRalplanMcpStateClear = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-input-file", tool_name: "mcp__omx_state__state_clear", tool_use_id: "tool-ralplan-mcp-state-clear", tool_input: { mode: "ralplan" }, }, { cwd }, ); assert.equal((blockedRalplanMcpStateClear.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits hook-specific deny ralplan PreToolUse JSON for wrapped implementation writes on the live CLI path", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-ralplan-wrapper-live-")); const sessionId = "sess-cli-ralplan-wrapper-live"; const stateDir = join(cwd, ".omx", "state"); const targetPath = join(cwd, "src", "scripts", "__tests__", "codex-native-hook.test.ts"); try { await mkdir(dirname(targetPath), { recursive: true }); await writeFile(targetPath, "seed\n", "utf-8"); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: sessionId, }); const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-cli-ralplan-wrapper-live", tool_name: "Bash", tool_input: { command: "bash -lc \"cat > src/scripts/__tests__/codex-native-hook.test.ts <<'EOF'\nexport const wrappedRalplanMutation = true;\nEOF\"", }, }, { cwd }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput"]); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /Ralplan is active \(phase: critic-review\)/); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /implementation\/write tools are blocked/); assert.match(String(hookSpecificOutput.additionalContext ?? ""), /Write only planning artifacts/); assert.equal(output.decision, undefined); assert.equal(output.reason, undefined); assert.equal(output.systemMessage, undefined); assert.equal(await readFile(targetPath, "utf-8"), "seed\n"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks MCP state_clear for standalone protected planning modes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-standalone-mcp-clear-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", active_skills: [{ skill: "deep-interview", phase: "planning", active: true }], }); await writeJson(join(stateDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const blockedDeepInterviewClear = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "mcp__omx_state__state_clear", tool_use_id: "tool-standalone-di-mcp-clear", tool_input: { mode: "deep-interview" }, }, { cwd }, ); assert.equal((blockedDeepInterviewClear.outputJson as { decision?: string } | null)?.decision, "block"); await writeJson(join(stateDir, "skill-active-state.json"), { version: 1, active: true, skill: "ralplan", phase: "planning", active_skills: [{ skill: "ralplan", phase: "planning", active: true }], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", }); const blockedRalplanClear = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "mcp__omx_state__state_clear", tool_use_id: "tool-standalone-ralplan-mcp-clear", tool_input: { mode: "ralplan" }, }, { cwd }, ); assert.equal((blockedRalplanClear.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows read-only diagnostics mentioning apply_patch while deep-interview blocks real apply_patch invocations", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-grep-apply-patch-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-grep"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-grep", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-grep", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-grep" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-grep", }); const allowedGrep = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-grep", tool_input: { command: 'grep -n "apply_patch" dist/scripts/codex-native-hook.js' }, }, { cwd }, ); assert.equal(allowedGrep.outputJson, null); const allowedSubshellGrep = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-grep-subshell", tool_input: { command: '(grep -n "apply_patch" dist/scripts/codex-native-hook.js)' }, }, { cwd }, ); assert.equal(allowedSubshellGrep.outputJson, null); // Double-quoted spans that merely mention the literal token, expand a // parameter, or run a substitution that is not `apply_patch` stay allowed // — the quoted-substitution fix must not over-block read-only diagnostics. const allowedQuotedMention = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-grep-quoted-mention", tool_input: { command: 'echo "${apply_patch} $(echo apply_patch)"' }, }, { cwd }, ); assert.equal(allowedQuotedMention.outputJson, null); const blockedApplyPatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-apply-patch-invoke", tool_input: { command: "apply_patch <<'EOF'\n*** Begin Patch\n*** Add File: src/leak.ts\n+export const x = 1;\n*** End Patch\nEOF" }, }, { cwd }, ); assert.equal((blockedApplyPatch.outputJson as { decision?: string } | null)?.decision, "block"); const blockedEnvAssignmentApplyPatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-apply-patch-env-assignment", tool_input: { command: "FOO=bar apply_patch <<'EOF'\n*** Begin Patch\n*** Add File: src/leak.ts\n+export const x = 1;\n*** End Patch\nEOF" }, }, { cwd }, ); assert.equal((blockedEnvAssignmentApplyPatch.outputJson as { decision?: string } | null)?.decision, "block"); const blockedEnvWrapperApplyPatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: "tool-di-apply-patch-env-wrapper", tool_input: { command: "env FOO=bar apply_patch <<'EOF'\n*** Begin Patch\n*** Add File: src/leak.ts\n+export const x = 1;\n*** End Patch\nEOF" }, }, { cwd }, ); assert.equal((blockedEnvWrapperApplyPatch.outputJson as { decision?: string } | null)?.decision, "block"); const heredocBody = "\n*** Begin Patch\n*** Add File: src/leak.ts\n+export const x = 1;\n*** End Patch\nEOF"; const blockedRealApplyPatchForms: Array<{ id: string; command: string }> = [ { id: "tool-di-apply-patch-env-i", command: `env -i apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-env-unset", command: `env -u FOO apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-env-i-assignment", command: `env -i FOO=bar apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-assignment-env", command: `FOO=bar env apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-exec-env-assignment", command: `exec env FOO=bar apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-absolute-path", command: `/usr/bin/apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-relative-path", command: `./apply_patch <<'EOF'${heredocBody}` }, { id: "tool-di-apply-patch-subshell", command: `(apply_patch <<'EOF'${heredocBody}\n)` }, { id: "tool-di-apply-patch-subshell-spaced", command: `( apply_patch <<'EOF'${heredocBody}\n)` }, { id: "tool-di-apply-patch-double-subshell", command: `((apply_patch <<'EOF'${heredocBody}\n))` }, { id: "tool-di-apply-patch-pipe-subshell", command: `true | (apply_patch <<'EOF'${heredocBody}\n)` }, { id: "tool-di-apply-patch-subshell-env", command: `(env apply_patch <<'EOF'${heredocBody}\n)` }, { id: "tool-di-apply-patch-command-substitution", command: `x=$(apply_patch <<'EOF'${heredocBody}\n)` }, { id: "tool-di-apply-patch-brace-group", command: `{ apply_patch <<'EOF'${heredocBody}\n}` }, // Command substitution runs even inside double quotes, so quoting the // already-blocked `$(…)` / `` `…` `` form must not bypass the guard. { id: "tool-di-apply-patch-quoted-command-substitution", command: `echo "$(apply_patch <<'EOF'${heredocBody}\n)"` }, { id: "tool-di-apply-patch-quoted-backtick", command: `echo "\`apply_patch <<'EOF'${heredocBody}\`"` }, { id: "tool-di-apply-patch-quoted-command-substitution-prefixed", command: `echo "patched: $(apply_patch <<'EOF'${heredocBody}\n) done"` }, ]; for (const form of blockedRealApplyPatchForms) { const blockedForm = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-grep", tool_name: "Bash", tool_use_id: form.id, tool_input: { command: form.command }, }, { cwd }, ); assert.equal( (blockedForm.outputJson as { decision?: string } | null)?.decision, "block", `expected deep-interview to block real apply_patch form: ${form.command}`, ); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks operator-adjacent transport bypass forms while still allowing a safe direct-wrapper state write", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-transport-bypass-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-di-transport-bypass"; const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: sessionId, thread_id: "thread-di-transport-bypass", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: sessionId, thread_id: "thread-di-transport-bypass", }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: sessionId, thread_id: "thread-di-transport-bypass", rounds: [{ answer: "Use CLI wrapper normalization for protected planning state commands." }], }); const preToolUse = (tool_use_id: string, command: string) => dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-di-transport-bypass", tool_name: "Bash", tool_use_id, tool_input: { command }, }, { cwd }, ); const absoluteCliEntry = resolve(cwd, "dist/cli/omx.js"); const blockedCommands = [ `printf 'omx state clear --json'|bash`, `printf 'omx state clear --json'|&bash`, `bash<<<'omx state clear --json'`, `bash<<'EOF'\nomx state clear --json\nEOF`, `bash< <(printf 'omx state clear --json')`, `env -S "bash -c 'omx state clear --json'"`, `env --split-string "bash -c 'omx state clear --json'"`, `env -S "node dist/cli/omx.js state clear --json"`, `env --split-string "node dist/cli/omx.js state clear --json"`, `env -S "dist/cli/omx.js state clear --json"`, `env --split-string "dist/cli/omx.js state clear --json"`, `node dist/cli/omx.js state clear --json`, `node ./dist/cli/omx.js state clear --json`, `node ${absoluteCliEntry} state clear --json`, `bun dist/cli/omx.js state clear --json`, `tsx src/cli/omx.ts state clear --json`, `dist/cli/omx.js state clear --json`, `./dist/cli/omx.js state clear --json`, `exec node dist/cli/omx.js state clear --json`, `command node dist/cli/omx.js state clear --json`, `command env VAR=x node dist/cli/omx.js state clear --json`, `env VAR=x node dist/cli/omx.js state clear --json`, `exec env -S "node dist/cli/omx.js state clear --json"`, ]; for (const [index, command] of blockedCommands.entries()) { const blocked = await preToolUse(`tool-di-transport-bypass-block-${index}`, command); assert.equal((blocked.outputJson as { decision?: string } | null)?.decision, "block", `expected transport guard to block: ${command}`); } const allowedCommands = [ `echo '|& <<< <<'`, `node dist/cli/omx.js state write --input '{"mode":"deep-interview","active":true}' --json`, ]; for (const [index, command] of allowedCommands.entries()) { const allowed = await preToolUse(`tool-di-transport-bypass-allow-${index}`, command); assert.equal(allowed.outputJson, null, `expected safe transport form to remain allowed: ${command}`); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows deep-interview same-command literal variable redirects to artifacts while blocking variable redirects outside them", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-var-redirect-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-var-redirect"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-var-redirect", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-var-redirect", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-var-redirect" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-var-redirect", }); const allowedVarRedirect = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-var-redirect", tool_name: "Bash", tool_use_id: "tool-di-var-redirect-allow", tool_input: { command: 'SNAP=".omx/context/example.md"\ncat > "$SNAP" <<\'EOF\'\ncontent\nEOF' }, }, { cwd }, ); assert.equal(allowedVarRedirect.outputJson, null); const blockedVarRedirect = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-var-redirect", tool_name: "Bash", tool_use_id: "tool-di-var-redirect-block", tool_input: { command: 'SNAP="src/leak.ts"\ncat > "$SNAP" <<\'EOF\'\ncontent\nEOF' }, }, { cwd }, ); assert.equal((blockedVarRedirect.outputJson as { decision?: string } | null)?.decision, "block"); const blockedUnresolvedVarRedirect = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-var-redirect", tool_name: "Bash", tool_use_id: "tool-di-var-redirect-unresolved", tool_input: { command: 'cat > "$SNAP" <<\'EOF\'\ncontent\nEOF' }, }, { cwd }, ); assert.equal((blockedUnresolvedVarRedirect.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows deep-interview apply_patch artifact writes from freeform patch text while blocking outside paths", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-apply-patch-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-apply-patch"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-apply-patch", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-apply-patch", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-apply-patch" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-apply-patch", }); const allowedAddFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-add", tool_input: { input: "*** Begin Patch\n*** Add File: .omx/context/findings.md\n+# Findings\n*** End Patch\n", }, }, { cwd }, ); assert.equal(allowedAddFile.outputJson, null); const allowedUpdateFile = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "ApplyPatch", tool_use_id: "tool-di-apply-patch-update", tool_input: { input: "*** Begin Patch\n*** Update File: .omx/specs/deep-interview-demo.md\n@@\n-old\n+new\n*** End Patch\n", }, }, { cwd }, ); assert.equal(allowedUpdateFile.outputJson, null); const allowedStateWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-state", tool_input: { input: "*** Begin Patch\n*** Add File: .omx/state/deep-interview-notes.json\n+{}\n*** End Patch\n", }, }, { cwd }, ); assert.equal(allowedStateWrite.outputJson, null); const blockedProtectedStatePatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-protected-state", tool_input: { input: "*** Begin Patch\n*** Update File: .omx/state/sessions/sess-di-apply-patch/deep-interview-state.json\n@@\n-{}\n+{\"active\":false}\n*** End Patch\n", }, }, { cwd }, ); assert.equal((blockedProtectedStatePatch.outputJson as { decision?: string } | null)?.decision, "block"); const blockedOutsidePath = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-outside", tool_input: { input: "*** Begin Patch\n*** Add File: src/implementation.ts\n+export const x = 1;\n*** End Patch\n", }, }, { cwd }, ); assert.equal((blockedOutsidePath.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedOutsidePath.outputJson as { reason?: string } | null)?.reason ?? ""), /Deep-interview is active/); const blockedMixedPaths = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-mixed", tool_input: { input: "*** Begin Patch\n*** Add File: .omx/context/ok.md\n+ok\n*** Add File: src/leak.ts\n+leak\n*** End Patch\n", }, }, { cwd }, ); assert.equal((blockedMixedPaths.outputJson as { decision?: string } | null)?.decision, "block"); const blockedUnparseablePatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-apply-patch", tool_name: "apply_patch", tool_use_id: "tool-di-apply-patch-empty", tool_input: { input: "not a recognizable patch" }, }, { cwd }, ); assert.equal((blockedUnparseablePatch.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Ralplan read-only inspection and planning artifact writes while blocking implementation targets", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-ralplan-guard-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-guard"; const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(sessionDir, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const preToolUse = async (tool_name: string, tool_use_id: string, tool_input: Record) => dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, tool_name, tool_use_id, tool_input, }, { cwd }, ); const readOnlyCommands = [ "git status --short", "ls -1 .omx/plans", "find .omx/plans -type f -name '*.md'", "grep -RIn \"Scholastic\" doc tests .omx/plans .omx/context", "rg \"Scholastic\" doc tests .omx/plans .omx/context", "sed -n '1,80p' .omx/plans/demo.md", "cat .omx/plans/demo.md", "git status --short; ls -1 .omx/plans; grep -RIn \"Scholastic\" doc tests .omx/plans .omx/context", ]; for (const [index, command] of readOnlyCommands.entries()) { const result = await preToolUse("Bash", `tool-ralplan-read-${index}`, { command }); assert.equal(result.outputJson, null, `read-only command should be allowed: ${command}`); } for (const path of [ ".omx/context/findings.md", ".omx/plans/issue-2863.md", ".omx/specs/issue-2863.md", ".omx/state/required-planning-state.json", ]) { const writeResult = await preToolUse("Write", `tool-ralplan-write-${path}`, { file_path: path, content: "ok" }); assert.equal(writeResult.outputJson, null, `Write should be allowed for ${path}`); } for (const protectedPath of [ ".omx/state/sessions/sess-ralplan-guard/ralplan-state.json", ".omx/state/sessions/sess-ralplan-guard/autopilot-state.json", ".omx/state/sessions/sess-ralplan-guard/skill-active-state.json", ]) { const protectedResult = await preToolUse("Write", `tool-ralplan-protected-${protectedPath}`, { file_path: protectedPath, content: "{}", }); assert.equal( (protectedResult.outputJson as { decision?: string } | null)?.decision, "block", `${protectedPath} should not be model-writable during ralplan`, ); } // A non-deactivating `omx state write` defers to the gate-enforcing // state_write backend (same enforcement for CLI and MCP). const allowedStateCliMutation = await preToolUse("Bash", "tool-ralplan-state-cli-write", { command: "omx state write --input '{\"mode\":\"autopilot\",\"current_phase\":\"ultragoal\"}' --json", }); assert.equal(allowedStateCliMutation.outputJson, null); // Broad deactivation vectors such as `omx state clear` are still rejected // at the transport boundary; complete consensus terminal writes are // covered by the state-write closeout tests. const blockedStateClear = await preToolUse("Bash", "tool-ralplan-state-cli-clear", { command: "omx state clear --json", }); assert.equal((blockedStateClear.outputJson as { decision?: string } | null)?.decision, "block"); const allowedPatchAdd = await preToolUse("apply_patch", "tool-ralplan-patch-add", { input: "*** Begin Patch\n*** Add File: .omx/plans/issue-2863.md\n+# Plan\n*** End Patch\n", }); assert.equal(allowedPatchAdd.outputJson, null); const allowedPatchUpdate = await preToolUse("ApplyPatch", "tool-ralplan-patch-update", { input: "*** Begin Patch\n*** Update File: .omx/plans/issue-2863.md\n@@\n-old\n+new\n*** End Patch\n", }); assert.equal(allowedPatchUpdate.outputJson, null); const allowedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-allow", { command: "printf '\\nmore\\n' >> .omx/plans/issue-2863.md", }); assert.equal(allowedRedirect.outputJson, null); const allowedTee = await preToolUse("Bash", "tool-ralplan-tee-allow", { command: "printf '\\nmore\\n' | tee -a .omx/specs/issue-2863.md", }); assert.equal(allowedTee.outputJson, null); const allowedCombinedSedArtifact = await preToolUse("Bash", "tool-ralplan-sed-combined-artifact-allow", { command: "sed -Ei 's/old/new/' .omx/plans/issue-2863.md", }); assert.equal(allowedCombinedSedArtifact.outputJson, null); for (const [toolUseId, command] of [ ["tool-ralplan-tmp-python-stdin-exec", "python < .omx/tmp/sess-ralplan-guard/run.txt"], ["tool-ralplan-tmp-node-stdin-exec", "node < .omx/tmp/sess-ralplan-guard/run.txt"], ["tool-ralplan-tmp-ruby-stdin-exec", "ruby < .omx/tmp/sess-ralplan-guard/run.txt"], ["tool-ralplan-tmp-perl-stdin-exec", "perl < .omx/tmp/sess-ralplan-guard/run.txt"], ["tool-ralplan-tmp-sh-stdin-exec", "sh < .omx/tmp/sess-ralplan-guard/run.txt"], ["tool-ralplan-tmp-bash-stdin-exec", "bash < .omx/tmp/sess-ralplan-guard/run.txt"], [ "tool-ralplan-tmp-same-command-stdin-exec", [ "python3 - <<'PY'", "from pathlib import Path", "Path('.omx/tmp/sess-ralplan-guard/run.txt').write_text('print(1)')", "PY", "python < .omx/tmp/sess-ralplan-guard/run.txt", ].join("\n"), ], ] as const) { const blockedTmpStdin = await preToolUse("Bash", toolUseId, { command }); assert.equal((blockedTmpStdin.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match(JSON.stringify(blockedTmpStdin.outputJson), /generated-script transport|\.omx\/tmp/); } const allowedReadOnlyEditors = await preToolUse("Bash", "tool-ralplan-read-only-editors-allow", { command: "sed -n '1,20p' src/runtime.ts; perl -ne 'print if $. < 3' src/runtime.ts", }); assert.equal(allowedReadOnlyEditors.outputJson, null); const blockedCombinedSedSource = await preToolUse("Bash", "tool-ralplan-sed-combined-source-block", { command: "sed -Ei 's/old/new/' src/runtime.ts", }); assert.equal((blockedCombinedSedSource.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedCombinedSedSource.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/runtime\.ts/); const blockedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-block", { command: "printf 'bad' > src/implementation.ts", }); assert.equal((blockedRedirect.outputJson as { decision?: string } | null)?.decision, "block"); const redirectReason = String((blockedRedirect.outputJson as { reason?: string } | null)?.reason ?? ""); assert.match(redirectReason, /Bash redirect write/); assert.match(redirectReason, /src\/implementation\.ts/); const blockedEdit = await preToolUse("Edit", "tool-ralplan-edit-block", { file_path: "src/implementation.ts", old_string: "a", new_string: "b", }); assert.equal((blockedEdit.outputJson as { decision?: string } | null)?.decision, "block"); const editReason = String((blockedEdit.outputJson as { reason?: string } | null)?.reason ?? ""); assert.match(editReason, /Edit path/); assert.match(editReason, /src\/implementation\.ts/); const blockedMixedPatch = await preToolUse("apply_patch", "tool-ralplan-patch-mixed", { input: "*** Begin Patch\n*** Add File: .omx/plans/ok.md\n+ok\n*** Add File: src/leak.ts\n+leak\n*** End Patch\n", }); assert.equal((blockedMixedPatch.outputJson as { decision?: string } | null)?.decision, "block"); const mixedReason = String((blockedMixedPatch.outputJson as { reason?: string } | null)?.reason ?? ""); assert.match(mixedReason, /apply_patch target/); assert.match(mixedReason, /src\/leak\.ts/); const blockedUnparseablePatch = await preToolUse("apply_patch", "tool-ralplan-patch-unparseable", { input: "not a recognizable patch", }); assert.equal((blockedUnparseablePatch.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedUnparseablePatch.outputJson as { reason?: string } | null)?.reason ?? ""), /apply_patch target extraction failed/); const blockedUnresolvedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-unresolved", { command: "cat > \"$PLAN_PATH\" <<'EOF'\ncontent\nEOF", }); assert.equal((blockedUnresolvedRedirect.outputJson as { decision?: string } | null)?.decision, "block"); const unresolvedReason = String((blockedUnresolvedRedirect.outputJson as { reason?: string } | null)?.reason ?? ""); assert.match(unresolvedReason, /unresolved Bash write target/); assert.match(unresolvedReason, /\$PLAN_PATH/); const blockedTraversal = await preToolUse("Write", "tool-ralplan-traversal-block", { file_path: ".omx/plans/../../src/leak.ts", content: "bad", }); assert.equal((blockedTraversal.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedTraversal.outputJson as { reason?: string } | null)?.reason ?? ""), /\.omx\/plans\/\.\.\/\.\.\/src\/leak\.ts/); const blockedAbsolute = await preToolUse("Write", "tool-ralplan-absolute-block", { file_path: "/tmp/leak.ts", content: "bad", }); assert.equal((blockedAbsolute.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedAbsolute.outputJson as { reason?: string } | null)?.reason ?? ""), /\/tmp\/leak\.ts/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Autopilot ralplan planning artifacts while blocking implementation writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-ralplan-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-autopilot-ralplan-artifact"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-ralplan-artifact", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ralplan", session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", active_skills: [ { skill: "autopilot", phase: "ralplan", active: true, session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", }); const preToolUse = async (tool_name: string, tool_use_id: string, tool_input: Record) => dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", tool_name, tool_use_id, tool_input, }, { cwd }, ); const issueReadOnlyEvidenceCommand = [ "printf 'collecting planning evidence\\n'", "ROOT=$(git rev-parse --show-toplevel)", "BASE=$(git merge-base HEAD origin/dev)", "git log --oneline ${BASE}..HEAD", "git diff --name-status ${BASE}..HEAD", "sed -n '1,120p' src/scripts/codex-native-hook.ts", "grep -RIn 'commandHasDeepInterviewWriteIntent' src/scripts/codex-native-hook.ts", "printf '%s\\n' \"$ROOT\"", ].join("; "); const allowedIssueReadOnlyBash = await preToolUse("Bash", "tool-autopilot-ralplan-read-only-bash", { command: issueReadOnlyEvidenceCommand, }); assert.equal(allowedIssueReadOnlyBash.outputJson, null); for (const command of ["git merge origin/dev", "git reset --hard HEAD", "git checkout-index -f -a", "git merge-file ours base theirs"]) { const blockedDestructiveGit = await preToolUse("Bash", `tool-autopilot-ralplan-${command.replace(/[^a-z0-9]+/gi, "-")}`, { command, }); assert.equal( (blockedDestructiveGit.outputJson as { decision?: string } | null)?.decision, "block", `${command} should stay blocked during Autopilot ralplan`, ); } const allowedPlanWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", tool_name: "Write", tool_use_id: "tool-autopilot-ralplan-plan-write", tool_input: { file_path: ".omx/plans/prd-omx-y7a.md", content: "# Plan" }, }, { cwd }, ); assert.equal(allowedPlanWrite.outputJson, null); const allowedSpecEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", tool_name: "Edit", tool_use_id: "tool-autopilot-ralplan-spec-edit", tool_input: { file_path: ".omx/specs/omx-y7a.md", old_string: "old", new_string: "new" }, }, { cwd }, ); assert.equal(allowedSpecEdit.outputJson, null); const allowedPythonPlanWrite = await preToolUse("Bash", "tool-autopilot-ralplan-python-plan-write", { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans').mkdir(parents=True, exist_ok=True) Path('.omx/plans/rebase-pr3010-ultragoal-fix-plan.md').write_text('planning text') PY`, }); assert.equal(allowedPythonPlanWrite.outputJson, null); const blockedPythonAllowedMkdirDynamicSourceWrite = await preToolUse("Bash", "tool-autopilot-ralplan-python-allowed-mkdir-dynamic-source-write", { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans').mkdir(parents=True, exist_ok=True) (Path('src') / 'generated.ts').write_text('implementation') PY`, }); assert.equal((blockedPythonAllowedMkdirDynamicSourceWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedPythonAllowedMkdirDynamicSourceWrite.outputJson as { reason?: string } | null)?.reason ?? ""), /write intent did not identify an allowed planning artifact path/); const blockedPythonSourceWrite = await preToolUse("Bash", "tool-autopilot-ralplan-python-source-write", { command: `python3 - <<'PY' from pathlib import Path Path('src/generated.ts').write_text('implementation') PY`, }); assert.equal((blockedPythonSourceWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedPythonSourceWrite.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/generated\.ts/); const blockedPythonMixedWrite = await preToolUse("Bash", "tool-autopilot-ralplan-python-mixed-write", { command: `python3 - <<'PY' from pathlib import Path Path('.omx/plans/rebase-pr3010-ultragoal-fix-plan.md').write_text('planning text') Path('src/generated').mkdir(parents=True, exist_ok=True) PY`, }); assert.equal((blockedPythonMixedWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedPythonMixedWrite.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/generated/); const blockedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-ralplan-artifact", thread_id: "thread-autopilot-ralplan-artifact", tool_name: "Edit", tool_use_id: "tool-autopilot-ralplan-src-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal((blockedImplementationEdit.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((blockedImplementationEdit.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/implementation\.ts/); assert.match(String((blockedImplementationEdit.outputJson as { reason?: string } | null)?.reason ?? ""), /not under allowed planning artifact paths/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Ralplan Beads tracker metadata writes during planning", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-ralplan-beads-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-ralplan-beads"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-ralplan-beads", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "ralplan", phase: "planning", session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", active_skills: [ { skill: "ralplan", phase: "planning", active: true, session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", }, ], }); await writeJson(join(sessionDir, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", }); const allowedWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", tool_name: "Write", tool_use_id: "tool-ralplan-beads-write", tool_input: { file_path: ".beads/issues.db", content: "metadata" }, }, { cwd }, ); assert.equal(allowedWrite.outputJson, null); const allowedEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", tool_name: "Edit", tool_use_id: "tool-ralplan-beads-edit", tool_input: { file_path: ".beads/tasks/issue.json", old_string: "old", new_string: "new" }, }, { cwd }, ); assert.equal(allowedEdit.outputJson, null); const allowedPatch = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", tool_name: "apply_patch", tool_use_id: "tool-ralplan-beads-apply-patch", tool_input: { input: "*** Begin Patch\n*** Add File: .beads/tasks/issue.json\n+{}\n*** End Patch\n", }, }, { cwd }, ); assert.equal(allowedPatch.outputJson, null); const allowedCommands = [ "bd --db .beads/issues.db create 'Issue title'", "bd --db .beads/issues.db update OMX-1 --title 'Issue title'", "bd --db .beads/issues.db edit OMX-1 --title 'Issue title'", "bd --db .beads/issues.db comments add OMX-1 'evidence'", "bd --db .beads/issues.db close OMX-1", "bd --db .beads/issues.db reopen OMX-1", "bd --db .beads/issues.db status OMX-1", "bd --db .beads/issues.db dep add OMX-1 OMX-2", ]; for (const [index, command] of allowedCommands.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-ralplan-beads", thread_id: "thread-ralplan-beads", tool_name: "Bash", tool_use_id: `tool-ralplan-beads-bd-${index}`, tool_input: { command }, }, { cwd }, ); assert.equal(result.outputJson, null, `expected Beads metadata command to be allowed: ${command}`); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks unsafe Beads tracker targets and mixed implementation writes during Autopilot ralplan planning", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-beads-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-autopilot-beads-block"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-beads-block", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ralplan", session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", active_skills: [ { skill: "autopilot", phase: "ralplan", active: true, session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", }); const blockedCommands = [ "bd --db /tmp/outside.db create 'Issue title'", "bd --db ../outside.db create 'Issue title'", "bd --db .beads create 'Issue title'", "bd --db $DB create 'Issue title'", "DB=.beads/issues.db bd --db $DB create 'Issue title'", "env bd --db /tmp/outside.db create 'Issue title'", "command bd --db /tmp/outside.db create 'Issue title'", "bd --db .beads/issues.db export", "bd --db .beads/issues.db create 'Issue title' > src/leak.ts", "bd --db .beads/issues.db create 'Issue title'; cat > src/leak.ts", ]; for (const [index, command] of blockedCommands.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", tool_name: "Bash", tool_use_id: `tool-autopilot-beads-block-${index}`, tool_input: { command }, }, { cwd }, ); assert.equal( (result.outputJson as { decision?: string } | null)?.decision, "block", `expected unsafe Beads command to be blocked: ${command}`, ); } const mentionWithImplementationWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", tool_name: "Bash", tool_use_id: "tool-autopilot-beads-mention-write", tool_input: { command: "echo bd --db .beads/issues.db create > src/leak.ts" }, }, { cwd }, ); assert.equal((mentionWithImplementationWrite.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(String((mentionWithImplementationWrite.outputJson as { reason?: string } | null)?.reason ?? ""), /src\/leak\.ts/); const blockedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-beads-block", thread_id: "thread-autopilot-beads-block", tool_name: "Edit", tool_use_id: "tool-autopilot-beads-src-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal((blockedImplementationEdit.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Autopilot rework implementation writes while ralplan remains guarded", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-rework-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-autopilot-rework"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-rework", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "rework", session_id: "sess-autopilot-rework", thread_id: "thread-autopilot-rework", active_skills: [ { skill: "autopilot", phase: "rework", active: true, session_id: "sess-autopilot-rework", thread_id: "thread-autopilot-rework", }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "rework", review_cycle: 2, review_verdict: { clean: false, recommendation: "REQUEST_CHANGES", findings: ["src/implementation.ts"] }, session_id: "sess-autopilot-rework", thread_id: "thread-autopilot-rework", }); const allowedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-autopilot-rework", thread_id: "thread-autopilot-rework", tool_name: "Edit", tool_use_id: "tool-autopilot-rework-src-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(allowedImplementationEdit.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks typed agent-role-only implementation writes under active ralplan", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-typed-agent-role-ralplan-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-typed-executor-ralplan"; const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, thread_id: "thread-typed-executor-ralplan", active_skills: [ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId, thread_id: "thread-typed-executor-ralplan", }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", session_id: sessionId, thread_id: "thread-typed-executor-ralplan", }); const allowedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-typed-executor-ralplan", agent_role: "executor", tool_name: "Edit", tool_use_id: "tool-typed-executor-ralplan-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(allowedImplementationEdit.outputJson?.decision, "block"); assert.match(String(allowedImplementationEdit.outputJson?.reason ?? ""), /Autopilot planning is active \(phase: ralplan\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows typed agent-role implementation writes under active ralplan when trusted provenance is present", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-trusted-typed-agent-role-ralplan-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-trusted-typed-executor-ralplan"; const leaderThreadId = "thread-trusted-typed-executor-ralplan-leader"; const childThreadId = "thread-trusted-typed-executor-ralplan"; const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId, cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, thread_id: leaderThreadId, active_skills: [ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId, thread_id: leaderThreadId, }, ], }); await writeJson(join(sessionDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", started_at: "2026-04-19T00:00:00.000Z", updated_at: "2026-04-19T00:10:00.000Z", session_id: sessionId, thread_id: leaderThreadId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: "2026-04-19T00:10:00.000Z", threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: "2026-04-19T00:00:00.000Z", last_seen_at: "2026-04-19T00:10:00.000Z", turn_count: 1 }, [childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: "2026-04-19T00:01:00.000Z", last_seen_at: "2026-04-19T00:10:00.000Z", turn_count: 1, leader_thread_id: leaderThreadId }, }, }, }, }); const allowedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: childThreadId, agent_role: "executor", source: { subagent: { thread_spawn: { parent_thread_id: leaderThreadId, }, }, }, tool_name: "Edit", tool_use_id: "tool-trusted-typed-executor-ralplan-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(allowedImplementationEdit.outputJson, null); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("blocks typed thread-spawn provenance when the parent belongs to another leader session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-stale-typed-thread-spawn-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stale-typed-thread-spawn"; const leaderThreadId = "thread-stale-typed-leader"; const childThreadId = "thread-stale-typed-child"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId, cwd }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { version: 1, active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, thread_id: leaderThreadId, active_skills: [ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId, thread_id: leaderThreadId }, ], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, thread_id: leaderThreadId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: "2026-04-19T00:10:00.000Z", threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: "2026-04-19T00:00:00.000Z", last_seen_at: "2026-04-19T00:10:00.000Z", turn_count: 1 }, }, }, }, }); const blockedImplementationEdit = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: childThreadId, agent_role: "executor", source: { subagent: { thread_spawn: { parent_thread_id: "thread-other-leader", }, }, }, tool_name: "Edit", tool_use_id: "tool-stale-typed-child-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(blockedImplementationEdit.outputJson?.decision, "block"); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("allows null-device fd redirects while deep-interview blocks real Bash writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-null-redirect-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-null-redirect"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-null-redirect", cwd }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-null-redirect", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-null-redirect" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-null-redirect", }); const allowedCommands = [ "find application -type d -name 'bug-tracking*' 2>/dev/null | head -20", "find application -type d -name 'bug-tracking*' 2> /dev/null | head -20", "find application -type d -name 'bug-tracking*' 2>NUL | head -20", "find application -type d -name 'bug-tracking*' 1>/dev/null", "find application -type d -name 'bug-tracking*' &>/dev/null", ]; for (const [index, command] of allowedCommands.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-null-redirect", tool_name: "Bash", tool_use_id: `tool-di-null-redirect-${index}`, tool_input: { command }, }, { cwd }, ); assert.equal(result.outputJson, null, command); } const blockedCommands = [ "find application -type d -name 'bug-tracking*' 2>errors.log | head -20", "find application -type d -name 'bug-tracking*' > /tmp/bug-tracking.txt", "find application -type d -name 'bug-tracking*' | tee /dev/null", ]; for (const [index, command] of blockedCommands.entries()) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-null-redirect", tool_name: "Bash", tool_use_id: `tool-di-real-redirect-${index}`, tool_input: { command }, }, { cwd }, ); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", "sess-di-handoff"); await mkdir(sessionDir, { recursive: true }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-di-handoff", active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }], }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-di-handoff", }); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-di-handoff", prompt: "$ralph implement the clarified spec in src/implementation.ts", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-di-handoff", tool_name: "Edit", tool_use_id: "tool-di-post-handoff-edit", tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(result.outputJson, null); const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean }; assert.equal(completed.active, false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-danger", tool_input: { command: "rm -rf dist" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: "Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for neutral pwd", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-neutral-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-neutral", tool_input: { command: "pwd" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("records native subagent capacity exhaustion from spawn_agent PostToolUse output", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-record-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-subagent-capacity-record", thread_id: "thread-subagent-capacity-record", turn_id: "turn-subagent-capacity-record", tool_name: "multi_agent_v1.spawn_agent", tool_response: { error: "collab spawn failed: agent thread limit reached", }, }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); const blocker = JSON.parse( await readFile(join(cwd, ".omx", "state", "native-subagent-capacity-blocker.json"), "utf-8"), ) as Record; assert.equal(blocker.reason, "agent_thread_limit_reached"); assert.equal(blocker.session_id, "sess-subagent-capacity-record"); assert.equal(blocker.thread_id, "thread-subagent-capacity-record"); assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent"); assert.match(String(blocker.error_summary), /agent thread limit reached/); assert.ok(Date.parse(String(blocker.expires_at)) > Date.parse(String(blocker.observed_at))); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks close_agent cleanup after recent native subagent capacity exhaustion", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-block-")); try { await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-subagent-capacity-close-block", thread_id: "thread-subagent-capacity-close-block", turn_id: "turn-subagent-capacity-close-block", tool_name: "multi_agent_v1.spawn_agent", tool_response: "collab spawn failed: agent thread limit reached", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-subagent-capacity-close-block", thread_id: "thread-subagent-capacity-close-block", tool_name: "multi_agent_v1.close_agent", tool_input: { target: "019ecc36-stale" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /agent thread limit reached/); assert.match(JSON.stringify(result.outputJson), /multi_agent_v1\.close_agent/); assert.match(JSON.stringify(result.outputJson), /multi_tool_use\.parallel/); assert.match(JSON.stringify(result.outputJson), /bounded capacity blocker/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits hook-specific deny PreToolUse CLI stdout for close_agent capacity blocks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-subagent-capacity-close-block-")); try { parseSingleJsonStdout(runNativeHookCli({ hook_event_name: "PostToolUse", cwd, session_id: "sess-cli-subagent-capacity-close-block", thread_id: "thread-cli-subagent-capacity-close-block", turn_id: "turn-cli-subagent-capacity-close-block", tool_name: "multi_agent_v1.spawn_agent", tool_response: "collab spawn failed: agent thread limit reached", }, { cwd })); const result = runNativeHookCliResult({ hook_event_name: "PreToolUse", cwd, session_id: "sess-cli-subagent-capacity-close-block", thread_id: "thread-cli-subagent-capacity-close-block", tool_name: "multi_agent_v1.close_agent", tool_input: { target: "019ecc36-stale" }, }, { cwd }); assert.equal(result.status, 0, result.stderr || result.stdout); const output = parseSingleJsonStdout(result.stdout); const hookSpecificOutput = output.hookSpecificOutput as Record; assert.deepEqual(Object.keys(output).sort(), ["hookSpecificOutput"]); assert.equal(hookSpecificOutput.hookEventName, "PreToolUse"); assert.equal(hookSpecificOutput.permissionDecision, "deny"); assert.match(String(hookSpecificOutput.permissionDecisionReason ?? ""), /Native subagent capacity was exhausted recently/); assert.match(String(hookSpecificOutput.additionalContext ?? ""), /agent thread limit reached/); assert.match(String(hookSpecificOutput.additionalContext ?? ""), /Do not call multi_agent_v1\.close_agent/); assert.equal(output.decision, undefined); assert.equal(output.reason, undefined); assert.equal(output.systemMessage, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks parallel close_agent cleanup after recent native subagent capacity exhaustion", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-parallel-close-block-")); try { await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-subagent-capacity-parallel-close-block", thread_id: "thread-subagent-capacity-parallel-close-block", tool_name: "multi_agent_v1.spawn_agent", tool_response: "agent thread limit reached", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-subagent-capacity-parallel-close-block", thread_id: "thread-subagent-capacity-parallel-close-block", tool_name: "multi_tool_use.parallel", tool_input: { tool_uses: [ { recipient_name: "multi_agent_v1.close_agent", parameters: { target: "stale-1" } }, { recipient_name: "multi_agent_v1.close_agent", parameters: { target: "stale-2" } }, ], }, }, { cwd }, ); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Do not call multi_agent_v1\.close_agent/); assert.match(JSON.stringify(result.outputJson), /do not batch close_agent through multi_tool_use\.parallel/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block close_agent without a recent native capacity blocker", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-no-blocker-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-subagent-capacity-close-no-blocker", thread_id: "thread-subagent-capacity-close-no-blocker", tool_name: "multi_agent_v1.close_agent", tool_input: { target: "completed-agent" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block close_agent when the native capacity blocker is expired", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-expired-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "native-subagent-capacity-blocker.json"), { schema_version: 1, reason: "agent_thread_limit_reached", session_id: "sess-subagent-capacity-close-expired", error_summary: "agent thread limit reached", observed_at: "2026-06-20T00:00:00.000Z", expires_at: "2026-06-20T00:30:00.000Z", cwd, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "sess-subagent-capacity-close-expired", thread_id: "thread-subagent-capacity-close-expired", tool_name: "multi_agent_v1.close_agent", tool_input: { target: "completed-agent" }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-slop-warn", tool_input: { command: [ "cat > src/runtime.ts <<'EOF'", "export function loadRuntime() {", " // implement a quick hack fallback if it fails", " return process.env.RUNTIME || 'local';", "}", "EOF", ].join("\n"), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined); assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse"); assert.match(JSON.stringify(result.outputJson), /don't make potential slop/); assert.match(JSON.stringify(result.outputJson), /architect/); assert.match(JSON.stringify(result.outputJson), /environment issue/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not warn on PreToolUse for read-only fallback text inspection", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-readonly-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-slop-readonly", tool_input: { command: "rg \"quick hack fallback if it fails\" src docs" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("warns when a read-only command is chained before sloppy fallback writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-chained-write-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-slop-chained-write", tool_input: { command: [ "rg foo src && cat > src/runtime.ts < { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-grounded-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-slop-grounded", tool_input: { command: [ "cat > src/compat.ts <<'EOF'", "export function resolveCompatMode() {", " // temporary fallback because legacy compatibility needs fail-safe startup behavior", " return 'legacy';", "}", "// Tested: npm test", "EOF", ].join("\n"), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop for untracked non-Bash-style sloppy fallback source edits", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-untracked-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile( join(cwd, "src", "runtime.ts"), [ "export function loadRuntime() {", " // implement a quick hack fallback if it fails", " return process.env.RUNTIME || 'local';", "}", ].join("\n"), ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-untracked" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((result.outputJson as { stopReason?: string } | null)?.stopReason, "sloppy_fallback_diff_audit"); assert.match(JSON.stringify(result.outputJson), /src\/runtime\.ts/); assert.match(JSON.stringify(result.outputJson), /grounded design/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking repeated Stop while sloppy fallback diff remains", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-repeat-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile( join(cwd, "src", "runtime.ts"), [ "export function loadRuntime() {", " // implement a quick hack fallback if it fails", " return process.env.RUNTIME || 'local';", "}", ].join("\n"), ); const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-repeat", turn_id: "turn-repeat" }; const first = await dispatchCodexNativeHook(payload, { cwd }); const repeated = await dispatchCodexNativeHook({ ...payload, stop_hook_active: true }, { cwd }); assert.equal((first.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((repeated.outputJson as { decision?: string } | null)?.decision, "block"); assert.equal((repeated.outputJson as { stopReason?: string } | null)?.stopReason, "sloppy_fallback_diff_audit"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop for unstaged tracked sloppy fallback source edits", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-unstaged-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile(join(cwd, "src", "runtime.ts"), "export const runtime = 'base';\n"); execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" }); await writeFile( join(cwd, "src", "runtime.ts"), [ "export function loadRuntime() {", " // just bypass fallback if it fails", " return process.env.RUNTIME || 'local';", "}", ].join("\n"), ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-unstaged" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /unstaged/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop from a subdirectory cwd for untracked sloppy source elsewhere", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-subdir-"); try { await mkdir(join(cwd, "src", "nested"), { recursive: true }); await writeFile(join(cwd, "src", "nested", "anchor.ts"), "export const anchor = true;\n"); await writeFile( join(cwd, "src", "runtime.ts"), [ "export function loadRuntime() {", " // implement a quick hack fallback if it fails", " return process.env.RUNTIME || 'local';", "}", ].join("\n"), ); const subdir = join(cwd, "src", "nested"); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd: subdir, session_id: "sess-stop-slop-subdir" }, { cwd: subdir }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /src\/runtime\.ts/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop for staged sloppy fallback source edits", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-staged-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile(join(cwd, "src", "runtime.ts"), "export const runtime = 'base';\n"); execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" }); await writeFile( join(cwd, "src", "runtime.ts"), [ "export function loadRuntime() {", " // temporary workaround fallback if it fails", " return process.env.RUNTIME || 'local';", "}", ].join("\n"), ); execFileSync("git", ["add", "src/runtime.ts"], { cwd, stdio: "ignore" }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-staged" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /staged/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop for grounded compatibility fallback source edits", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-grounded-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile( join(cwd, "src", "compat.ts"), [ "export function resolveCompatMode() {", " // temporary fallback for legacy startup", " // compatibility fail-safe tested by regression coverage", " return 'legacy';", "}", ].join("\n"), ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-grounded" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop when existing nearby source context grounds a new fallback line", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-existing-ground-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile( join(cwd, "src", "compat.ts"), [ "export function resolveCompatMode() {", " // compatibility fail-safe tested by regression coverage", " return 'legacy';", "}", ].join("\n"), ); execFileSync("git", ["add", "src/compat.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "initial"], { cwd, stdio: "ignore" }); await writeFile( join(cwd, "src", "compat.ts"), [ "export function resolveCompatMode() {", " // compatibility fail-safe tested by regression coverage", " // temporary fallback if it fails", " return 'legacy';", "}", ].join("\n"), ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-existing-ground" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop for source-adjacent test file fallback wording", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-test-file-"); try { await mkdir(join(cwd, "src"), { recursive: true }); await writeFile( join(cwd, "src", "runtime.test.ts"), "it('documents no quick hack fallback if it fails', () => {});\n", ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-test-file" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop for docs-only fallback wording", async () => { const cwd = await initTempGitRepo("omx-native-hook-stop-slop-docs-"); try { await mkdir(join(cwd, "docs"), { recursive: true }); await writeFile( join(cwd, "docs", "notes.md"), "Do not implement a quick hack fallback if it fails.\n", ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-slop-docs" }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps git commit Lore enforcement ahead of sloppy fallback advisory", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-git-priority-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-slop-git-priority", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Lore protocol/); assert.doesNotMatch(JSON.stringify(result.outputJson), /don't make potential slop/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git commit with supported response shape when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); const hookSpecificOutput = (result.outputJson as { hookSpecificOutput?: Record }) .hookSpecificOutput ?? {}; assert.equal("additionalContext" in hookSpecificOutput, false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "1"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-env-enabled", tool_input: { command: 'git commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Lore protocol/); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { delete process.env.OMX_LORE_COMMIT_GUARD; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-disabled", tool_input: { command: 'git commit -m "fix: use conventional commit"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => { await withLoreGuardConfig("1", "config-enabled", async (cwd) => { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-config-enabled", tool_input: { command: 'git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Lore protocol/); }); }); it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => { await withLoreGuardConfig("0", "config-disabled", async (cwd) => { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-config-disabled", tool_input: { command: 'git commit -m "fix: use conventional commit"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); }); }); it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => { await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-config-inline-enabled", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Lore protocol/); }); }); it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => { await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-config-codex-home-unset", tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); }); }); it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-inline-disabled", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows inline disabled guard to override an enabled process env", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "1"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-inline-override-disabled", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-newline-assignment", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => { await withLoreGuardConfig("1", "config-env-unset", async (cwd) => { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-config-env-unset", tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); }); }); it("restores default-off Lore guard when env -u unsets an enabled process env", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "1"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-env-unset", tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("restores default-off Lore guard when env -i clears an enabled process env", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "1"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-env-ignore", tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-inline-unknown", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats Lore commit guard disabled values as trim and case tolerant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = " OFF "; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-off", tool_input: { command: 'git commit -m "chore: conventional commit"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("keeps Lore commit enforcement disabled for unknown guard values", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "maybe"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-lore-unknown", tool_input: { command: 'git commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("continues to later PreToolUse checks when Lore commit guard is disabled", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-disabled-destructive-")); const original = process.env.OMX_LORE_COMMIT_GUARD; try { process.env.OMX_LORE_COMMIT_GUARD = "false"; const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-lore-disabled-destructive", tool_input: { command: "rm -rf dist" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.doesNotMatch(JSON.stringify(result.outputJson), /Lore protocol/); assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/); } finally { if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD; else process.env.OMX_LORE_COMMIT_GUARD = original; await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for `git help commit`", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-help-commit-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-help-commit", tool_input: { command: "git help commit" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for `git config alias.ci commit`", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-config-alias-commit-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-config-alias-commit", tool_input: { command: "git config alias.ci commit" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for `git tag commit`", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-tag-commit-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-tag-commit", tool_input: { command: "git tag commit" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse env-prefixed git commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-env-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git commit when git options appear before the real commit subcommand", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-option-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-option-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse env wrapper-prefixed git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-env-wrapper-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-env-wrapper-invalid", tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse env flag wrapper-prefixed git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-env-flag-wrapper-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid", tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse env value-taking wrapper-prefixed git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-env-value-wrapper-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid", tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse path-qualified Windows git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-windows-path-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-windows-path-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse quoted backslash Windows git.exe commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-exe-commit-windows-backslash-path-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse path-qualified git commit when the inline message is not Lore-compliant", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-path-invalid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-path-invalid", tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add a blank line after the subject before the narrative body.", "- Add a narrative body paragraph explaining the decision context.", "- Add at least one Lore trailer such as `Constraint:`, `Confidence:`, or `Tested:`.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git commit when the message comes from an external source", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-file-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-file", tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Use inline `git commit -m ...` paragraphs for Lore-format commits in this path; file/editor/reuse/fixup message sources are not inspectable safely from pre-tool-use enforcement.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse git commit when Lore trailers exist but the OmX co-author trailer is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-missing-omx-coauthor-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-missing-omx-coauthor", tool_input: { command: [ 'OMX_LORE_COMMIT_GUARD=1 git commit', '-m "Prevent invalid history from bypassing Lore enforcement"', '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."', '-m "Constraint: Native PreToolUse can only inspect the Bash command text"', '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "git commit is blocked until the inline commit message satisfies the Lore format and includes the required OmX co-author trailer.", hookSpecificOutput: { hookEventName: "PreToolUse", }, systemMessage: [ "git commit is blocked until the inline commit message follows the Lore protocol and includes `Co-authored-by: OmX `.", "- Add the required co-author trailer: `Co-authored-by: OmX `.", ].join("\n"), }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for Lore-compliant git commit with OmX co-author trailer", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-valid-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-valid", tool_input: { command: [ 'OMX_LORE_COMMIT_GUARD=1 git commit', '-m "Prevent invalid history from bypassing Lore enforcement"', '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."', '-m "Constraint: Native PreToolUse can only inspect the Bash command text"', '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for compact inline Lore commit with only OmX co-author trailer", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-coauthor-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-compact-coauthor", tool_input: { command: [ 'OMX_LORE_COMMIT_GUARD=1 git commit', '-m "Launch lvisai.xyz intro site"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on PreToolUse for body-omitted inline Lore commit with decision trailers", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-trailers-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-compact-trailers", tool_input: { command: [ 'OMX_LORE_COMMIT_GUARD=1 git commit', '-m "Launch lvisai.xyz intro site"', '-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks PreToolUse compact inline Lore commit when the blank separator is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-compact-no-separator-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-compact-no-separator", tool_input: { command: [ 'OMX_LORE_COMMIT_GUARD=1 git commit', '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /Add a blank line after the subject/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("warns on PreToolUse git commit when mapped source changes lack staged docs refresh", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-warn-")); try { execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await mkdir(join(cwd, "src", "scripts"), { recursive: true }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); await writeFile(join(cwd, "README.md"), "base\n", "utf-8"); execFileSync("git", ["add", "README.md", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-doc-refresh-warn", tool_input: { command: [ 'git commit', '-m "Keep native hooks aligned with docs"', '-m "Update the stop hook internals without refreshing the operator docs yet."', '-m "Constraint: native hook warning MVP must remain non-blocking on commit path"', '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined); assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse"); assert.match(JSON.stringify(result.outputJson), /Document-refresh warning/); assert.match(JSON.stringify(result.outputJson), /docs\/codex-native-hooks\.md/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not warn on PreToolUse when relevant docs are staged", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-docs-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); await mkdir(join(cwd, "docs"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); await writeFile(join(cwd, "docs", "codex-native-hooks.md"), "initial\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts", "docs/codex-native-hooks.md"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); await writeFile(join(cwd, "docs", "codex-native-hooks.md"), "updated\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts", "docs/codex-native-hooks.md"], { cwd, stdio: "ignore" }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-doc-refresh-docs", tool_input: { command: [ 'git commit', '-m "Keep native hooks aligned with docs"', '-m "Update the stop hook internals and refresh the native hook docs together."', '-m "Constraint: native hook warning MVP must remain non-blocking on commit path"', '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not run commit-path document-refresh against payload cwd when git -C targets another repo", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-chdir-")); const otherRepo = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-other-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["init"], { cwd: otherRepo, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: otherRepo, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd: otherRepo, stdio: "ignore" }); await writeFile(join(otherRepo, "README.md"), "base\n", "utf-8"); execFileSync("git", ["add", "README.md"], { cwd: otherRepo, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd: otherRepo, stdio: "ignore" }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-doc-refresh-chdir", tool_input: { command: [ `git -C ${JSON.stringify(otherRepo)}`, 'commit', '-m "Keep native hooks aligned with docs"', '-m "Document-refresh check should not inspect the caller cwd when commit targets another repo."', '-m "Constraint: alternate git targets are skipped unless hook-side repo resolution is added explicitly"', '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); await rm(otherRepo, { recursive: true, force: true }); } }); it("suppresses PreToolUse document-refresh warning when commit message includes an exemption", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-document-refresh-exempt-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-git-commit-doc-refresh-exempt", tool_input: { command: [ 'git commit', '-m "Keep native hooks aligned with docs"', '-m "Update the stop hook internals without docs refresh because behavior is internal-only."', '-m "Constraint: native hook warning MVP must remain non-blocking on commit path"', `-m "${DOCUMENT_REFRESH_EXEMPTION_PREFIX} internal-only behavior verified"`, '-m "Tested: node --test dist/scripts/__tests__/codex-native-hook.test.js"', '-m "Co-authored-by: OmX "', ].join(" "), }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns PostToolUse remediation guidance for command-not-found output", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-failure-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-fail", tool_input: { command: "foo --version" }, tool_response: "{\"exit_code\":127,\"stdout\":\"\",\"stderr\":\"bash: foo: command not found\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "The Bash output indicates a command/setup failure that should be fixed before retrying.", hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: "Bash reported `command not found`, `permission denied`, or a missing file/path. Verify the command, dependency installation, PATH, file permissions, and referenced paths before retrying.", }, }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent when successful search output contains old Bash failure text", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-successful-search-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-search-log", tool_input: { command: "rg 'command not found' .omx/logs" }, tool_response: JSON.stringify({ exit_code: 0, stdout: "old-session.log: bash: foo: command not found", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent when Bash stdout only contains failure-like source text", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-failure-source-text-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-source-text", tool_input: { command: "sed -n '1,40p' hook-source.ts" }, tool_response: "const text = 'bash: foo: command not found';\nconst detail = 'permission denied';", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent for rc-zero build logs that mention missing grep paths", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-build-log-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-build-log", tool_input: { command: "npm run build" }, tool_response: JSON.stringify({ exit_code: 0, stdout: "build passed\nnote: grep fixture says no such file or directory", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat Bash output containing MCP transport text as MCP transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-source-text-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-mcp-source-text", tool_input: { command: "sed -n '580,620p' codex-native-pre-post.ts" }, tool_response: JSON.stringify({ exit_code: 0, stdout: "reason: 'MCP transport closed before response over stdio pipe closed'", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent when successful output includes prior hook context text", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-recursive-context-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-hook-context", tool_input: { command: "cat transcript.txt" }, tool_response: JSON.stringify({ exit_code: 0, stdout: "Bash reported `command not found`, `permission denied`, or a missing file/path. Verify the command, dependency installation, PATH, file permissions, and referenced paths before retrying.", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent when successful Bash output quotes MCP transport warnings", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-bash-mcp-quote-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-bash-mcp-quote", tool_input: { command: "cat diagnostic-log.txt" }, tool_response: JSON.stringify({ exit_code: 0, stdout: "diagnostic log quoted: MCP transport closed; stdio pipe closed before response", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent when Bash hard-failure text has no parsed exit code", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-bash-unparsed-failure-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-bash-unparsed-failure", tool_input: { command: "cat captured-output.txt" }, tool_response: "captured transcript says: bash: foo: command not found", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat non-MCP source output containing detector constants as MCP transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-read-mcp-source-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Read", tool_use_id: "tool-read-mcp-source", tool_input: { file_path: "src/scripts/codex-native-pre-post.ts" }, tool_response: "const MCP_TRANSPORT_FAILURE_PATTERNS = [/transport closed/i, /server disconnected/i];\nconst context = /\\bmcp\\b/i;", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat non-MCP docs stdout mentioning closed MCP transport as transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-docs-mcp-log-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "ShellOutput", tool_use_id: "tool-docs-mcp-log", tool_response: JSON.stringify({ stdout: "Troubleshooting note: MCP transport closed after the server disconnected in an old log.", stderr: "", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not MCP-block non-MCP command output with unrelated stderr and MCP transport stdout", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-nonmcp-mixed-output-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "ShellOutput", tool_use_id: "tool-nonmcp-mixed-output", tool_response: JSON.stringify({ stdout: "captured log line: MCP transport closed before response", stderr: "grep: fixture.txt: No such file or directory", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("still blocks MCP-like raw transport failures", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-raw-transport-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-raw-transport", tool_response: "transport closed after server disconnected", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns PostToolUse MCP transport fallback guidance for clear MCP transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-transport", tool_input: { mode: "team", active: true }, tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); const output = result.outputJson as { decision?: string; reason?: string; hookSpecificOutput?: { additionalContext?: string }; } | null; assert.equal(output?.decision, "block"); assert.equal( output?.reason, "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.", ); const additionalContext = String( output?.hookSpecificOutput?.additionalContext ?? "", ); assert.match( additionalContext, /omx state write --input/, ); assert.match( additionalContext, /plain Node stdio processes/i, ); assert.match( additionalContext, /read-stall-state/, ); assert.match( additionalContext, /OMX_MCP_TRANSPORT_DEBUG=1/, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not classify non-transport MCP failures as transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-nontransport-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-nontransport", tool_input: { active: true }, tool_response: "{\"error\":\"validation failed\",\"details\":\"mode is required\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("marks active team state failed on MCP transport death without deleting team state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-mcp-transport-")); const previousCwd = process.cwd(); try { process.chdir(cwd); await initTeamState( "transport-team", "task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-transport" }, ); await writeJson(join(cwd, ".omx", "state", "team-state.json"), { active: true, team_name: "transport-team", current_phase: "team-exec", }); await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-transport", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-transport-team", tool_input: { mode: "team", active: true }, tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}", }, { cwd }, ); const phase = await readTeamPhase("transport-team", cwd); const attention = await readTeamLeaderAttention("transport-team", cwd); assert.equal(phase?.current_phase, "failed"); assert.equal(attention?.leader_attention_reason, "mcp_transport_dead"); assert.equal(attention?.leader_attention_pending, true); assert.equal(existsSync(join(cwd, ".omx", "state", "team", "transport-team")), true); } finally { process.chdir(previousCwd); await rm(cwd, { recursive: true, force: true }); } }); it("marks canonical team state failed when native payload session ids differ during MCP transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-native-transport-")); const previousCwd = process.cwd(); const canonicalSessionId = "omx-canonical-session"; const nativeSessionId = "codex-native-session"; try { process.chdir(cwd); await writeSessionStart(cwd, canonicalSessionId); const sessionPath = join(cwd, ".omx", "state", "session.json"); const sessionState = JSON.parse( await readFile(sessionPath, "utf-8"), ) as { session_id?: string; native_session_id?: string }; await writeFile( sessionPath, JSON.stringify( { ...sessionState, native_session_id: nativeSessionId, }, null, 2, ), ); await initTeamState( "transport-team", "task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: canonicalSessionId }, ); await writeJson(join(cwd, ".omx", "state", "team-state.json"), { active: true, team_name: "transport-team", current_phase: "team-exec", }); await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: nativeSessionId, tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-transport-team-native", tool_input: { mode: "team", active: true }, tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}", }, { cwd }, ); const phase = await readTeamPhase("transport-team", cwd); const attention = await readTeamLeaderAttention("transport-team", cwd); assert.equal(phase?.current_phase, "failed"); assert.equal(attention?.leader_attention_reason, "mcp_transport_dead"); assert.equal(attention?.leader_attention_pending, true); assert.equal(attention?.leader_session_id, canonicalSessionId); } finally { process.chdir(previousCwd); await rm(cwd, { recursive: true, force: true }); } }); it("does not block ordinary non-zero grep output in PostToolUse", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-grep-nonzero-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-grep-nonzero", tool_input: { command: "grep -R missing-pattern src | head -20" }, tool_response: "{\"exit_code\":1,\"stdout\":\"src/example.ts:TODO\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block ordinary non-zero diagnostic output in PostToolUse", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-diagnostic-nonzero-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-diagnostic-nonzero", tool_input: { command: "find src -name nope -print" }, tool_response: "{\"exit_code\":1,\"stdout\":\"searched 10 files\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats stderr-only informative non-zero output as reviewable instead of a generic failure", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-stderr-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-useful-stderr", tool_input: { command: "gh pr checks" }, tool_response: "{\"exit_code\":8,\"stdout\":\"\",\"stderr\":\"build pending\\nlint pass\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "The Bash command returned a non-zero exit code but produced useful output that should be reviewed before retrying.", hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: "The Bash output appears informative despite the non-zero exit code. Review and report the output before retrying instead of assuming the command simply failed.", }, }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats non-zero gh pr checks style output as informative instead of a generic failure", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-useful", tool_input: { command: "gh pr checks" }, tool_response: "{\"exit_code\":8,\"stdout\":\"build\\tpending\\t2m\\nlint\\tpass\\t18s\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "The Bash command returned a non-zero exit code but produced useful output that should be reviewed before retrying.", hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: "The Bash output appears informative despite the non-zero exit code. Review and report the output before retrying instead of assuming the command simply failed.", }, }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats wrapped gh pr checks output as reviewable", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-gh-wrapped-")); try { for (const command of [ "GH_PAGER=cat gh pr checks", "env GH_TOKEN=ghp_testtoken gh pr checks", "/usr/bin/env gh pr checks", "env -- gh pr checks", "env -C repo gh pr checks", "/usr/bin/gh pr checks", "gh --repo owner/repo pr checks", "echo a; gh pr checks", "cd repo && gh pr checks", ]) { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: `tool-useful-${command}`, tool_input: { command }, tool_response: "{\"exit_code\":8,\"stdout\":\"build pending\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat heredoc gh pr checks text as a reviewable command", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-gh-heredoc-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-heredoc-gh-checks", tool_input: { command: "cat <<'EOF'\ngh pr checks\nEOF\nfalse" }, tool_response: "{\"exit_code\":1,\"stdout\":\"gh pr checks\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat echoed gh pr checks text as a reviewable command", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-gh-echo-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-echo-gh-checks", tool_input: { command: "echo gh pr checks" }, tool_response: "{\"exit_code\":1,\"stdout\":\"gh pr checks\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns MCP transport-death guidance and preserves failed team state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-dead-")); try { await initTeamState( "mcp-transport-dead-team", "transport failure fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-mcp-dead" }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-mcp-dead", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-dead", tool_response: JSON.stringify({ error: "transport closed", message: "MCP server disconnected", }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/); const hookSpecificOutput = result.outputJson?.hookSpecificOutput as { hookEventName?: string; additionalContext?: string; } | undefined; assert.equal(hookSpecificOutput?.hookEventName, "PostToolUse"); assert.match( String(hookSpecificOutput?.additionalContext || ""), /Retry via CLI parity with `omx state write --input '\{\}' --json`\./, ); assert.match( String(hookSpecificOutput?.additionalContext || ""), /omx team api read-stall-state/, ); const phase = JSON.parse( await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "phase.json"), "utf-8"), ) as { current_phase?: string; transitions?: Array<{ reason?: string }> }; assert.equal(phase.current_phase, "failed"); assert.equal(phase.transitions?.at(-1)?.reason, "mcp_transport_dead"); const attention = JSON.parse( await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "leader-attention.json"), "utf-8"), ) as { leader_attention_reason?: string; attention_reasons?: string[] }; assert.equal(attention.leader_attention_reason, "mcp_transport_dead"); assert.ok(attention.attention_reasons?.includes("mcp_transport_dead")); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("stays silent on neutral successful PostToolUse output", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-neutral-")); try { const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, tool_name: "Bash", tool_use_id: "tool-ok", tool_input: { command: "pwd" }, tool_response: "{\"exit_code\":0,\"stdout\":\"/repo\",\"stderr\":\"\"}", }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns CLI fallback guidance and preserves failed team state on clear MCP transport death", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-")); try { await initTeamState( "transport-team", "transport failure fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-mcp-transport" }, ); await writeJson(join(cwd, ".omx", "state", "team-state.json"), { active: true, team_name: "transport-team", current_phase: "team-exec", }); const result = await dispatchCodexNativeHook( { hook_event_name: "PostToolUse", cwd, session_id: "sess-stop-mcp-transport", tool_name: "mcp__omx_state__state_write", tool_use_id: "tool-mcp-fail", tool_input: { mode: "team", active: true }, tool_response: JSON.stringify({ error: "MCP transport closed unexpectedly", exit_code: 1, }), }, { cwd }, ); assert.equal(result.omxEventName, "post-tool-use"); assert.deepEqual(result.outputJson, { decision: "block", reason: "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.", hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: "Clear MCP transport-death signal detected. Preserve current team/runtime state. Retry via CLI parity with `omx state write --input '{\"mode\":\"team\",\"active\":true}' --json`. OMX MCP servers are plain Node stdio processes, so they still shut down when stdin/transport closes. If this happened during team runtime, inspect first with `omx team status ` or `omx team api read-stall-state --input '{\"team_name\":\"\"}' --json`, and only force cleanup after capturing needed state. For root-cause debugging, rerun with `OMX_MCP_TRANSPORT_DEBUG=1` to log why the stdio transport closed.", }, }); const phase = await readTeamPhase("transport-team", cwd); const attention = await readTeamLeaderAttention("transport-team", cwd); assert.equal(phase?.current_phase, "failed"); assert.equal(attention?.leader_attention_reason, "mcp_transport_dead"); } finally { await rm(cwd, { recursive: true, force: true }); } }); for (const rootActiveCase of [ { mode: "autopilot", phase: "execution" }, { mode: "ultrawork", phase: "executing" }, { mode: "ultraqa", phase: "diagnose" }, ] as const) { it(`returns Stop continuation output from root ${rootActiveCase.mode} state when no session is active`, async () => { const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-root-${rootActiveCase.mode}-`)); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, `${rootActiveCase.mode}-state.json`), { active: true, mode: rootActiveCase.mode, current_phase: rootActiveCase.phase, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX ${rootActiveCase.mode} is still active (phase: ${rootActiveCase.phase}); continue the task and gather fresh verification evidence before stopping.`, stopReason: `${rootActiveCase.mode}_${rootActiveCase.phase}`, systemMessage: `OMX ${rootActiveCase.mode} is still active (phase: ${rootActiveCase.phase}).`, }); } finally { await rm(cwd, { recursive: true, force: true }); } }); } it("returns Stop continuation output while Autopilot is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-autopilot"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-autopilot", "autopilot-state.json"), { active: true, current_phase: "execution", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-autopilot", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX autopilot is still active (phase: execution); continue the task and gather fresh verification evidence before stopping.", stopReason: "autopilot_execution", systemMessage: "OMX autopilot is still active (phase: execution).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses parent Autopilot Stop continuation in side conversations", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-side-conversation-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-autopilot-side-conversation"; const transcriptPath = join(cwd, "side-conversation-rollout.jsonl"); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "deep-interview", }); await writeFile( transcriptPath, `${JSON.stringify({ type: "message", role: "user", content: [ "Side conversation boundary.", "Everything before this boundary is inherited history from the parent thread. It is reference context only. It is not your current task.", "Only messages submitted after this boundary are active user instructions for this side conversation.", "You are a side-conversation assistant, separate from the main thread.", ].join("\n\n"), })}\n`, "utf-8", ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-autopilot-side-conversation", transcript_path: transcriptPath, last_assistant_message: "Waiting for a new side-conversation question.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("requires Autopilot code review after a compact-boundary Stop exemption", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-review-compact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-autopilot-review-compact"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "code-review", state: { phase_cycle: ["ralplan", "ralph", "code-review"], handoff_artifacts: { ralplan: ".omx/plans/prd-issue-2366.md", ralph: { verification: ["npm test"] }, code_review: null, }, review_verdict: null, }, }); const compactBoundary = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, stop_reason: "context compact", }, { cwd }, ); const resumedStop = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(compactBoundary.omxEventName, "stop"); assert.equal(compactBoundary.outputJson, null); assert.equal(resumedStop.omxEventName, "stop"); assert.deepEqual(resumedStop.outputJson, { decision: "block", reason: "OMX autopilot is still active (phase: code-review); continue the task and gather fresh verification evidence before stopping.", stopReason: "autopilot_code-review", systemMessage: "OMX autopilot is still active (phase: code-review). Run the required $code-review step before completing or clearing Autopilot state.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses duplicate Autopilot planning Stop replays so stale planning state cannot loop indefinitely", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-planning-replay-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-autopilot-planning-replay"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-autopilot-planning-replay", "autopilot-state.json"), { active: true, current_phase: "planning", }); const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-autopilot-planning-replay", thread_id: "thread-stop-autopilot-planning-replay", turn_id: "turn-stop-autopilot-planning-replay", last_assistant_message: "Autopilot planning is still active.", }; const first = await dispatchCodexNativeHook(payload, { cwd }); const replay = await dispatchCodexNativeHook( { ...payload, stop_hook_active: true, }, { cwd }, ); assert.equal(first.omxEventName, "stop"); assert.deepEqual(first.outputJson, { decision: "block", reason: "OMX autopilot is still active (phase: planning); continue the task and gather fresh verification evidence before stopping.", stopReason: "autopilot_planning", systemMessage: "OMX autopilot is still active (phase: planning).", }); assert.equal(replay.omxEventName, "stop"); assert.equal(replay.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-autopilot-terminal-run-state"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, active: false, mode: "autopilot", outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-20T11:00:00.000Z", updated_at: "2026-05-20T11:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-autopilot-terminal-run-state", turn_id: "turn-stop-autopilot-terminal-run-state-1", last_assistant_message: "Done. Verification passed.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-autopilot-active-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, active: true, mode: "autopilot", outcome: "continue", current_phase: "ralplan", updated_at: "2026-05-20T11:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-autopilot-active-ralplan", turn_id: "turn-stop-autopilot-active-ralplan-1", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.", stopReason: "autopilot_ralplan", systemMessage: "OMX autopilot is still active (phase: ralplan).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd }); await writeJson(join(stateDir, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "planning", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); for (const staleRootCase of [ { mode: "autopilot", phase: "execution" }, { mode: "ultrawork", phase: "executing" }, { mode: "ultraqa", phase: "diagnose" }, ] as const) { it(`does not block Stop from stale root ${staleRootCase.mode} state when the explicit session directory is missing`, async () => { const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-missing-session-${staleRootCase.mode}-`)); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, `${staleRootCase.mode}-state.json`), { active: true, mode: staleRootCase.mode, current_phase: staleRootCase.phase, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "missing-session", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); } it("does not block Stop when an explicit blocked_on_user run_outcome is present on a mode state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-blocked-outcome-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-autopilot-blocked-outcome"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-autopilot-blocked-outcome", "autopilot-state.json"), { active: true, current_phase: "execution", run_outcome: "blocked_on_user", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-autopilot-blocked-outcome", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output while Ultrawork is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-ultrawork"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-ultrawork", "ultrawork-state.json"), { active: true, current_phase: "executing", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-ultrawork" }, { cwd }, ); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.", stopReason: "ultrawork_executing", systemMessage: "OMX ultrawork is still active (phase: executing).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output while UltraQA is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultraqa-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-ultraqa"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-ultraqa", "ultraqa-state.json"), { active: true, current_phase: "diagnose", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-ultraqa" }, { cwd }, ); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX ultraqa is still active (phase: diagnose); continue the task and gather fresh verification evidence before stopping.", stopReason: "ultraqa_diagnose", systemMessage: "OMX ultraqa is still active (phase: diagnose).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("marks leader-owned team attention during native Stop dispatch without a polling watcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-attention-")); try { await initTeamState( "stop-attention-team", "native stop attention", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-attention" }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-attention", }, { cwd }, ); const attention = await readTeamLeaderAttention("stop-attention-team", cwd); assert.equal(result.omxEventName, "stop"); assert.equal(attention?.source, "native_stop"); assert.equal(attention?.leader_session_active, false); assert.equal(attention?.leader_session_id, "sess-stop-team-attention"); assert.match(attention?.leader_session_stopped_at ?? "", /^\d{4}-\d{2}-\d{2}T/); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (stop-attention-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output while team phase is non-terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "team-exec", team_name: "review-team", session_id: "sess-stop-team", }); await writeJson(join(stateDir, "team", "review-team", "phase.json"), { current_phase: "team-verify", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (review-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-verify", systemMessage: "OMX team pipeline is still active at phase team-verify.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop for a team worker with a non-terminal assigned task via native worker context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevLeaderCwd = process.env.OMX_TEAM_LEADER_CWD; try { await initTeamState( "worker-stop-team", "worker stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker" }, ); const workerCwd = join(cwd, ".omx", "team", "worker-stop-team", "worktrees", "worker-1"); const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team", "workers", "worker-1"); await mkdir(workerCwd, { recursive: true }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", index: 1, role: "executor", assigned_tasks: ["1"], worktree_path: workerCwd, team_state_root: join(cwd, ".omx", "state"), }); await writeJson(join(workerDir, "status.json"), { state: "working", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team", "tasks", "task-1.json"), { id: "1", subject: "hook task", description: "finish hook task", status: "in_progress", owner: "worker-1", created_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-stop-team/worker-1"; process.env.OMX_TEAM_STATE_ROOT = join(cwd, ".omx", "state"); process.env.OMX_TEAM_LEADER_CWD = cwd; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd: workerCwd, session_id: "sess-stop-team-worker", }, { cwd: workerCwd }, ); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (in_progress); continue the current assigned task or report a concrete blocker before stopping.", stopReason: "team_worker_worker-1_1_in_progress", systemMessage: "OMX team worker worker-1 is still assigned task 1 (in_progress).", }); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevLeaderCwd === "string") process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd; else delete process.env.OMX_TEAM_LEADER_CWD; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop as a team-worker task failure when worker status is terminal but task evidence is not completed", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-stale-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevLeaderCwd = process.env.OMX_TEAM_LEADER_CWD; try { await initTeamState( "worker-stale-team", "worker stale stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-stale" }, ); const stateDir = join(cwd, ".omx", "state"); const workerCwd = join(cwd, ".omx", "team", "worker-stale-team", "worktrees", "worker-1"); const workerDir = join(stateDir, "team", "worker-stale-team", "workers", "worker-1"); await mkdir(workerCwd, { recursive: true }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", index: 1, role: "executor", assigned_tasks: ["1"], worktree_path: workerCwd, team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team", "worker-stale-team", "tasks", "task-1.json"), { id: "1", subject: "stale hook task", description: "non-completed task should still block terminal worker Stop", status: "in_progress", owner: "worker-1", created_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-stale-team/worker-1"; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.OMX_TEAM_LEADER_CWD = cwd; const payload = { hook_event_name: "Stop", cwd: workerCwd, session_id: "sess-stop-team-worker-stale", thread_id: "thread-stop-team-worker-stale", }; const result = await dispatchCodexNativeHook(payload, { cwd: workerCwd }); const replay = await dispatchCodexNativeHook( { ...payload, stop_hook_active: true }, { cwd: workerCwd }, ); assert.equal( (result.outputJson as { stopReason?: string } | null)?.stopReason, "team_worker_worker-1_1_in_progress", ); assert.equal(replay.outputJson, null); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevLeaderCwd === "string") process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd; else delete process.env.OMX_TEAM_LEADER_CWD; await rm(cwd, { recursive: true, force: true }); } }); it("re-blocks live team worker Stop replays but suppresses stale terminal worker repeats", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-repeat-")); try { await initTeamState( "worker-repeat-team", "worker stop repeat guard", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-repeat" }, ); const stateDir = join(cwd, ".omx", "state"); const workerDir = join(stateDir, "team", "worker-repeat-team", "workers", "worker-1"); const taskPath = join(stateDir, "team", "worker-repeat-team", "tasks", "task-1.json"); const workerCwd = join(cwd, ".omx", "team", "worker-repeat-team", "worktrees", "worker-1"); await mkdir(workerCwd, { recursive: true }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", index: 1, role: "executor", assigned_tasks: ["1"], worktree_path: workerCwd, team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "working", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(taskPath, { id: "1", subject: "hook task", description: "finish hook task", status: "in_progress", owner: "worker-1", created_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-repeat-team/worker-1"; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.OMX_TEAM_LEADER_CWD = cwd; const basePayload = { hook_event_name: "Stop", cwd: workerCwd, session_id: "sess-stop-team-worker-repeat", thread_id: "thread-stop-team-worker-repeat", turn_id: "turn-stop-team-worker-repeat-1", last_assistant_message: "I need to stop before this task is done.", }; const expectedInProgress = { decision: "block", reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (in_progress); continue the current assigned task or report a concrete blocker before stopping.", stopReason: "team_worker_worker-1_1_in_progress", systemMessage: "OMX team worker worker-1 is still assigned task 1 (in_progress).", }; const first = await dispatchCodexNativeHook(basePayload, { cwd: workerCwd }); const replay = await dispatchCodexNativeHook( { ...basePayload, stop_hook_active: true }, { cwd: workerCwd }, ); const freshTurn = await dispatchCodexNativeHook( { ...basePayload, turn_id: "turn-stop-team-worker-repeat-2", stop_hook_active: true }, { cwd: workerCwd }, ); await writeJson(taskPath, { id: "1", subject: "hook task", description: "finish hook task", status: "blocked", owner: "worker-1", created_at: new Date().toISOString(), }); const stateChanged = await dispatchCodexNativeHook( { ...basePayload, turn_id: "turn-stop-team-worker-repeat-3", stop_hook_active: true }, { cwd: workerCwd }, ); assert.deepEqual(first.outputJson, expectedInProgress); assert.deepEqual(replay.outputJson, expectedInProgress); assert.deepEqual(freshTurn.outputJson, expectedInProgress); assert.deepEqual(stateChanged.outputJson, { decision: "block", reason: "OMX team worker worker-1 is still assigned non-terminal task 1 (blocked); continue the current assigned task or report a concrete blocker before stopping.", stopReason: "team_worker_worker-1_1_blocked", systemMessage: "OMX team worker worker-1 is still assigned task 1 (blocked).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Stop for a team worker when assigned task is terminal and bypasses generic team blocking", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { await initTeamState( "worker-stop-team-terminal", "worker stop terminal fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-terminal" }, ); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); const workerDir = join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "workers", "worker-1"); await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "config.json"), { name: "worker-stop-team-terminal", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "manifest.v2.json"), { name: "worker-stop-team-terminal", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", index: 1, role: "executor", assigned_tasks: ["1"], worktree_path: cwd, team_state_root: join(cwd, ".omx", "state"), }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(cwd, ".omx", "state", "team", "worker-stop-team-terminal", "tasks", "task-1.json"), { id: "1", subject: "hook task", description: "finish hook task", status: "completed", owner: "worker-1", created_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-stop-team-terminal/worker-1"; process.env.OMX_TEAM_STATE_ROOT = join(cwd, ".omx", "state"); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-terminal", }, { cwd }, ); const replay = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-terminal", turn_id: "turn-worker-stop-terminal-replay", }, { cwd }, ); assert.equal(result.outputJson, null); assert.equal(replay.outputJson, null); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); const stopNudges = tmuxLog.match(/send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/g) || []; assert.equal(stopNudges.length, 1, "allowed worker Stop should nudge leader exactly once inside cooldown"); const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8")); assert.equal(nudgeState.delivery, "sent"); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("steers worker Stop leader nudge directly when leader pane is busy", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-busy-leader-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { await initTeamState( "worker-stop-team-busy-leader", "worker stop busy leader", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-busy-leader" }, ); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { busyLeader: true })); await chmod(join(fakeBinDir, "tmux"), 0o755); const stateDir = join(cwd, ".omx", "state"); const teamDir = join(stateDir, "team", "worker-stop-team-busy-leader"); const workerDir = join(teamDir, "workers", "worker-1"); await writeJson(join(teamDir, "config.json"), { name: "worker-stop-team-busy-leader", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(teamDir, "manifest.v2.json"), { name: "worker-stop-team-busy-leader", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", index: 1, role: "executor", assigned_tasks: ["1"], worktree_path: cwd, team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(teamDir, "tasks", "task-1.json"), { id: "1", subject: "hook task", description: "finish hook task", status: "completed", owner: "worker-1", created_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-stop-team-busy-leader/worker-1"; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-busy-leader", }, { cwd }, ); assert.equal(result.outputJson, null); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/); assert.doesNotMatch(tmuxLog, /send-keys -t %42 Tab/); const submits = tmuxLog.match(/send-keys -t %42 C-m/g) || []; assert.equal(submits.length, 2, "busy worker-stop nudge should submit directly as steering, not queue via Tab"); const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8")); assert.equal(nudgeState.delivery, "steered"); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("dedupes allowed worker Stop leader nudges across workers in the same team window", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-team-dedupe-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "worker-stop-team-dedupe"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [ { name: "worker-1", index: 1, pane_id: "%10" }, { name: "worker-2", index: 2, pane_id: "%11" }, ], }); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const first = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-1" }, }); const second = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-2" }, }); assert.equal(first.result, "sent"); assert.equal(second.result, "suppressed_team_cooldown"); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); const stopNudges = tmuxLog.match(/send-keys -t %42 -l \[OMX\] worker-\d+ native Stop allowed/g) || []; assert.equal(stopNudges.length, 1, "same-team workers should share one leader nudge cooldown window"); const teamNudgeState = JSON.parse(await readFile(join(teamDir, "worker-stop-nudge.json"), "utf-8")); assert.equal(teamNudgeState.worker, "worker-1"); assert.equal(teamNudgeState.delivery, "sent"); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("serializes concurrent allowed worker Stop leader nudges with a team lock", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-concurrent-dedupe-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "worker-stop-concurrent"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { sendDelayMs: 100 })); await chmod(join(fakeBinDir, "tmux"), 0o755); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [ { name: "worker-1", index: 1, pane_id: "%10" }, { name: "worker-2", index: 2, pane_id: "%11" }, ], }); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const results = await Promise.all([ maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-1" }, }), maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-2" }, }), ]); assert.equal(results.filter((result) => result.result === "sent").length, 1); assert.equal(results.filter((result) => result.result === "suppressed_team_lock_held").length, 1); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); const stopNudges = tmuxLog.match(/send-keys -t %42 -l \[OMX\] worker-\d+ native Stop allowed/g) || []; assert.equal(stopNudges.length, 1, "concurrent same-team workers should emit only one leader nudge"); assert.equal(existsSync(join(teamDir, "worker-stop-nudge.lock")), false); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("skips worker Stop leader nudge when team state is missing or shut down", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-missing-team-")); try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const result = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName: "removed-team", workerName: "worker-1" }, }); assert.equal(result.result, "team_state_gone_or_shutdown"); assert.equal(existsSync(join(stateDir, "team", "removed-team", "worker-stop-nudge.json")), false); await writeJson(join(stateDir, "team", "shutdown-team", "shutdown.json"), { started_at: new Date().toISOString(), }); const shutdownResult = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName: "shutdown-team", workerName: "worker-1" }, }); assert.equal(shutdownResult.result, "team_state_gone_or_shutdown"); assert.equal(existsSync(join(stateDir, "team", "shutdown-team", "worker-stop-nudge.json")), false); const deliveryLogPath = join(logsDir, `team-delivery-${new Date().toISOString().split("T")[0]}.jsonl`); const deliveryEvents = (await readFile(deliveryLogPath, "utf-8")) .trim() .split("\n") .map((line) => JSON.parse(line)); const suppressedEvents = deliveryEvents.filter((event) => event.reason === "team_state_gone_or_shutdown"); assert.equal(suppressedEvents.length, 2, "late closed-team Stop nudges should be diagnostics, not queued prompts"); assert.equal(suppressedEvents.every((event) => event.result === "suppressed" && event.transport === "none"), true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not treat old visible worker Stop transcript as pending queue state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-queue-dedupe-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "queued-stop-dedupe"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile( join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { busyLeader: true, captureText: `[OMX] worker-1 native Stop allowed. Run \`omx team status ${teamName}\`, read worker messages/results, then assign next task, reconcile completion, or shut down. [OMX_TMUX_INJECT]\n` + "• Working… (esc to interrupt)", }), ); await chmod(join(fakeBinDir, "tmux"), 0o755); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-2", index: 2, pane_id: "%11" }], }); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-2" }, }); assert.equal(result.result, "steered"); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-2 native Stop allowed/); assert.doesNotMatch(tmuxLog, /send-keys -t %42 Tab/); const teamNudgeState = JSON.parse(await readFile(join(teamDir, "worker-stop-nudge.json"), "utf-8")); assert.equal(teamNudgeState.worker, "worker-2"); assert.equal(teamNudgeState.delivery, "steered"); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("reports deferred when non-teardown persistence failure prevents worker Stop nudge cooldown state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-persist-fail-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "worker-stop-persist-fail"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeFile(join(teamDir, "workers"), "not a directory"); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-1" }, }); assert.equal(result.result, "deferred"); assert.equal(existsSync(join(teamDir, "worker-stop-nudge.json")), false); assert.equal(existsSync(join(teamDir, "workers", "worker-1", "worker-stop-nudge.json")), false); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/); const deliveryLogPath = join(logsDir, `team-delivery-${new Date().toISOString().split("T")[0]}.jsonl`); const deliveryEvents = (await readFile(deliveryLogPath, "utf-8")) .trim() .split("\n") .map((line) => JSON.parse(line)); const deferredEvent = deliveryEvents.find((event) => event.event === "nudge_triggered" && event.result === "deferred"); assert.equal(deferredEvent?.team, teamName); assert.equal(deferredEvent?.from_worker, "worker-1"); assert.match(String(deferredEvent?.reason || ""), /EEXIST|ENOTDIR|not a directory|file already exists/); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("does not recreate team state when teardown removes it during worker Stop delivery", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-teardown-race-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "worker-stop-teardown-race"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { removePathOnSend: teamDir })); await chmod(join(fakeBinDir, "tmux"), 0o755); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-1" }, }); assert.equal(result.result, "sent"); assert.equal(existsSync(teamDir), false, "worker Stop delivery must not recreate removed team state"); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("does not recreate team state when teardown removes it before deferred worker Stop recording", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-deferred-teardown-")); const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const logsDir = join(cwd, ".omx", "logs"); const teamName = "worker-stop-deferred-teardown"; const teamDir = join(stateDir, "team", teamName); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeJson(join(teamDir, "manifest.v2.json"), { name: teamName, tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeFile( join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { currentCommand: "bash", captureText: "$ ", removePathOnCapture: teamDir, }), ); await chmod(join(fakeBinDir, "tmux"), 0o755); process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await maybeNudgeLeaderForAllowedWorkerStop({ stateDir, logsDir, workerContext: { teamName, workerName: "worker-1" }, }); assert.equal(result.result, "team_state_gone_or_shutdown"); assert.equal(existsSync(teamDir), false, "deferred worker Stop recording must not recreate removed team state"); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/); const deliveryLogPath = join(logsDir, `team-delivery-${new Date().toISOString().split("T")[0]}.jsonl`); const deliveryEvents = (await readFile(deliveryLogPath, "utf-8")) .trim() .split("\n") .map((line) => JSON.parse(line)); assert.equal( deliveryEvents.some((event) => event.team === teamName && event.result === "suppressed" && event.transport === "none" && event.reason === "team_state_gone_or_shutdown" ), true, "teardown-race worker Stop nudges should be diagnostic suppression events, not queued prompts", ); } finally { if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("allows worker Stop when the Stop nudge helper cannot deliver", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-helper-fail-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { await initTeamState( "worker-stop-helper-fail", "worker stop helper failure", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-helper-fail" }, ); const fakeBinDir = join(cwd, "fake-bin"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(join(cwd, "tmux.log"), { failSend: true })); await chmod(join(fakeBinDir, "tmux"), 0o755); const stateDir = join(cwd, ".omx", "state"); const workerDir = join(stateDir, "team", "worker-stop-helper-fail", "workers", "worker-1"); await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "config.json"), { name: "worker-stop-helper-fail", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "manifest.v2.json"), { name: "worker-stop-helper-fail", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", assigned_tasks: ["1"], team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team", "worker-stop-helper-fail", "tasks", "task-1.json"), { id: "1", status: "completed", owner: "worker-1", }); process.env.OMX_TEAM_WORKER = "worker-stop-helper-fail/worker-1"; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-helper-fail" }, { cwd }, ); assert.equal(result.outputJson, null); const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8")); assert.equal(nudgeState.delivery, "deferred"); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("does not treat failed or ambiguous worker task state as completed Stop evidence", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-failed-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { await initTeamState( "worker-stop-failed-task", "worker stop failed task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-failed" }, ); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); const stateDir = join(cwd, ".omx", "state"); const workerDir = join(stateDir, "team", "worker-stop-failed-task", "workers", "worker-1"); await writeJson(join(stateDir, "team", "worker-stop-failed-task", "config.json"), { name: "worker-stop-failed-task", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", assigned_tasks: ["1"], team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "failed", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team", "worker-stop-failed-task", "tasks", "task-1.json"), { id: "1", status: "failed", owner: "worker-1", }); process.env.OMX_TEAM_WORKER = "worker-stop-failed-task/worker-1"; delete process.env.OMX_TEAM_INTERNAL_WORKER; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-failed", thread_id: "thread-stop-team-worker-failed", turn_id: "turn-stop-team-worker-failed", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.stopReason || ""), /non_completed_task_1_failed/); assert.match(JSON.stringify(result.outputJson), /team/i); assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false); const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : ""; assert.doesNotMatch(tmuxLog, /native Stop allowed/); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevInternalTeamWorker === "string") process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker; else delete process.env.OMX_TEAM_INTERNAL_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("blocks worker Stop on missing task assignment without relying on generic team state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-missing-assignment-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { const stateDir = join(cwd, ".omx", "state"); const workerDir = join(stateDir, "team", "worker-missing-assignment", "workers", "worker-1"); await mkdir(workerDir, { recursive: true }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", assigned_tasks: [], team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "idle", updated_at: new Date().toISOString(), }); process.env.OMX_TEAM_WORKER = "worker-missing-assignment/worker-1"; delete process.env.OMX_TEAM_INTERNAL_WORKER; process.env.OMX_TEAM_STATE_ROOT = stateDir; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-missing-assignment", thread_id: "thread-stop-team-worker-missing-assignment", turn_id: "turn-stop-team-worker-missing-assignment", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_missing_task_assignment"); assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevInternalTeamWorker === "string") process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker; else delete process.env.OMX_TEAM_INTERNAL_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); } }); it("blocks unresolved worker Stop before generic auto-nudge can bypass it", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-missing-state-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); process.env.OMX_TEAM_WORKER = "worker-missing-state/worker-1"; delete process.env.OMX_TEAM_INTERNAL_WORKER; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-missing-state", thread_id: "thread-stop-team-worker-missing-state", turn_id: "turn-stop-team-worker-missing-state", last_assistant_message: "Should I proceed?", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_missing_worker_state"); assert.doesNotMatch(JSON.stringify(result.outputJson), /auto_nudge/); const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : ""; assert.doesNotMatch(tmuxLog, /native Stop allowed/); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevInternalTeamWorker === "string") process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker; else delete process.env.OMX_TEAM_INTERNAL_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("prefers canonical internal worker identity over public worker identity for Stop nudges", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-internal-env-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); const workerDir = join(stateDir, "team", "internal-stop-team", "workers", "worker-1"); await writeJson(join(stateDir, "team", "internal-stop-team", "config.json"), { name: "internal-stop-team", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", assigned_tasks: ["1"], team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team", "internal-stop-team", "tasks", "task-1.json"), { id: "1", status: "completed", owner: "worker-1", }); process.env.OMX_TEAM_WORKER = "public-stop-team/worker-1"; process.env.OMX_TEAM_INTERNAL_WORKER = "internal-stop-team/worker-1"; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-internal-env", thread_id: "thread-stop-team-worker-internal-env", turn_id: "turn-stop-team-worker-internal-env", }, { cwd }, ); assert.equal(result.outputJson, null); const tmuxLog = await readFile(tmuxLogPath, "utf-8"); assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/); assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), true); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevInternalTeamWorker === "string") process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker; else delete process.env.OMX_TEAM_INTERNAL_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("blocks worker Stop when canonical task ownership has a newer non-terminal task", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-owned-task-")); const prevTeamWorker = process.env.OMX_TEAM_WORKER; const prevInternalTeamWorker = process.env.OMX_TEAM_INTERNAL_WORKER; const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; const prevPath = process.env.PATH; try { const stateDir = join(cwd, ".omx", "state"); const fakeBinDir = join(cwd, "fake-bin"); const tmuxLogPath = join(cwd, "tmux.log"); await mkdir(fakeBinDir, { recursive: true }); await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath)); await chmod(join(fakeBinDir, "tmux"), 0o755); const workerDir = join(stateDir, "team", "worker-owned-task", "workers", "worker-1"); await writeJson(join(stateDir, "team", "worker-owned-task", "config.json"), { name: "worker-owned-task", tmux_session: "omx-team-worker-stop", leader_pane_id: "%42", workers: [{ name: "worker-1", index: 1, pane_id: "%10" }], }); await writeJson(join(workerDir, "identity.json"), { name: "worker-1", assigned_tasks: ["1"], team_state_root: stateDir, }); await writeJson(join(workerDir, "status.json"), { state: "done", current_task_id: "1", updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team", "worker-owned-task", "tasks", "task-1.json"), { id: "1", status: "completed", owner: "worker-1", }); await writeJson(join(stateDir, "team", "worker-owned-task", "tasks", "task-2.json"), { id: "2", status: "in_progress", owner: "worker-1", }); process.env.OMX_TEAM_WORKER = "worker-owned-task/worker-1"; delete process.env.OMX_TEAM_INTERNAL_WORKER; process.env.OMX_TEAM_STATE_ROOT = stateDir; process.env.PATH = `${fakeBinDir}:${prevPath || ""}`; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-worker-owned-task", thread_id: "thread-stop-team-worker-owned-task", turn_id: "turn-stop-team-worker-owned-task", }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "team_worker_worker-1_2_in_progress"); assert.equal(existsSync(join(workerDir, "worker-stop-nudge.json")), false); const tmuxLog = existsSync(tmuxLogPath) ? await readFile(tmuxLogPath, "utf-8") : ""; assert.doesNotMatch(tmuxLog, /native Stop allowed/); } finally { if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker; else delete process.env.OMX_TEAM_WORKER; if (typeof prevInternalTeamWorker === "string") process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker; else delete process.env.OMX_TEAM_INTERNAL_WORKER; if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; if (typeof prevPath === "string") process.env.PATH = prevPath; else delete process.env.PATH; await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-")); try { await initTeamState( "canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-canonical", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from canonical team state owned by another thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-")); try { await initTeamState( "canonical-other-thread-team", "canonical other-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" }, ); const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record; await writeJson(manifestPath, { ...manifest, leader: { ...(manifest.leader as Record | undefined), thread_id: "thread-other", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-canonical-thread", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop from canonical team state owned by the current thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-")); try { await initTeamState( "canonical-current-thread-team", "canonical current-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" }, ); const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record; await writeJson(manifestPath, { ...manifest, leader: { ...(manifest.leader as Record | undefined), thread_id: "thread-current", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-canonical-current-thread", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (canonical-current-thread-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-")); try { await initTeamState( "release-ready-team", "release readiness finalize", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" }, ); await writeReleaseReadinessLeaderAttention( "release-ready-team", "sess-stop-release-ready", cwd, { workRemaining: false }, ); await writeReleaseReadinessStateMarker( "sess-stop-release-ready", "release-ready-team", cwd, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-release-ready", thread_id: "thread-stop-release-ready", turn_id: "turn-stop-release-ready-1", mode: "release-readiness", last_assistant_message: "Launch-ready: yes", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: 'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.', stopReason: "release_readiness_auto_finalize", systemMessage: "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not auto-finalize non-release team stops that happen to contain a stable recommendation summary", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-non-release-readiness-control-")); try { await initTeamState( "general-review-team", "general team stop control", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-general-review" }, ); await writeReleaseReadinessLeaderAttention( "general-review-team", "sess-stop-general-review", cwd, { workRemaining: false }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-general-review", thread_id: "thread-stop-general-review", turn_id: "turn-stop-general-review-1", last_assistant_message: "Launch-ready: yes", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (general-review-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("honors terminal team run-state before later canonical-team Stop fallback", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-terminal-run-state-canonical-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-team-terminal-run-state"; await initTeamState( "terminal-run-state-team", "terminal team stop canonical fallback regression", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: sessionId }, ); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, mode: "team", active: false, outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-04-27T12:00:00.000Z", updated_at: "2026-04-27T12:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-team-terminal-run-state", turn_id: "turn-stop-team-terminal-run-state-1", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-fires canonical-team Stop output for a later fresh Stop reply when coarse mode state is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-refire-")); try { await initTeamState( "canonical-team-refire", "canonical stop fallback refire", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-refire" }, ); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-canonical-refire", thread_id: "thread-stop-team-canonical-refire", turn_id: "turn-stop-team-canonical-refire-1", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-canonical-refire", thread_id: "thread-stop-team-canonical-refire", turn_id: "turn-stop-team-canonical-refire-2", stop_hook_active: true, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (canonical-team-refire) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from canonical team state alone when the canonical phase is terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-terminal-")); try { await initTeamState( "terminal-team", "terminal stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-terminal" }, ); await writeJson(join(cwd, ".omx", "state", "team", "terminal-team", "phase.json"), { current_phase: "complete", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-terminal", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output from canonical team state when manifest session ownership is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-legacy-")); try { await initTeamState( "legacy-team", "legacy stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-legacy" }, ); const manifestPath = join(cwd, ".omx", "state", "team", "legacy-team", "manifest.v2.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record; await writeJson(manifestPath, { ...manifest, leader: { ...(manifest.leader as Record | undefined), session_id: "", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-legacy", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (legacy-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("reads canonical Stop fallback team state from OMX_TEAM_STATE_ROOT when configured", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-root-")); const sharedRoot = join(cwd, "shared-root"); const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_TEAM_STATE_ROOT = sharedRoot; await initTeamState( "canonical-root-team", "canonical stop root fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-root", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (canonical-root-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); assert.equal(existsSync(join(sharedRoot, "team", "canonical-root-team", "phase.json")), true); } finally { if (typeof priorTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = priorTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); } }); it("ignores stale source-root team Stop fallback when OMX_TEAM_STATE_ROOT is authoritative", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-stale-source-root-")); const teamStateRoot = join(cwd, "shared-team-state"); const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_TEAM_STATE_ROOT = teamStateRoot; await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await mkdir(join(teamStateRoot, "team", "stale-source-team"), { recursive: true }); await writeJson(join(cwd, ".omx", "state", "team-state.json"), { active: true, team_name: "stale-source-team", current_phase: "team-exec", }); await writeJson(join(teamStateRoot, "team", "stale-source-team", "phase.json"), { current_phase: "team-exec", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stale-source-team", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { if (typeof priorTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = priorTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-")); const teamStateRoot = join(cwd, "shared-team-state"); const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_TEAM_STATE_ROOT = teamStateRoot; await initTeamState( "env-root-team", "env root stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-env-root", OMX_TEAM_STATE_ROOT: teamStateRoot, }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-env-root", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (env-root-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop from session-scoped team mode when session.json points to another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-session-mismatch-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-live-team"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-team" }); await writeJson(join(stateDir, "sessions", "sess-live-team", "team-state.json"), { active: true, mode: "team", current_phase: "team-exec", team_name: "session-live-team", }); await writeJson(join(stateDir, "team", "session-live-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-live-team", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (session-live-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output for active ralplan skill with matching active mode state and without active subagents", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-skill"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill" }); await writeJson(join(stateDir, "sessions", "sess-stop-skill", "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", }); await writeJson(join(stateDir, "sessions", "sess-stop-skill", "ralplan-state.json"), { active: true, current_phase: "planning", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-skill", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Status: continue_from_artifact/); assert.match(String(result.outputJson?.reason ?? ""), /ralplan is still active \(phase: planning\)/); assert.match(String(result.outputJson?.reason ?? ""), /continue from the current ralplan artifact/i); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); assert.match(String(result.outputJson?.systemMessage ?? ""), /complete, paused for review, waiting for input, or still continuing/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop after owner-session ralplan CLI completion writes to native canonical state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-owner-alias-complete-")); const previousSessionId = process.env.OMX_SESSION_ID; try { const stateDir = join(cwd, ".omx", "state"); const nativeSessionId = "native-id"; const ownerSessionId = "omx-owner-id"; await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: nativeSessionId, native_session_id: nativeSessionId, owner_omx_session_id: ownerSessionId, cwd, }); await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: nativeSessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: nativeSessionId }], }); await writeJson(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: nativeSessionId, }); const now = "2026-06-30T00:00:00.000Z"; await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [nativeSessionId]: { session_id: nativeSessionId, leader_thread_id: "thread-leader", updated_at: now, threads: { "thread-leader": { thread_id: "thread-leader", kind: "leader", first_seen_at: now, last_seen_at: now, turn_count: 1 }, "thread-architect": { thread_id: "thread-architect", kind: "subagent", first_seen_at: now, last_seen_at: now, completed_at: now, turn_count: 1 }, "thread-critic": { thread_id: "thread-critic", kind: "subagent", first_seen_at: now, last_seen_at: now, completed_at: now, turn_count: 1 }, }, }, }, }); const consensusGate = { required: true, complete: true, sequence: ["architect-review", "critic-review"], planning_artifacts_are_not_consensus: true, required_review_roles: ["architect", "critic"], ralplan_architect_review: { agent_role: "architect", verdict: "approve", provenance_kind: "native_subagent", session_id: nativeSessionId, thread_id: "thread-architect", artifact_path: ".omx/artifacts/architect.md", tracker_path: ".omx/state/subagent-tracking.json", }, ralplan_critic_review: { agent_role: "critic", verdict: "approve", provenance_kind: "native_subagent", session_id: nativeSessionId, thread_id: "thread-critic", artifact_path: ".omx/artifacts/critic.md", tracker_path: ".omx/state/subagent-tracking.json", }, }; process.env.OMX_SESSION_ID = ownerSessionId; const writeResult = await executeStateOperation("state_write", { mode: "ralplan", active: false, current_phase: "complete", status: "complete", terminal_state: "complete", ralplan_consensus_gate: consensusGate, workingDirectory: cwd, }); assert.notEqual(writeResult.isError, true); assert.equal(existsSync(join(stateDir, "sessions", ownerSessionId, "ralplan-state.json")), false); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: nativeSessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { if (typeof previousSessionId === "string") process.env.OMX_SESSION_ID = previousSessionId; else delete process.env.OMX_SESSION_ID; await rm(cwd, { recursive: true, force: true }); } }); it("does not block on stale ralplan skill-active state when the matching mode state is absent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-skill-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-stale-skill"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-stale-skill" }); await writeJson(join(stateDir, "sessions", "sess-stop-stale-skill", "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: "sess-stop-stale-skill", active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: "sess-stop-stale-skill", }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-stale-skill", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block when canonical root ralplan state is inactive but session ralplan state is stale active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralplan-root-inactive-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-stale-session-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "reviewing", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", session_id: sessionId, cwd, }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("clears stale ralplan Stop cache when authoritative state is terminal inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-terminal-cache-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-ralplan-terminal-cache"; const threadId = "thread-stop-ralplan-terminal-cache"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", session_id: sessionId, active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", status: "complete", run_outcome: "complete", session_id: sessionId, cwd, }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", session_id: sessionId, active_skills: [], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", status: "complete", run_outcome: "complete", session_id: sessionId, cwd, }); await writeJson(join(stateDir, "native-stop-state.json"), { sessions: { [sessionId]: { last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`, updated_at: "2026-07-04T00:00:00.000Z", }, [threadId]: { last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`, updated_at: "2026-07-04T00:00:00.000Z", }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: threadId, stop_hook_active: true, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8")) as { sessions?: Record }; assert.deepEqual(stopState.sessions, {}); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-ralplan-canonical-root-no-cwd"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", session_id: sessionId, active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, cwd, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-current-active-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", session_id: "sess-stop-old-ralplan", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", session_id: "sess-stop-old-ralplan", }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when root inactive ralplan state is unscoped", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-unscoped-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-unscoped-root-current-active"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when unscoped root completion lacks a plan artifact", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-no-plan-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-unscoped-root-no-plan-artifact"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", planning_complete: true, updated_at: "2026-06-21T08:05:00.000Z", }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, updated_at: "2026-06-21T08:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not loop when newer unscoped root ralplan complete state shadows stale session planning", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralplan-newer-root-complete-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-stale-session-ralplan-newer-root"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, updated_at: "2026-06-21T08:00:00.000Z", }); await mkdir(join(cwd, ".omx", "plans"), { recursive: true }); await writeFile(join(cwd, ".omx", "plans", "prd-issue-2923.md"), "# PRD\n"); await writeFile(join(cwd, ".omx", "plans", "test-spec-issue-2923.md"), "# Test spec\n"); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", cwd, planning_complete: true, latest_plan_path: ".omx/plans/prd-issue-2923.md", updated_at: "2026-06-21T08:05:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, tool_name: "apply_patch", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when newer unscoped root completion belongs to another worktree", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-cross-worktree-root-complete-")); try { const otherWorktree = join(tmpdir(), "omx-native-hook-other-worktree-2925"); const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-ralplan-cross-worktree-root"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, cwd, updated_at: "2026-06-21T08:00:00.000Z", }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", cwd: otherWorktree, planning_complete: true, latest_plan_path: ".omx/plans/prd-issue-2925.md", updated_at: "2026-06-21T08:05:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, tool_name: "apply_patch", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking current session ralplan when newer unscoped root completion lacks project context", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-root-complete-no-cwd-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-ralplan-root-complete-no-cwd"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, cwd, updated_at: "2026-06-21T08:00:00.000Z", }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralplan", phase: "complete", active_skills: [], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", planning_complete: true, latest_plan_path: ".omx/plans/prd-issue-2925.md", updated_at: "2026-06-21T08:05:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, tool_name: "apply_patch", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block stale session ralplan when root ralplan is terminal and another root skill is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-ralplan-other-root-skill-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-stale-ralplan-other-root-skill"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "intent-first", session_id: sessionId, active_skills: [{ skill: "deep-interview", phase: "intent-first", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "complete", session_id: sessionId, cwd, }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking session ralplan when canonical root state is not inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-active-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-session-ralplan-root-active"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block on stale ralplan skill-active when canonical run-state is terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-terminal-ralplan-run-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-terminal-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, mode: "ralplan", active: false, outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-01T00:00:00.000Z", updated_at: "2026-05-01T00:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block on stale ralplan skill-active when pinned mode state belongs to another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-ralplan-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-current-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId, }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: "sess-other-ralplan", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns an explicit ralplan waiting status while subagents are still active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-skill-subagent"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-subagent" }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "ralplan-state.json"), { active: true, current_phase: "planning", }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { "sess-stop-skill-subagent": { session_id: "sess-stop-skill-subagent", leader_thread_id: "leader-1", updated_at: new Date().toISOString(), threads: { "leader-1": { thread_id: "leader-1", kind: "leader", first_seen_at: new Date().toISOString(), last_seen_at: new Date().toISOString(), turn_count: 1, }, "sub-1": { thread_id: "sub-1", kind: "subagent", first_seen_at: new Date().toISOString(), last_seen_at: new Date().toISOString(), turn_count: 1, }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-skill-subagent", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Status: waiting/); assert.match(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/); assert.match(String(result.outputJson?.reason ?? ""), /then continue from the current ralplan artifact/i); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_waiting_subagent"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not report ralplan subagent waiting when notify-fallback already recorded completion", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-complete-")); try { const stateDir = join(cwd, ".omx", "state"); const now = new Date().toISOString(); await mkdir(join(stateDir, "sessions", "sess-stop-skill-subagent-complete"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-subagent-complete" }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent-complete", "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent-complete", "ralplan-state.json"), { active: true, current_phase: "planning", }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { "sess-stop-skill-subagent-complete": { session_id: "sess-stop-skill-subagent-complete", leader_thread_id: "leader-1", updated_at: now, threads: { "leader-1": { thread_id: "leader-1", kind: "leader", first_seen_at: now, last_seen_at: now, turn_count: 1, }, "sub-1": { thread_id: "sub-1", kind: "subagent", first_seen_at: now, last_seen_at: now, completed_at: now, last_completed_turn_id: "turn-complete-1", completion_source: "notify-fallback-watcher", turn_count: 2, }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-skill-subagent-complete", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson?.decision, "block"); assert.doesNotMatch(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/); assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-stale-root-skill", thread_id: "thread-stop-stale-root-skill", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop while autoresearch is active without validator completion", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autoresearch-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-autoresearch"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-autoresearch", cwd }); await writeJson(join(stateDir, "sessions", "sess-stop-autoresearch", "autoresearch-state.json"), { active: true, mode: "autoresearch", current_phase: "executing", session_id: "sess-stop-autoresearch", validation_mode: "mission-validator-script", mission_validator_command: "node scripts/validate.js", completion_artifact_path: '.omx/specs/autoresearch-demo/completion.json', }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-autoresearch", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX autoresearch is still active (phase: executing); continue until validator evidence is complete before stopping.", stopReason: "autoresearch_executing", systemMessage: "OMX autoresearch is still active (phase: executing); continue until validator evidence is complete before stopping.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Stop once autoresearch validator evidence is complete", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autoresearch-complete-")); try { const stateDir = join(cwd, ".omx", "state"); const specDir = join(cwd, '.omx', 'specs', 'autoresearch-demo'); await mkdir(join(stateDir, "sessions", "sess-stop-autoresearch-complete"), { recursive: true }); await mkdir(specDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-autoresearch-complete", cwd }); await writeJson(join(stateDir, "sessions", "sess-stop-autoresearch-complete", "autoresearch-state.json"), { active: true, mode: "autoresearch", current_phase: "reviewing", session_id: "sess-stop-autoresearch-complete", validation_mode: "mission-validator-script", mission_validator_command: "node scripts/validate.js", completion_artifact_path: '.omx/specs/autoresearch-demo/completion.json', }); await writeJson(join(specDir, 'completion.json'), { status: 'passed', passed: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-autoresearch-complete", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from stale root autoresearch state when the explicit session has no scoped autoresearch state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autoresearch-")); try { const stateDir = join(cwd, ".omx", "state"); const specDir = join(cwd, '.omx', 'specs', 'autoresearch-demo'); await mkdir(join(stateDir, 'sessions', 'sess-current'), { recursive: true }); await mkdir(specDir, { recursive: true }); await writeJson(join(stateDir, 'session.json'), { session_id: 'sess-current', cwd }); await writeJson(join(stateDir, 'autoresearch-state.json'), { active: true, mode: 'autoresearch', current_phase: 'executing', validation_mode: 'mission-validator-script', mission_validator_command: 'node scripts/validate.js', completion_artifact_path: '.omx/specs/autoresearch-demo/completion.json', }); const result = await dispatchCodexNativeHook( { hook_event_name: 'Stop', cwd, session_id: 'sess-current', }, { cwd }, ); assert.equal(result.omxEventName, 'stop'); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from stale root autoresearch state when the explicit session directory is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-missing-session-autoresearch-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "autoresearch-state.json"), { active: true, mode: "autoresearch", current_phase: "executing", validation_mode: "mission-validator-script", mission_validator_command: "node scripts/validate.js", completion_artifact_path: ".omx/specs/autoresearch-demo/completion.json", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "missing-session", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop solely because deep-interview is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview" }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview", "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview", "deep-interview-state.json"), { active: true, current_phase: "planning", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-deep-interview", }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop when deep-interview has a pending omx question obligation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question" }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-stop-deep-interview-question", thread_id: "thread-stop-deep-interview-question", }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", session_id: "sess-stop-deep-interview-question", thread_id: "thread-stop-deep-interview-question", question_enforcement: { obligation_id: "obligation-1", source: "omx-question", status: "pending", requested_at: "2026-04-19T03:20:00.000Z", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-deep-interview-question", thread_id: "thread-stop-deep-interview-question", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.", stopReason: "deep_interview_question_required", systemMessage: "OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping; read the returned answers[] JSON before continuing.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop when a same-session deep-interview question obligation is pending even after the mode marked itself inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-inactive-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question-inactive" }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-stop-deep-interview-question-inactive", thread_id: "thread-stop-deep-interview-question-inactive", }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "deep-interview-state.json"), { active: false, mode: "deep-interview", current_phase: "intent-first", lifecycle_outcome: "askuserQuestion", run_outcome: "blocked_on_user", completed_at: "2026-04-19T03:20:30.000Z", session_id: "sess-stop-deep-interview-question-inactive", thread_id: "thread-stop-deep-interview-question-inactive", question_enforcement: { obligation_id: "obligation-inactive", source: "omx-question", status: "pending", lifecycle_outcome: "askuserQuestion", requested_at: "2026-04-19T03:20:00.000Z", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-deep-interview-question-inactive", thread_id: "thread-stop-deep-interview-question-inactive", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.", stopReason: "deep_interview_question_required", systemMessage: "OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping; read the returned answers[] JSON before continuing.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not re-block Stop after a same-session deep-interview question record is already answered", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-answered-")); try { const sessionId = "sess-stop-deep-interview-question-answered"; const stateDir = join(cwd, ".omx", "state"); const sessionDir = join(stateDir, "sessions", sessionId); await mkdir(join(sessionDir, "questions"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(sessionDir, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: sessionId, thread_id: "thread-stop-deep-interview-question-answered", }); await writeJson(join(sessionDir, "deep-interview-state.json"), { active: false, mode: "deep-interview", current_phase: "intent-first", lifecycle_outcome: "askuserQuestion", run_outcome: "blocked_on_user", completed_at: "2026-04-19T03:20:30.000Z", session_id: sessionId, thread_id: "thread-stop-deep-interview-question-answered", question_enforcement: { obligation_id: "obligation-answered", source: "omx-question", status: "pending", lifecycle_outcome: "askuserQuestion", requested_at: "2026-04-19T03:20:00.000Z", }, }); await writeJson(join(sessionDir, "questions", "question-answered.json"), { kind: "omx.question/v1", question_id: "question-answered", session_id: sessionId, created_at: "2026-04-19T03:20:05.000Z", updated_at: "2026-04-19T03:20:10.000Z", status: "answered", question: "What should happen next?", options: [{ label: "Continue", value: "continue" }], allow_other: false, other_label: "Other", multi_select: false, type: "single-answerable", source: "deep-interview", answer: { kind: "option", value: "continue", selected_labels: ["Continue"], selected_values: ["continue"], }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-deep-interview-question-answered", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); const state = JSON.parse( await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"), ) as { lifecycle_outcome?: string; question_enforcement?: { status?: string; question_id?: string; satisfied_at?: string }; run_outcome?: string; }; assert.equal(state.question_enforcement?.status, "satisfied"); assert.equal(state.question_enforcement?.question_id, "question-answered"); assert.ok(state.question_enforcement?.satisfied_at); assert.equal(state.lifecycle_outcome, undefined); assert.equal(state.run_outcome, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking pending deep-interview question Stop replays until the obligation changes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-replay-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question-replay"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question-replay" }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-replay", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-stop-deep-interview-question-replay", }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-replay", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", question_enforcement: { obligation_id: "obligation-replay", source: "omx-question", status: "pending", requested_at: "2026-04-19T03:20:00.000Z", }, }); const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-deep-interview-question-replay", }; const expected = { decision: "block", reason: "Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.", stopReason: "deep_interview_question_required", systemMessage: "OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping; read the returned answers[] JSON before continuing.", }; const first = await dispatchCodexNativeHook(payload, { cwd }); const replay = await dispatchCodexNativeHook({ ...payload, stop_hook_active: true }, { cwd }); assert.equal(first.omxEventName, "stop"); assert.deepEqual(first.outputJson, expected); assert.equal(replay.omxEventName, "stop"); assert.deepEqual(replay.outputJson, expected); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop once the deep-interview question obligation is satisfied or cleared", async () => { for (const status of ["satisfied", "cleared"] as const) { const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-stop-deep-interview-question-${status}-`)); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", `sess-stop-deep-interview-question-${status}`), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: `sess-stop-deep-interview-question-${status}` }); await writeJson(join(stateDir, "sessions", `sess-stop-deep-interview-question-${status}`, "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: `sess-stop-deep-interview-question-${status}`, }); await writeJson(join(stateDir, "sessions", `sess-stop-deep-interview-question-${status}`, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", question_enforcement: { obligation_id: `obligation-${status}`, source: "omx-question", status, requested_at: "2026-04-19T03:20:00.000Z", ...(status === "satisfied" ? { question_id: "question-1", satisfied_at: "2026-04-19T03:21:00.000Z" } : { cleared_at: "2026-04-19T03:21:00.000Z", clear_reason: "error" }), }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: `sess-stop-deep-interview-question-${status}`, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } } }); it("ignores pending deep-interview question obligations from another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-foreign-session-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-other"), { recursive: true }); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-other", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-other", }); await writeJson(join(stateDir, "sessions", "sess-other", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", question_enforcement: { obligation_id: "obligation-foreign", source: "omx-question", status: "pending", requested_at: "2026-04-19T03:20:00.000Z", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks a new same-session deep-interview question obligation even after an earlier round was satisfied", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-next-round-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question-next-round"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question-next-round" }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-next-round", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-stop-deep-interview-question-next-round", }); await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-next-round", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", question_enforcement: { obligation_id: "obligation-next-round", source: "omx-question", status: "pending", requested_at: "2026-04-19T03:22:00.000Z", question_id: "question-old-round", satisfied_at: "2026-04-19T03:21:00.000Z", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-deep-interview-question-next-round", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.", stopReason: "deep_interview_question_required", systemMessage: "OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping; read the returned answers[] JSON before continuing.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("ignores root skill-active fallback from a different thread when evaluating Stop", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-thread-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", session_id: "", thread_id: "other-thread", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-main", thread_id: "main-thread", }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns a non-blocking Stop document-refresh warning before auto-nudge when Ralph is not active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-document-refresh-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-doc-refresh", last_assistant_message: "Launch-ready: yes", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined); assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "Stop"); assert.match(JSON.stringify(result.outputJson), /Document-refresh warning/); assert.match(JSON.stringify(result.outputJson), /staged \+ unstaged changes/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not warn on ordinary non-terminal Stop attempts before auto-nudge", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-document-refresh-nonterminal-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-doc-refresh-nonterminal", last_assistant_message: "Continuing implementation; next I will run focused tests.", }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("dedupes identical Stop document-refresh warnings during active Stop-hook replays", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-document-refresh-dedupe-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-doc-refresh-dedupe", last_assistant_message: "Launch-ready: yes", } as const; const first = await dispatchCodexNativeHook(payload, { cwd }); const replay = await dispatchCodexNativeHook({ ...payload, stop_hook_active: true }, { cwd }); assert.match(JSON.stringify(first.outputJson), /Document-refresh warning/); assert.equal(replay.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses Stop document-refresh warning when the final handoff message includes an exemption", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-document-refresh-exempt-")); try { await mkdir(join(cwd, "src", "scripts"), { recursive: true }); execFileSync("git", ["init"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd, stdio: "ignore" }); execFileSync("git", ["config", "user.name", "Test User"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 1;\n", "utf-8"); execFileSync("git", ["add", "src/scripts/codex-native-hook.ts"], { cwd, stdio: "ignore" }); execFileSync("git", ["commit", "-m", "init"], { cwd, stdio: "ignore" }); await writeFile(join(cwd, "src", "scripts", "codex-native-hook.ts"), "export const hook = 2;\n", "utf-8"); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-doc-refresh-exempt", last_assistant_message: `${DOCUMENT_REFRESH_EXEMPTION_PREFIX} internal-only behavior verified`, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Codex App Stop when Ralph is marked complete without completion-audit evidence", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-missing-")); try { const sessionId = "sess-ralph-complete-missing"; const statePath = join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json"); await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd }); await writeJson(statePath, { active: false, mode: "ralph", current_phase: "complete", session_id: sessionId, completed_at: "2026-05-10T12:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, last_assistant_message: "Done. Ralph complete.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); const reason = String(result.outputJson?.reason); assert.match(reason, /Ralph completion audit is missing required evidence/); assert.match(reason, /set "completion_audit" on the Ralph state object/); assert.doesNotMatch(reason, /state\.completion_audit/); assert.match(reason, /repo-relative JSON file/); assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/); assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit"); const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record; assert.equal(reopened.active, false); assert.equal(reopened.current_phase, "complete"); assert.equal(reopened.completion_audit_gate, "blocked"); assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit"); assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z"); const repeat = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, last_assistant_message: "Done. Ralph complete.", }, { cwd }, ); assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit"); assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Codex App Stop when complete Ralph state carries checklist and verification evidence", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-present-")); try { const sessionId = "sess-ralph-complete-present"; await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd }); await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json"), { active: false, mode: "ralph", current_phase: "complete", session_id: sessionId, completed_at: "2026-05-10T12:00:00.000Z", completion_audit: { passed: true, prompt_to_artifact_checklist: ["issue #2260 fixed", "tests added"], verification_evidence: ["node --test dist/scripts/__tests__/codex-native-hook.test.js"], }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, last_assistant_message: "Done with completion audit evidence recorded.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output while Ralph is active without an explicit session pin", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeFile( join(stateDir, "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing", }), ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: executing; state: .omx/state/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_executing", systemMessage: "OMX Ralph is still active (phase: executing; state: .omx/state/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop from session-scoped Ralph state when session.json points to another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-session-mismatch-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-live-ralph"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-ralph" }); await writeJson(join(stateDir, "sessions", "sess-live-ralph", "ralph-state.json"), { active: true, current_phase: "executing", session_id: "sess-live-ralph", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-live-ralph", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-live-ralph/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_executing", systemMessage: "OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-live-ralph/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from stale session-scoped Ralph state that belongs to another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await mkdir(join(stateDir, "sessions", "sess-stale"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-stale", "ralph-state.json"), { active: true, current_phase: "starting", session_id: "sess-stale", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from stale current-session Ralph state when session.json points to a dead owner", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-current-session-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-dead"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-dead", cwd, pid: Number.MAX_SAFE_INTEGER, started_at: "2026-01-01T00:00:00.000Z", }); await writeJson(join(stateDir, "sessions", "sess-dead", "ralph-state.json"), { active: true, current_phase: "verifying", session_id: "sess-dead", }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "team", phase: "team-exec", active_skills: [{ skill: "team", phase: "team-exec", active: true, session_id: "sess-dead" }], }); await writeJson(join(stateDir, "native-stop-state.json"), { sessions: { "sess-dead": { last_signature: "ralph-stop|sess-dead|thread-1|no-message|verifying", updated_at: "2026-04-20T21:00:00.000Z", }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-dead", thread_id: "thread-1", stop_hook_active: true, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not hard-block Stop on stale session-scoped Ralph starting state after visible active modes are cleared", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-cleared-stale-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-cleared-ralph"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "starting", session_id: sessionId, }); await writeJson(join(stateDir, "skill-active-state.json"), { active: false, skill: "ralph", active_skills: [], }); const listActive = await executeStateOperation("state_list_active", { workingDirectory: cwd, }); assert.deepEqual(listActive.payload, { active_modes: [] }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Stop from stale orphaned session-scoped Ralph starting iteration zero state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-orphan-starting-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stale-orphan-ralph"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd }); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "starting", iteration: 0, session_id: sessionId, updated_at: "2000-01-01T00:00:00.000Z", }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralph", phase: "starting", session_id: sessionId, active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: sessionId }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-verifier-terminal", last_assistant_message: "APPROVE: read-only verifier evidence is fresh and sufficient.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop on visible active session-scoped Ralph starting state and reports its path", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-visible-starting-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-visible-ralph"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "starting", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralph", phase: "starting", active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: sessionId }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/sess-visible-ralph/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_starting", systemMessage: "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/sess-visible-ralph/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("retires prompt-seeded Ralph starting state when canonical Ralph already completed with audit", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-shadowed-starting-")); try { const stateDir = join(cwd, ".omx", "state"); const nativeSessionId = "native-hook-seed"; const canonicalSessionId = "omx-runtime-session"; await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true }); await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, cwd, }); await writeJson(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "starting", session_id: nativeSessionId, iteration: 0, task_slug: "mvp-h-local-method-preflight-execution", started_at: "2026-05-14T07:00:00.000Z", }); await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), { active: true, skill: "ralph", phase: "starting", session_id: nativeSessionId, active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: nativeSessionId }], }); await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), { active: false, mode: "ralph", current_phase: "complete", session_id: canonicalSessionId, completed_at: "2026-05-14T07:30:00.000Z", completion_audit: { passed: true, prompt_to_artifact_checklist: ["task evidence mapped"], verification_evidence: ["fresh verification evidence recorded"], }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: nativeSessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); const retiredState = JSON.parse(await readFile(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), "utf-8")); assert.equal(retiredState.active, false); assert.equal(retiredState.current_phase, "complete"); assert.equal(retiredState.stop_reason, "shadowed_by_completed_canonical_ralph"); assert.equal(retiredState.shadowed_by_completed_canonical_ralph.session_id, canonicalSessionId); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not retire prompt-seeded Ralph starting state from a completed canonical Ralph owned by another thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-shadowed-thread-mismatch-")); try { const stateDir = join(cwd, ".omx", "state"); const nativeSessionId = "native-hook-seed"; const canonicalSessionId = "omx-runtime-session"; await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true }); await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: canonicalSessionId, cwd, }); await writeJson(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "starting", session_id: nativeSessionId, iteration: 0, task_slug: "mvp-h-local-method-preflight-execution", started_at: "2026-05-14T07:00:00.000Z", }); await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), { active: true, skill: "ralph", phase: "starting", session_id: nativeSessionId, active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: nativeSessionId }], }); await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), { active: false, mode: "ralph", current_phase: "complete", session_id: canonicalSessionId, owner_codex_thread_id: "thread-A", completed_at: "2026-05-14T07:30:00.000Z", completion_audit: { passed: true, prompt_to_artifact_checklist: ["task evidence mapped"], verification_evidence: ["fresh verification evidence recorded"], }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: nativeSessionId, thread_id: "thread-B", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/native-hook-seed/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_starting", systemMessage: "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/native-hook-seed/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); const preservedState = JSON.parse(await readFile(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), "utf-8")); assert.equal(preservedState.active, true); assert.equal(preservedState.current_phase, "starting"); assert.equal(preservedState.stop_reason, undefined); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-other"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-other", "ralph-state.json"), { active: true, current_phase: "starting", session_id: "sess-other", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block a question-only pane from Ralph state owned by another Codex session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-question-pane-")); const previousTmuxPane = process.env.TMUX_PANE; try { const stateDir = join(cwd, ".omx", "state"); const questionSessionId = "sess-question-pane"; const questionNativeSessionId = "codex-question-pane"; await mkdir(join(stateDir, "sessions", questionSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: questionSessionId, native_session_id: questionNativeSessionId, cwd, }); await writeJson(join(stateDir, "sessions", questionSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: questionSessionId, owner_omx_session_id: "sess-ralph-owner", owner_codex_session_id: "codex-ralph-owner", thread_id: "thread-ralph-owner", tmux_pane_id: "%41", }); process.env.TMUX_PANE = "%99"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: questionNativeSessionId, thread_id: "thread-question-pane", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { if (typeof previousTmuxPane === "string") process.env.TMUX_PANE = previousTmuxPane; else delete process.env.TMUX_PANE; await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop when Ralph skill-active initialization points at another session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-stale-skill-active-")); try { const stateDir = join(cwd, ".omx", "state"); const currentSessionId = "sess-current-ralph"; await mkdir(join(stateDir, "sessions", currentSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: currentSessionId, native_session_id: currentSessionId, cwd, }); await writeJson(join(stateDir, "sessions", currentSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "verifying", session_id: currentSessionId, owner_omx_session_id: currentSessionId, task_slug: "stale-rebound-task", }); await writeJson(join(stateDir, "sessions", currentSessionId, "skill-active-state.json"), { active: true, skill: "ralph", phase: "verifying", session_id: currentSessionId, initialized_mode: "ralph", initialized_state_path: ".omx/state/sessions/sess-old-ralph/ralph-state.json", active_skills: [{ skill: "ralph", phase: "verifying", active: true, session_id: currentSessionId }], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: currentSessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks same-session Ralph Stop continuation when ownership identifiers match", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-owned-session-")); const previousTmuxPane = process.env.TMUX_PANE; try { const stateDir = join(cwd, ".omx", "state"); const omxSessionId = "sess-ralph-owned"; const nativeSessionId = "codex-ralph-owned"; await mkdir(join(stateDir, "sessions", omxSessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: omxSessionId, native_session_id: nativeSessionId, cwd, }); await writeJson(join(stateDir, "sessions", omxSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: omxSessionId, owner_omx_session_id: omxSessionId, owner_codex_session_id: nativeSessionId, thread_id: "thread-ralph-owned", tmux_pane_id: "%42", }); process.env.TMUX_PANE = "%42"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: nativeSessionId, thread_id: "thread-ralph-owned", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-ralph-owned/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_executing", systemMessage: "OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-ralph-owned/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); } finally { if (typeof previousTmuxPane === "string") process.env.TMUX_PANE = previousTmuxPane; else delete process.env.TMUX_PANE; await rm(cwd, { recursive: true, force: true }); } }); it("allows native verifier subagent Stop to complete while leader Ralph remains active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-subagent-verdict-")); try { const stateDir = join(cwd, ".omx", "state"); const omxSessionId = "sess-ralph-leader-verifier"; const leaderNativeSessionId = "codex-ralph-leader-verifier"; const childNativeSessionId = "codex-verifier-child"; await mkdir(join(stateDir, "sessions", omxSessionId), { recursive: true }); await writeSessionStart(cwd, omxSessionId, { nativeSessionId: leaderNativeSessionId, }); await writeJson(join(stateDir, "sessions", omxSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "verifying", session_id: omxSessionId, owner_omx_session_id: omxSessionId, owner_codex_session_id: leaderNativeSessionId, }); const transcriptPath = join(cwd, "verifier-subagent-rollout.jsonl"); await writeFile( transcriptPath, `${JSON.stringify({ type: "session_meta", payload: { id: childNativeSessionId, source: { subagent: { thread_spawn: { parent_thread_id: leaderNativeSessionId, depth: 1, agent_nickname: "Verifier", agent_role: "verifier", }, }, }, agent_nickname: "Verifier", agent_role: "verifier", }, })}\n`, ); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: childNativeSessionId, transcript_path: transcriptPath, }, { cwd, sessionOwnerPid: process.pid }, ); const childStop = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: childNativeSessionId, thread_id: childNativeSessionId, last_assistant_message: "Verdict: APPROVED. Evidence is sufficient.", }, { cwd }, ); assert.equal(childStop.omxEventName, "stop"); assert.equal(childStop.outputJson, null); const leaderStop = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: leaderNativeSessionId, thread_id: leaderNativeSessionId, last_assistant_message: "Waiting on verification integration.", }, { cwd }, ); assert.equal(leaderStop.omxEventName, "stop"); assert.deepEqual(leaderStop.outputJson, { decision: "block", reason: "OMX Ralph is still active (phase: verifying; state: .omx/state/sessions/sess-ralph-leader-verifier/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_verifying", systemMessage: "OMX Ralph is still active (phase: verifying; state: .omx/state/sessions/sess-ralph-leader-verifier/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("prefers canonical run-state terminal lifecycle before stale session Ralph state during Stop", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-canonical-run-state-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-canonical-run-state-ralph"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, mode: "ralph", active: false, outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-04-27T12:00:00.000Z", updated_at: "2026-04-27T12:00:00.000Z", }); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "verifying", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root Ralph fallback when the current session has no scoped Ralph state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd }); await writeJson(join(stateDir, "ralph-state.json"), { active: true, current_phase: "executing", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop when the current session Ralph state is cancelled even if stale root fallback remains", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-cancelled-session-ralph-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd }); await writeJson(join(stateDir, "sessions", "sess-current", "ralph-state.json"), { active: false, current_phase: "cancelled", completed_at: "2026-04-10T23:30:38.000Z", session_id: "sess-current", }); await writeJson(join(stateDir, "ralph-state.json"), { active: true, current_phase: "starting", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root Ralph fallback when an explicit session_id is present and session.json points to another worktree", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-cwd-mismatch-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-elsewhere", cwd: join(cwd, "..", "different-worktree"), }); await writeJson(join(stateDir, "ralph-state.json"), { active: true, current_phase: "executing", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps blocking Ralph Stop replays until the active task advances", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-replay-")); const previousOmxSessionId = process.env.OMX_SESSION_ID; try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeFile( join(stateDir, "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing", }), ); process.env.OMX_SESSION_ID = "sess-stop-ralph-replay"; const payload = { hook_event_name: "Stop", cwd, last_assistant_message: "Next active targets:\n\n1. scheduler integration\n\nI am continuing.", }; const expected = { decision: "block", reason: "OMX Ralph is still active (phase: executing; state: .omx/state/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", stopReason: "ralph_executing", systemMessage: "OMX Ralph is still active (phase: executing; state: .omx/state/ralph-state.json); continue the task and gather fresh verification evidence before stopping.", }; const first = await dispatchCodexNativeHook(payload, { cwd }); const replay = await dispatchCodexNativeHook( { ...payload, stop_hook_active: true, }, { cwd }, ); assert.equal(first.omxEventName, "stop"); assert.deepEqual(first.outputJson, expected); assert.equal(replay.omxEventName, "stop"); assert.deepEqual(replay.outputJson, expected); } finally { if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId; else delete process.env.OMX_SESSION_ID; await rm(cwd, { recursive: true, force: true }); } }); it("lets dispatcher dedupe identical native stop hook replays after Stop payload normalization", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-dedupe-")); const previousOmxSessionId = process.env.OMX_SESSION_ID; try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe"), { recursive: true }); await writeHookCounterPlugin(cwd); await writeFile( join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe", "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing", session_id: "sess-stop-ralph-hook-dedupe", }), ); process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-dedupe"; const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-ralph-hook-dedupe", thread_id: "thread-stop-ralph-hook-dedupe", turn_id: "turn-stop-ralph-hook-dedupe-1", last_assistant_message: "Next active targets:\n\n1. scheduler integration\n\nI am continuing.", }; await dispatchCodexNativeHook(payload, { cwd }); await dispatchCodexNativeHook( { ...payload, stop_hook_active: true, }, { cwd }, ); const marker = JSON.parse( await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"), ) as { count: number }; assert.equal(marker.count, 1); } finally { if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId; else delete process.env.OMX_SESSION_ID; await rm(cwd, { recursive: true, force: true }); } }); it("preserves per-turn native stop hook delivery even when stop_hook_active remains true", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-refire-")); const previousOmxSessionId = process.env.OMX_SESSION_ID; try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-refire"), { recursive: true }); await writeHookCounterPlugin(cwd); await writeFile( join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing", session_id: "sess-stop-ralph-hook-refire", }), ); process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-refire"; const payload = { hook_event_name: "Stop", cwd, session_id: "sess-stop-ralph-hook-refire", thread_id: "thread-stop-ralph-hook-refire", turn_id: "turn-stop-ralph-hook-refire-1", last_assistant_message: "Continuing current task.", }; await dispatchCodexNativeHook(payload, { cwd }); await dispatchCodexNativeHook( { ...payload, turn_id: "turn-stop-ralph-hook-refire-2", stop_hook_active: true, }, { cwd }, ); await writeFile( join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"), JSON.stringify({ active: true, current_phase: "executing", session_id: "sess-stop-ralph-hook-refire", }), ); await dispatchCodexNativeHook( { ...payload, turn_id: "turn-stop-ralph-hook-refire-3", stop_hook_active: true, }, { cwd }, ); const marker = JSON.parse( await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"), ) as { count: number }; assert.equal(marker.count, 3); } finally { if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId; else delete process.env.OMX_SESSION_ID; await rm(cwd, { recursive: true, force: true }); } }); it("returns Stop continuation output for native auto-nudge stall prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("bounds repeated ordinary working Stop loops with a diagnostic summary", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-working-loop-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-working-loop"; process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS = "2"; process.env.OMX_NATIVE_STOP_NO_PROGRESS_IDLE_MS = "0"; const payload = { hook_event_name: "Stop", cwd, session_id: "sess-working-loop", thread_id: "thread-working-loop", turn_id: "turn-working-loop-1", last_assistant_message: "Keep going and finish the cleanup.", }; const first = await dispatchCodexNativeHook(payload, { cwd }); assert.equal(first.outputJson?.stopReason, "auto_nudge"); const repeated = await dispatchCodexNativeHook( { ...payload, turn_id: "turn-working-loop-2", stop_hook_active: true, }, { cwd }, ); assert.equal(repeated.omxEventName, "stop"); assert.equal(repeated.outputJson?.decision, "block"); assert.equal(repeated.outputJson?.stopReason, "ordinary_task_no_progress_guard"); assert.match(String(repeated.outputJson?.systemMessage), /no-progress guard triggered/); assert.match(String(repeated.outputJson?.systemMessage), /diagnostic summary/); assert.match(String(repeated.outputJson?.systemMessage), /complete, blocked, failed, or needs missing information/); const persisted = JSON.parse( await readFile(join(cwd, ".omx", "state", "native-stop-state.json"), "utf-8"), ) as { sessions: Record }; assert.equal(persisted.sessions["sess-working-loop"]?.ordinary_no_progress_guard?.repeat_count, 2); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-blocks duplicate native auto-nudge replays for the same Stop reply", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-once-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-once"; await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-once", thread_id: "thread-stop-auto", turn_id: "turn-stop-auto-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-once", thread_id: "thread-stop-auto", turn_id: "turn-stop-auto-1", stop_hook_active: true, last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-blocks duplicate native auto-nudge replays across native/canonical session-id drift", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-session-drift-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "omx-canonical"; await writeJson(join(stateDir, "session.json"), { session_id: "omx-canonical", native_session_id: "codex-native", }); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "codex-native", thread_id: "thread-stop-auto-drift", turn_id: "turn-stop-auto-drift-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "omx-canonical", thread_id: "thread-stop-auto-drift", turn_id: "turn-stop-auto-drift-1", stop_hook_active: true, last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); const persisted = JSON.parse( await readFile(join(stateDir, "native-stop-state.json"), "utf-8"), ) as { sessions?: Record }; assert.deepEqual(Object.keys(persisted.sessions ?? {}), ["omx-canonical"]); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("dedupes native stop hook replay across owner launch SessionStart reconciliation drift", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-dispatch-session-drift-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "omx-canonical"), { recursive: true }); await writeHookCounterPlugin(cwd); process.env.OMX_SESSION_ID = "omx-canonical"; await writeSessionStart(cwd, "omx-canonical"); await writeJson(join(stateDir, "sessions", "omx-canonical", "ralph-state.json"), { active: true, current_phase: "executing", session_id: "omx-canonical", }); await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "codex-native-new", }, { cwd, sessionOwnerPid: process.pid }, ); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "codex-native-new", thread_id: "thread-stop-hook-drift", turn_id: "turn-stop-hook-drift-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "omx-canonical", thread_id: "thread-stop-hook-drift", turn_id: "turn-stop-hook-drift-1", stop_hook_active: true, last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); const marker = JSON.parse( await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"), ) as { count: number }; assert.equal(marker.count, 1); const sessionState = JSON.parse( await readFile(join(stateDir, "session.json"), "utf-8"), ) as { session_id?: string; native_session_id?: string }; assert.equal(sessionState.session_id, "omx-canonical"); assert.equal(sessionState.native_session_id, "codex-native-new"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-fires native auto-nudge for a later fresh Stop reply even when stop_hook_active is true", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-refire-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-refire"; await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-refire", thread_id: "thread-stop-auto-refire", turn_id: "turn-stop-auto-refire-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-refire", thread_id: "thread-stop-auto-refire", turn_id: "turn-stop-auto-refire-2", stop_hook_active: true, last_assistant_message: "Continue with the cleanup from here.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("auto-continues native Stop on permission-seeking prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-permission-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-permission"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-permission", last_assistant_message: "Would you like me to continue with the cleanup?", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("auto-continues native Stop on \"if you want\" permission-seeking prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-if-you-want-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-if-you-want"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-if-you-want", last_assistant_message: "If you want, I can continue with the cleanup from here.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not auto-continue native Stop while deep-interview is waiting on an intent-first question", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-question-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-auto-question"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-question"; await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-question" }); await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "skill-active-state.json"), { version: 1, active: true, skill: "deep-interview", phase: "planning", session_id: "sess-stop-auto-question", thread_id: "thread-stop-auto-question", input_lock: { active: true, scope: "deep-interview-auto-approval", blocked_inputs: ["yes", "proceed"], message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.", }, }); await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-question", thread_id: "thread-stop-auto-question", turn_id: "turn-stop-auto-question-1", last_assistant_message: [ "Round 2 | Target: Decision boundary | Ambiguity: 24%", "", "If an existing project spider still declares session_mode = \"owned\", should ZenX fail loudly so the stale attribute is removed, or should it ignore the attribute and initialize the session pool anyway?", "Keep going once I have your answer.", ].join("\n"), }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses native auto-nudge re-fire while session-scoped deep-interview state is still active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-state-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-auto-interview"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-interview"; await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-interview" }); await writeJson(join(stateDir, "sessions", "sess-stop-auto-interview", "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-interview", thread_id: "thread-stop-auto-interview", turn_id: "turn-stop-auto-interview-2", stop_hook_active: true, last_assistant_message: "If you want, I can keep going from here.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses native auto-nudge when root deep-interview mode state is active and no session is known", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-mode-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, turn_id: "turn-stop-auto-mode-1", last_assistant_message: "Would you like me to continue with the next step?", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("treats inherited OMX_SESSION_ID as session-aware for native auto-nudge Stop checks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-env-session-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-mode"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-stop-auto-env-session", turn_id: "turn-stop-auto-env-session-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8")) as Record; assert.ok((stopState.sessions as Record)["sess-stop-auto-mode"]); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("ignores generic SESSION_ID for native auto-nudge Stop session scoping", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-generic-session-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.SESSION_ID = "generic-shell-session"; const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, thread_id: "thread-stop-auto-generic-session", turn_id: "turn-stop-auto-generic-session-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8")) as Record; const sessions = stopState.sessions as Record; assert.equal(sessions["generic-shell-session"], undefined); assert.ok(sessions["thread-stop-auto-generic-session"]); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not suppress native auto-nudge from stale root deep-interview mode state when the explicit session-scoped mode state is absent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-mode-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-stale-root-mode"; await writeJson(join(stateDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-stale-root-mode", thread_id: "thread-stop-auto-stale-root-mode", turn_id: "turn-stop-auto-stale-root-mode-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not suppress native auto-nudge from stale root deep-interview skill state when the explicit session-scoped canonical skill state is absent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-skill-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-stale-root-skill"; await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-stale-root-skill", thread_id: "thread-stop-auto-stale-root-skill", turn_id: "turn-stop-auto-stale-root-skill-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not suppress native auto-nudge from stale root deep-interview input lock when the explicit session-scoped canonical skill state is absent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-stale-root-lock-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-stale-root-lock"; await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "deep-interview", phase: "planning", input_lock: { active: true, scope: "deep-interview-auto-approval", blocked_inputs: ["yes", "proceed"], message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.", }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-stale-root-lock", thread_id: "thread-stop-auto-stale-root-lock", turn_id: "turn-stop-auto-stale-root-lock-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not suppress native auto-nudge from active root deep-interview state when the current scoped mode state is explicitly inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-inactive-scoped-mode-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-auto-inactive-mode"), { recursive: true }); process.env.OMX_SESSION_ID = "sess-stop-auto-inactive-mode"; await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-inactive-mode" }); await writeJson(join(stateDir, "sessions", "sess-stop-auto-inactive-mode", "deep-interview-state.json"), { active: false, mode: "deep-interview", current_phase: "completed", }); await writeJson(join(stateDir, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "intent-first", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-auto-inactive-mode", thread_id: "thread-stop-auto-inactive-mode", turn_id: "turn-stop-auto-inactive-mode-1", last_assistant_message: "Keep going and finish the cleanup.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("clears stale root skill-active state when current session ralplan is terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-terminal-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-terminal-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "completed", lifecycle_outcome: "finished", run_outcome: "finish", final_artifact: "proposed_plan", }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "ultrawork", phase: "planning", source: "keyword-detector", active_skills: [ { skill: "ultrawork", phase: "planning", active: true }, ], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-terminal-ralplan", turn_id: "turn-stop-terminal-ralplan-1", last_assistant_message: "Done.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); const rootSkillState = JSON.parse( await readFile(join(stateDir, "skill-active-state.json"), "utf-8"), ) as { active?: boolean; active_skills?: unknown[]; reconciliation_reason?: string }; assert.equal(rootSkillState.active, false); assert.deepEqual(rootSkillState.active_skills, []); assert.equal(rootSkillState.reconciliation_reason, "stop_hook_session_state_terminal"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("preserves legitimate session-scoped ultrawork blocking while reconciling root skill-active state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-active-root-skill-session-mode-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-active-ultrawork"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "ultrawork-state.json"), { active: true, mode: "ultrawork", current_phase: "executing", session_id: sessionId, }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "ultrawork", phase: "planning", source: "keyword-detector", active_skills: [ { skill: "ultrawork", phase: "planning", active: true, session_id: sessionId }, ], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-active-ultrawork", turn_id: "turn-stop-active-ultrawork-1", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: "OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.", stopReason: "ultrawork_executing", systemMessage: "OMX ultrawork is still active (phase: executing).", }); const rootSkillState = JSON.parse( await readFile(join(stateDir, "skill-active-state.json"), "utf-8"), ) as { active?: boolean; active_skills?: Array<{ skill?: string }> }; assert.equal(rootSkillState.active, true); assert.deepEqual(rootSkillState.active_skills?.map((entry) => entry.skill), ["ultrawork"]); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("reconciles stale root skill-active state under OMX_ROOT boxed state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-boxed-source-")); const omxRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-boxed-root-")); const previousOmxRoot = process.env.OMX_ROOT; try { process.env.OMX_ROOT = omxRoot; const stateDir = join(omxRoot, ".omx", "state"); const sourceStateDir = join(cwd, ".omx", "state"); const sessionId = "sess-stop-boxed-ralplan"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: false, mode: "ralplan", current_phase: "completed", lifecycle_outcome: "finished", run_outcome: "finish", }); await writeJson(join(stateDir, "skill-active-state.json"), { active: true, skill: "ultrawork", phase: "planning", source: "keyword-detector", active_skills: [ { skill: "ultrawork", phase: "planning", active: true }, ], }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: sessionId, thread_id: "thread-stop-boxed-ralplan", turn_id: "turn-stop-boxed-ralplan-1", last_assistant_message: "Done.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); const boxedRootSkillState = JSON.parse( await readFile(join(stateDir, "skill-active-state.json"), "utf-8"), ) as { active?: boolean; active_skills?: unknown[]; reconciliation_reason?: string }; assert.equal(boxedRootSkillState.active, false); assert.deepEqual(boxedRootSkillState.active_skills, []); assert.equal(boxedRootSkillState.reconciliation_reason, "stop_hook_session_state_terminal"); assert.equal(existsSync(join(sourceStateDir, "skill-active-state.json")), false); } finally { if (previousOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = previousOmxRoot; await rm(cwd, { recursive: true, force: true }); await rm(omxRoot, { recursive: true, force: true }); } }); it("auto-continues native Stop for permission-seeking prompts even outside OMX runtime", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-plain-session-")); try { await dispatchCodexNativeHook( { hook_event_name: "SessionStart", cwd, session_id: "plain-stop-session", }, { cwd, sessionOwnerPid: process.pid, }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "plain-stop-session", thread_id: "plain-thread", turn_id: "plain-turn-1", last_assistant_message: "If you want, I can continue with the cleanup from here.", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: DEFAULT_AUTO_NUDGE_RESPONSE, stopReason: "auto_nudge", systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-fires team Stop output for a later fresh Stop reply while the team is still active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-refire-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "team-exec", team_name: "review-team", session_id: "sess-stop-team-refire", thread_id: "thread-stop-team-refire", }); await writeJson(join(stateDir, "team", "review-team", "phase.json"), { current_phase: "team-verify", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-refire", thread_id: "thread-stop-team-refire", turn_id: "turn-stop-team-refire-1", }, { cwd }, ); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-team-refire", thread_id: "thread-stop-team-refire", turn_id: "turn-stop-team-refire-2", stop_hook_active: true, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (review-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-verify", systemMessage: "OMX team pipeline is still active at phase team-verify.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses duplicate team Stop replays across native/canonical session-id drift", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-session-drift-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "omx-canonical"), { recursive: true }); process.env.OMX_SESSION_ID = "omx-canonical"; await writeJson(join(stateDir, "session.json"), { session_id: "omx-canonical", native_session_id: "codex-native", }); await writeJson(join(stateDir, "sessions", "omx-canonical", "team-state.json"), { active: true, current_phase: "starting", team_name: "current-team", session_id: "omx-canonical", }); await writeJson(join(stateDir, "team", "current-team", "phase.json"), { current_phase: "team-verify", max_fix_attempts: 3, current_fix_attempt: 1, transitions: [], updated_at: new Date().toISOString(), }); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "codex-native", thread_id: "thread-stop-team-drift", turn_id: "turn-stop-team-drift-1", }, { cwd }, ); const duplicate = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "omx-canonical", thread_id: "thread-stop-team-drift", turn_id: "turn-stop-team-drift-1", stop_hook_active: true, }, { cwd }, ); assert.equal(duplicate.omxEventName, "stop"); assert.deepEqual(duplicate.outputJson, { decision: "block", reason: `OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-verify", systemMessage: "OMX team pipeline is still active at phase team-verify.", }); const fresh = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "omx-canonical", thread_id: "thread-stop-team-drift", turn_id: "turn-stop-team-drift-2", stop_hook_active: true, }, { cwd }, ); assert.equal(fresh.omxEventName, "stop"); assert.deepEqual(fresh.outputJson, { decision: "block", reason: `OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-verify", systemMessage: "OMX team pipeline is still active at phase team-verify.", }); const persisted = JSON.parse( await readFile(join(stateDir, "native-stop-state.json"), "utf-8"), ) as { sessions?: Record }; assert.deepEqual(Object.keys(persisted.sessions ?? {}), ["omx-canonical"]); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses duplicate ultrawork Stop replays while stop_hook_active stays true", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-repeat-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-ultrawork-repeat"), { recursive: true }); await writeJson(join(stateDir, "sessions", "sess-stop-ultrawork-repeat", "ultrawork-state.json"), { active: true, current_phase: "executing", }); const first = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-ultrawork-repeat", thread_id: "thread-stop-ultrawork-repeat", turn_id: "turn-stop-ultrawork-repeat-1", }, { cwd }, ); const repeated = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-ultrawork-repeat", thread_id: "thread-stop-ultrawork-repeat", turn_id: "turn-stop-ultrawork-repeat-1", stop_hook_active: true, }, { cwd }, ); const fresh = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-ultrawork-repeat", thread_id: "thread-stop-ultrawork-repeat", turn_id: "turn-stop-ultrawork-repeat-2", stop_hook_active: true, }, { cwd }, ); assert.equal(first.omxEventName, "stop"); assert.deepEqual(repeated.outputJson, null); assert.equal(fresh.omxEventName, "stop"); assert.deepEqual(fresh.outputJson, { decision: "block", reason: "OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.", stopReason: "ultrawork_executing", systemMessage: "OMX ultrawork is still active (phase: executing).", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("re-blocks active ralplan skill state on repeated Stop hooks", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-repeat-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-stop-skill-repeat"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-repeat" }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-repeat", "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", }); await writeJson(join(stateDir, "sessions", "sess-stop-skill-repeat", "ralplan-state.json"), { active: true, current_phase: "planning", }); await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-skill-repeat", thread_id: "thread-stop-skill-repeat", turn_id: "turn-stop-skill-repeat-1", }, { cwd }, ); const repeated = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-stop-skill-repeat", thread_id: "thread-stop-skill-repeat", turn_id: "turn-stop-skill-repeat-1", stop_hook_active: true, }, { cwd }, ); assert.equal(repeated.omxEventName, "stop"); assert.equal(repeated.outputJson?.decision, "block"); assert.match(String(repeated.outputJson?.reason ?? ""), /Status: continue_from_artifact/); assert.match(String(repeated.outputJson?.reason ?? ""), /continue from the current ralplan artifact/i); assert.equal(repeated.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes while ralplan is active without execution handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-pretool-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "critic-review", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); assert.match( String((result.outputJson?.hookSpecificOutput as { additionalContext?: string } | undefined)?.additionalContext ?? ""), /\$ultragoal.*\$team.*\$ralph/i, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes while Autopilot is supervising deep-interview without a persisted phase transition", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-deep-interview-pretool-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-deep-interview-pretool-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "deep-interview", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "deep-interview", session_id: sessionId, handoff_artifacts: { deep_interview: null, ralplan: null, ultragoal: null, code_review: null, ultraqa: null, }, ralplan_consensus_gate: { ralplan_architect_review: null, ralplan_critic_review: null, complete: false, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-deep-interview-pretool-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active .*implementation\/write tools are blocked/i); assert.match( String((result.outputJson?.hookSpecificOutput as { additionalContext?: string } | undefined)?.additionalContext ?? ""), /To implement, first ask for or process an explicit transition/i, ); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes while Autopilot is supervising ralplan without handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-pretool-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, state: { handoff_artifacts: { ralplan_consensus_gate: { required: true, complete: false }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-ralplan-pretool-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); for (const command of [ "sed -i 's/old/new/' src/runtime.ts", "perl -pi -e 's/old/new/' src/runtime.ts", ]) { const writeIntentResult = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-bash-block", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal((writeIntentResult.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match( String((writeIntentResult.outputJson as { reason?: string } | null)?.reason ?? ""), /Bash .* (?:write intent|mutation target) .*not workflow state\/ledger\/mailbox\/handoff metadata|target |not under allowed planning artifact paths or metadata paths/, ); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows Autopilot planning handoffs with active:true state writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-planning-state-write-allow-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-planning-state-write-allow"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "planning", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "planning", session_id: sessionId, state: { handoff_artifacts: { ralplan_consensus_gate: { required: true, complete: false }, }, }, }); const allowedPlanningHandoff = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-planning-state-write-allow", tool_name: "mcp__omx_state__state_write", tool_input: { mode: "autopilot", active: true, current_phase: "planning" }, }, { cwd }, ); assert.equal(allowedPlanningHandoff.outputJson, null); const blockedPlanningDeactivation = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-planning-state-write-allow", tool_name: "mcp__omx_state__state_write", tool_input: { mode: "autopilot", active: false, current_phase: "planning" }, }, { cwd }, ); assert.equal((blockedPlanningDeactivation.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes when Autopilot ralplan is visible only in skill-active phase", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-skill-ralplan-pretool-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-skill-ralplan-pretool-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "autopilot:ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "planning", session_id: sessionId, state: { handoff_artifacts: { ralplan_consensus_gate: { required: true, complete: false }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-skill-ralplan-pretool-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("ignores stale Autopilot ralplan skill mirrors after detail state leaves planning", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-stale-ralplan-mirror-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-stale-ralplan-mirror"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "autopilot:ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }], }); for (const phase of ["ultragoal", "code-review", "completing", "complete"]) { await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: phase, session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-stale-ralplan-mirror", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null, `stale skill-active ralplan mirror must not block when Autopilot detail phase is ${phase}`); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows explicit blank Autopilot detail phase to use a ralplan skill mirror", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-blank-phase-mirror-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-blank-phase-mirror"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "autopilot:ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-blank-phase-mirror", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-no-canonical"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-ralplan-no-canonical", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-terminal-pretool-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-terminal-pretool"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, active: false, mode: "autopilot", outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-30T00:00:00.000Z", updated_at: "2026-05-30T00:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-ralplan-terminal-pretool", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /(?:Ralplan|Autopilot planning) is active \(phase: ralplan\)/); assert.match(JSON.stringify(result.outputJson), /implementation\/write tools are blocked/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks bash implementation writes while Autopilot is supervising ralplan without handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-bash-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-pretool-bash-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-ralplan-pretool-bash-block", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes when ralplan and Autopilot ralplan are both active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-autopilot-mixed-planning-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-autopilot-mixed-planning"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "ralplan", session_id: sessionId, active_skills: [ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId }, { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }, ], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-autopilot-mixed-planning", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes while Autopilot is supervising replan without handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-replan-pretool-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-replan-pretool-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "autopilot", phase: "replan", session_id: sessionId, active_skills: [{ skill: "autopilot", phase: "replan", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "replan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-replan-pretool-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-native-map-block"; const nativeSessionId = "019e-autopilot-ralplan-native"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-autopilot-ralplan-native-map-block", tool_name: "apply_patch", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks bash implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-bash-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-native-map-bash"; const nativeSessionId = "019e-autopilot-ralplan-native-bash"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-autopilot-ralplan-native-map-bash", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks standalone ralplan writes when native Codex id maps to OMX session state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-native-map-block"; const nativeSessionId = "019e-ralplan-native-map"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-ralplan-native-map-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks deep-interview writes when native Codex id maps to OMX session state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-native-map-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-deep-interview-native-map-block"; const nativeSessionId = "019e-deep-interview-native-map"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "deep-interview", "interview"); await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), { active: true, mode: "deep-interview", current_phase: "interview", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-deep-interview-native-map-block", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows mapped ralplan planning artifact writes without execution handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-native-map-artifact"; const nativeSessionId = "019e-ralplan-native-map-artifact"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-ralplan-native-map-artifact", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > .omx/plans/prd-native-map.md\nplanning\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks mapped implementation writes when explicit ultragoal conductor handoff is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-handoff-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-native-map-handoff"; const nativeSessionId = "019e-ralplan-native-map-handoff"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ultragoal", phase: "planning", session_id: sessionId, active_skills: [ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId }, { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId }, ], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "complete", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-ralplan-native-map-handoff", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks mapped implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-terminal-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-ralplan-native-map-terminal"; const nativeSessionId = "019e-autopilot-ralplan-native-terminal"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), { version: 1, active: false, mode: "autopilot", outcome: "finish", lifecycle_outcome: "finished", current_phase: "complete", completed_at: "2026-05-30T00:00:00.000Z", updated_at: "2026-05-30T00:00:00.000Z", }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-autopilot-ralplan-native-map-terminal", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block"); assert.match(JSON.stringify(result.outputJson), /(?:Ralplan|Autopilot planning) is active \(phase: ralplan\)/); assert.match(JSON.stringify(result.outputJson), /implementation\/write tools are blocked/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("fails closed for implementation writes when a different live root session owns active ralplan state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-live-root-conflict-")); const ownerCwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-live-root-owner-")); try { const stateDir = join(cwd, ".omx", "state"); const ownerSessionId = "sess-ralplan-live-root-owner"; const ownerNativeSessionId = "019e-ralplan-live-root-owner"; await writeLiveNativeMappedSessionState(ownerCwd, stateDir, ownerSessionId, ownerNativeSessionId); await writeSessionSkillActiveState(stateDir, ownerSessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", ownerSessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: ownerSessionId, cwd: ownerCwd, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-ralplan-live-root-unresolved-current", thread_id: "thread-ralplan-live-root-conflict", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /live root session pointer/i); assert.match(String(result.outputJson?.reason ?? ""), /failing closed/i); const blockedMcpStateClear = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-ralplan-live-root-unresolved-current", thread_id: "thread-ralplan-live-root-conflict", tool_name: "mcp__omx_state__state_clear", tool_input: { mode: "ralplan" }, }, { cwd }, ); assert.equal((blockedMcpStateClear.outputJson as { decision?: string } | null)?.decision, "block"); const blockedMcpStateWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-ralplan-live-root-unresolved-current", thread_id: "thread-ralplan-live-root-conflict", tool_name: "mcp__omx_state__state_write", tool_input: { mode: "ralplan", active: false }, }, { cwd }, ); assert.equal((blockedMcpStateWrite.outputJson as { decision?: string } | null)?.decision, "block"); const blockedOmittedSessionTerminalWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-ralplan-live-root-unresolved-current", thread_id: "thread-ralplan-live-root-conflict", tool_name: "Bash", tool_input: { command: "omx state write --mode ralplan --input '{\"active\":false,\"current_phase\":\"complete\"}' --json", }, }, { cwd }, ); assert.equal((blockedOmittedSessionTerminalWrite.outputJson as { decision?: string } | null)?.decision, "block"); } finally { await rm(cwd, { recursive: true, force: true }); await rm(ownerCwd, { recursive: true, force: true }); } }); it("preserves authoritative owner planning artifact writes with a live root session pointer", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-live-root-owner-pass-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-live-root-owner-pass"; const nativeSessionId = "019e-ralplan-live-root-owner-pass"; await writeLiveNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, cwd, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-ralplan-live-root-owner-pass", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > .omx/plans/live-root-owner.md\nplanning\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block unrelated native Codex ids when current OMX session mapping does not match", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-unrelated-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-native-map-owner"; const ownerNativeSessionId = "019e-ralplan-native-owner"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, ownerNativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-unrelated-native-session", thread_id: "thread-ralplan-native-map-unrelated", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks mapped Autopilot ralplan writes from the authoritative team state root", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-team-root-")); const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-")); const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_TEAM_STATE_ROOT = teamStateRoot; const stateDir = teamStateRoot; const sessionId = "sess-autopilot-ralplan-team-root"; const nativeSessionId = "019e-autopilot-ralplan-team-root"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "ralplan", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-autopilot-ralplan-team-root", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); assert.equal(existsSync(join(cwd, ".omx", "state", "session.json")), false); } finally { if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); await rm(teamStateRoot, { recursive: true, force: true }); } }); it("does not block unrelated native Codex ids from the authoritative team state root", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-team-root-unrelated-")); const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-unrelated-")); const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_TEAM_STATE_ROOT = teamStateRoot; const stateDir = teamStateRoot; const sessionId = "sess-ralplan-team-root-owner"; const nativeSessionId = "019e-ralplan-team-root-owner"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: "019e-unrelated-team-root-native", thread_id: "thread-ralplan-team-root-unrelated", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot; else delete process.env.OMX_TEAM_STATE_ROOT; await rm(cwd, { recursive: true, force: true }); await rm(teamStateRoot, { recursive: true, force: true }); } }); it("allows ralplan planning artifact writes without execution handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-pretool-artifact"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-artifact", tool_name: "Write", tool_input: { file_path: ".omx/plans/prd-issue-2603.md" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks bash implementation writes while ralplan is active without execution handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-block-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-pretool-bash-block"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-bash-block", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows bash planning artifact writes while ralplan is active without execution handoff", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-artifact-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-pretool-bash-artifact"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ralplan", phase: "planning", session_id: sessionId, active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-bash-artifact", tool_name: "Bash", tool_input: { command: "cat <<'EOF' > .omx/plans/prd-issue-2603.md\nplanning\nEOF" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows implementation writes when an explicit execution handoff is active", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-handoff-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-pretool-handoff"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), { active: true, skill: "ultragoal", phase: "planning", session_id: sessionId, active_skills: [ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId }, { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId }, ], }); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "complete", session_id: sessionId, }); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-pretool-handoff", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks mapped native-session Main-root conductor writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-native-map-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralph-conductor-native-map"; const nativeSessionId = "019e-ralph-conductor-native-map"; await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: nativeSessionId, thread_id: "thread-ralph-conductor-native-map", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ralph phase: executing\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks standalone ultragoal conductor writes without requiring ralplan active_skills", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-standalone-conductor-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ultragoal-standalone-conductor"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ultragoal-standalone-conductor", tool_name: "Write", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root ralplan writes even when payload has only a typed agent_role", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-agent-role-main-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralplan-agent-role-main"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), { active: true, mode: "ralplan", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralplan-agent-role-main", agent_role: "executor", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active \(phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root conductor writes even when payload has only a typed agent_role", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-agent-role-main-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-agent-role-main"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "planning", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-agent-role-main", agent_role: "executor", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows conductor writes from tracked typed native subagents", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-tracked-subagent-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-tracked-subagent"; const leaderThreadId = "thread-conductor-leader"; const childThreadId = "thread-conductor-child"; const nowIso = new Date().toISOString(); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "planning", session_id: sessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: nowIso, threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, [childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: childThreadId, agent_role: "executor", source: { subagent: { thread_spawn: { parent_thread_id: leaderThreadId, }, }, }, tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("allows conductor writes from tracked typed native subagents when PreToolUse omits thread_spawn source", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-tracked-subagent-no-source-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-tracked-subagent-no-source"; const leaderThreadId = "thread-conductor-leader-no-source"; const childThreadId = "thread-conductor-child-no-source"; const nowIso = new Date().toISOString(); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "executing", session_id: sessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: nowIso, threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, [childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: childThreadId, agent_role: "executor", tool_name: "apply_patch", tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" }, }, { cwd }, ); assert.equal(result.outputJson, null); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("blocks conductor writes when corrupt tracker state labels the leader as a subagent", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-corrupt-leader-subagent-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-corrupt-leader-subagent"; const leaderThreadId = "thread-conductor-corrupt-leader"; const nowIso = new Date().toISOString(); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "executing", session_id: sessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: nowIso, threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: leaderThreadId, agent_role: "executor", tool_name: "apply_patch", tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("blocks conductor writes when thread_spawn provenance is attached to the leader thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-thread-spawn-leader-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = cwd; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-thread-spawn-leader"; const leaderThreadId = "thread-conductor-thread-spawn-leader"; const nowIso = new Date().toISOString(); await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId }); await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), { active: true, mode: "ultragoal", current_phase: "executing", session_id: sessionId, }); await writeJson(join(stateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { [sessionId]: { session_id: sessionId, leader_thread_id: leaderThreadId, updated_at: nowIso, threads: { [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: leaderThreadId, agent_role: "executor", source: { subagent: { thread_spawn: { parent_thread_id: leaderThreadId, }, }, }, tool_name: "apply_patch", tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalOmxTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot; await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root ralph conductor source and planning artifact writes while allowing .omx workflow state writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralph-conductor-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blocked = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(blocked.outputJson?.decision, "block"); assert.match(String(blocked.outputJson?.reason ?? ""), /ralph phase: executing/); for (const [toolName, filePath] of [ ["Write", ".omx/plans/conductor-owned-plan.md"], ["Edit", ".omx/specs/conductor-owned-spec.md"], ] as const) { const planningArtifactWrite = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: toolName, tool_input: { file_path: filePath }, }, { cwd }, ); assert.equal(planningArtifactWrite.outputJson?.decision, "block", `${toolName} ${filePath}`); assert.match(String(planningArtifactWrite.outputJson?.reason ?? ""), /plan\/code writes are blocked/); } const allowed = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Write", tool_input: { file_path: ".omx/state/sessions/sess-ralph-conductor-write/ralph-state.json" }, }, { cwd }, ); assert.equal(allowed.outputJson, null); const protectedRawState = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Write", tool_input: { file_path: ".omx/state/sessions/sess-ralph-conductor-write/autopilot-state.json" }, }, { cwd }, ); assert.equal(protectedRawState.outputJson?.decision, "block"); const safeTransport = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Bash", tool_input: { command: "omx state write --input '{\"mode\":\"ralph\",\"current_phase\":\"executing\",\"active\":true}' --json" }, }, { cwd }, ); assert.equal(safeTransport.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-bash-mutations"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blockedCommands = [ "mv src/old.ts src/new.ts", "cp package.json src/package-copy.json", "touch src/generated.ts", "mkdir -p src/generated", "rm -f src/generated.ts", "chmod 600 src/runtime.ts", "sudo -n cp README.md src/readme-copy.md", "env cp package.json src/package-copy.json", "exec cp package.json src/package-copy.json", "env FOO=1 mv src/a.ts src/b.ts", "cp -t src .omx/state/conductor-ledger.json", "mv --target-directory src .omx/state/foo", "install -t src .omx/state/foo", "touch .omx/plans/conductor-owned-plan.md", "cat <<'EOF' > .omx/specs/conductor-owned-spec.md\n# Spec\nEOF", "python3 <<'PY'\nfrom pathlib import Path\nPath('src/x.ts').write_text('x')\nPY", "python3 - <<'PY'\nimport shutil\nshutil.copyfile('a', 'src/foo')\nPY", "bash -lc \"mv src/old.ts src/new.ts\"", "sh -c 'cp package.json src/package-copy.json'", "bash -lc \"sed -i 's/old/new/' src/runtime.ts\"", "bash -lc \"perl -pi -e 's/old/new/' src/runtime.ts\"", "printf ok; cp package.json src/package-copy.json", "(cp .omx/state/foo src/foo)", "{ cp .omx/state/foo src/foo; }", "curl -o src/downloaded.json https://example.invalid/data.json", "curl -O https://example.invalid/data.json", "wget -O src/downloaded.json https://example.invalid/data.json", "wget -o src/wget.log https://example.invalid/data.json", "cd /tmp && cp .omx/state/foo src/foo", ]; for (const command of blockedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-bash-mutations", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Bash (?:.* mutation target|.*write target) .*not workflow state\/ledger\/mailbox\/handoff metadata|target /); } const allowedCommands = [ "touch .omx/state/conductor-ledger.json", "mkdir -p .omx/handoffs/run-1", "cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/conductor-ledger.json", "mv .omx/handoffs/run-1/conductor-ledger.json .omx/handoffs/run-1/ledger.json", "env cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/env-ledger.json", "exec cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/exec-ledger.json", "cp src/source.ts .omx/state/source-copy.ts", "cat <<'EOF' > .omx/state/conductor-heredoc.json\n{}\nEOF", "bash -lc \"printf safe\"", "sh -c 'printf safe'", "curl https://example.invalid/data.json", "curl -o .omx/state/downloaded.json https://example.invalid/data.json", "wget -O .omx/state/downloaded.json https://example.invalid/data.json", ]; for (const command of allowedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-bash-mutations", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal(result.outputJson, null, command); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows autopilot rework implementation writes while conductor phases stay guarded", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-rework-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-rework-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "rework"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "rework", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-rework-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root ralph conductor source writes while allowing .omx workflow state writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralph-conductor-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blocked = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(blocked.outputJson?.decision, "block"); assert.match(String(blocked.outputJson?.reason ?? ""), /ralph phase: executing/); const allowed = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Write", tool_input: { file_path: ".omx/state/sessions/sess-ralph-conductor-write/ralph-state.json" }, }, { cwd }, ); assert.equal(allowed.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks non-shell direct writes in Main-root conductor states", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-bash-mutations"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeLiveNativeMappedSessionState(cwd, stateDir, sessionId, "native-conductor-bash-mutations"); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blockedCommands = [ "node -e \"require('fs').appendFileSync('src/runtime.ts','x')\"", "python3 -c \"open('src/runtime.ts','a').write('x')\"", "curl -fsSL https://example.test/runtime.ts -o src/runtime.ts", "curl -fsSL -O https://example.test/runtime.ts --output-dir src", "curl -fsSL -LO https://example.test/runtime.ts --output-dir src", "curl -fsSL --remote-name --output-dir=src https://example.test/runtime.ts", "wget -O src/runtime.ts https://example.test/runtime.ts", "curl --output-dir src -O https://example.test/runtime.ts", "curl --create-dirs --output-dir src -o .omx/state/out https://example.test/runtime.ts", "wget -P src https://example.test/runtime.ts", "wget --directory-prefix=src https://example.test/runtime.ts", "git rm src/runtime.ts", "git clean -fd src", ]; for (const command of blockedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-bash-mutations", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Bash (?:node|python) write target .*not workflow state\/ledger\/mailbox\/handoff metadata|Bash (?:curl|wget) (?:output|mutation) target .*not workflow state\/ledger\/mailbox\/handoff metadata|Bash git worktree mutation is not workflow state\/ledger\/mailbox\/handoff metadata|target /); } const allowedCommands = [ "node -e \"console.log('ok')\"", "python3 -c \"print('ok')\"", "python3 - <<'PY'\nimport shutil\nshutil.copyfile('a', '.omx/state/foo')\nPY", "curl -fsSL https://example.test/runtime.ts -o .omx/state/download.log", "curl -fsSL https://example.test/runtime.ts --output=.omx/state/download-inline.log", "curl -fsSL -O https://example.test/runtime.ts --output-dir .omx/state", "curl -fsSL -OL https://example.test/runtime.ts --output-dir .omx/state", "curl -fsSL --remote-name --output-dir=.omx/state https://example.test/runtime.ts", "wget -O .omx/state/download.log https://example.test/runtime.ts", "wget --output-document=.omx/state/download-inline.log https://example.test/runtime.ts", "curl --output-dir .omx/state -O https://example.test/runtime.ts", "curl --create-dirs --output-dir .omx/state -o out https://example.test/runtime.ts", "wget -P .omx/state https://example.test/runtime.ts", "wget --directory-prefix=.omx/state https://example.test/runtime.ts", ]; for (const command of allowedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-bash-mutations", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal(result.outputJson, null, command); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root team conductor writes from root team state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-root-team-conductor-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-root-team-conductor-write"; const teamName = "root-team-conductor-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeLiveNativeMappedSessionState(cwd, stateDir, sessionId, "native-root-team-conductor-write"); await writeSessionSkillActiveState(stateDir, sessionId, "team", "team-exec"); await writeJson(join(stateDir, "team-state.json"), { active: true, mode: "team", current_phase: "starting", team_name: teamName, session_id: sessionId, thread_id: "thread-root-team-conductor-write", }); await writeJson(join(stateDir, "team", teamName, "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-root-team-conductor-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.outputJson?.decision, "block"); assert.match(String(result.outputJson?.reason ?? ""), /team phase: team-exec/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows autopilot rework implementation writes while conductor phases stay guarded", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-rework-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-rework-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "rework"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "rework", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-rework-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Main-root ralph conductor source writes while allowing .omx workflow state writes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-ralph-conductor-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blocked = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(blocked.outputJson?.decision, "block"); assert.match(String(blocked.outputJson?.reason ?? ""), /ralph phase: executing/); const nativeMappedCwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-native-write-")); try { const nativeMappedStateDir = join(nativeMappedCwd, ".omx", "state"); const canonicalSessionId = "omx-canonical-ralph-conductor-write"; const nativeSessionId = "codex-native-ralph-conductor-write"; await writeNativeMappedSessionState(nativeMappedCwd, nativeMappedStateDir, canonicalSessionId, nativeSessionId); await writeSessionSkillActiveState(nativeMappedStateDir, canonicalSessionId, "ralph", "executing"); await writeJson(join(nativeMappedStateDir, "sessions", canonicalSessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: canonicalSessionId, }); const nativeMappedBlocked = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd: nativeMappedCwd, session_id: nativeSessionId, thread_id: "thread-native-ralph-conductor-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd: nativeMappedCwd }, ); assert.equal(nativeMappedBlocked.outputJson?.decision, "block"); assert.match(String(nativeMappedBlocked.outputJson?.reason ?? ""), /ralph phase: executing/); } finally { await rm(nativeMappedCwd, { recursive: true, force: true }); } const allowed = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-ralph-conductor-write", tool_name: "Write", tool_input: { file_path: ".omx/state/sessions/sess-ralph-conductor-write/ralph-state.json" }, }, { cwd }, ); assert.equal(allowed.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-bash-mutations"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const blockedCommands = [ "mv src/old.ts src/new.ts", "cp package.json src/package-copy.json", "touch src/generated.ts", "mkdir -p src/generated", "rm -f src/generated.ts", "chmod 600 src/runtime.ts", "sudo -n cp README.md src/readme-copy.md", "env cp package.json src/package-copy.json", "exec cp package.json src/package-copy.json", "env FOO=1 mv src/a.ts src/b.ts", "git reset --hard > .omx/state/reset.log", "npm install > .omx/state/install.log", "printf ok; cp package.json src/package-copy.json", "printf ok && mv src/a.ts src/b.ts", "printf ok\ncp package.json src/package-copy.json", "printf ok > .omx/state/log\nmv src/a src/b", "cp package.json --target-directory src/generated", "mv src/a.ts --target-directory=src/generated", "mv src/a.ts --target-directory=.omx/state", "install package.json -t src/generated", "install -d .omx/state src/generated", "ln package.json -t src/generated", "cp package.json --target-directory", "if true; then mv src/a.ts src/b.ts; fi", "(cp package.json src/package-copy.json)", "bash -lc \"mv src/old.ts src/new.ts\"", "sh -c 'cp package.json src/package-copy.json'", "echo $(cp package.json src/package-copy.json)", "echo `mv src/old.ts src/new.ts`", "bash -lc \"sed -i 's/old/new/' src/runtime.ts\"", "bash -lc \"perl -pi -e 's/old/new/' src/runtime.ts\"", "sed -Ei 's/old/new/' src/runtime.ts", "do_src_write() { cp package.json src/package-copy.json; }; do_src_write", "do_src_write() ( mv src/old.ts src/new.ts ); time do_src_write", "cat <(cp package.json src/package-copy.json)", "cat >(mv src/old.ts src/new.ts)", "cat > .omx/state/conductor.log </); } const allowedCommands = [ "touch .omx/state/conductor-ledger.json", "mkdir -p .omx/handoffs/run-1", "cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/conductor-ledger.json", "mv .omx/handoffs/run-1/conductor-ledger.json .omx/handoffs/run-1/ledger.json", "env cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/env-ledger.json", "exec cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/exec-ledger.json", "cat <<'EOF' > .omx/state/conductor-heredoc.json\n{}\nEOF", "printf safe > .omx/state/conductor.log", "printf one > .omx/state/one.log\nprintf two > .omx/handoffs/run-1/two.log", "touch .omx/state/line-one.json\ncp .omx/state/line-one.json .omx/handoffs/run-1/line-two.json", "bash -lc \"printf safe\"", "sh -c 'printf safe'", "cp .omx/state/conductor-ledger.json --target-directory .omx/handoffs/run-1", "mv .omx/state/conductor-ledger.json --target-directory=.omx/handoffs/run-1", "install .omx/state/conductor-ledger.json -t .omx/handoffs/run-1", "ln .omx/state/conductor-ledger.json -t .omx/handoffs/run-1", "if true; then cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/if-ledger.json; fi", "(cp .omx/state/conductor-ledger.json .omx/handoffs/run-1/subshell-ledger.json)", "sed -n '1,20p' src/runtime.ts", "perl -ne 'print' src/runtime.ts", "sed -i 's/old/new/' .omx/state/conductor-ledger.json", "perl -pi -e 's/old/new/' .omx/state/conductor-ledger.json", "sed -Ei 's/old/new/' .omx/state/conductor-ledger.json", "cp src/source.ts .omx/state/source-copy.ts", "install src/source.ts -t .omx/state", "ln src/source.ts -t .omx/handoffs/run-1", "curl --output-dir .omx/state -O https://example.test/archive.tgz", "curl -OL --output-dir .omx/state https://example.test/archive.tgz", "wget -P .omx/state https://example.test/archive.tgz", "curl --create-dirs --output-dir .omx/state -o out https://example.test/archive.tgz", "install -d .omx/state .omx/handoffs/run-1", "rsync README.md .omx/state/readme.md", "xargs env printf safe { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-sed-perl-metadata-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-conductor-sed-perl-metadata"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "ralph", "executing"); await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), { active: true, mode: "ralph", current_phase: "executing", session_id: sessionId, }); const allowedCommands = [ "sed -i 's/old/new/' .omx/state/conductor.log", "perl -pi -e 's/old/new/' .omx/state/conductor.log", "bash -lc \"sed -i 's/old/new/' .omx/state/conductor.log\"", "bash -lc \"perl -pi -e 's/old/new/' .omx/state/conductor.log\"", ]; for (const command of allowedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-sed-perl-metadata", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal(result.outputJson, null, command); } const blockedCommands = [ "sed -i 's/old/new/' src/runtime.ts", "perl -pi -e 's/old/new/' src/runtime.ts", "bash -lc \"sed -i 's/old/new/' src/runtime.ts\"", "bash -lc \"perl -pi -e 's/old/new/' src/runtime.ts\"", ]; for (const command of blockedCommands) { const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-conductor-sed-perl-metadata", tool_name: "Bash", tool_input: { command }, }, { cwd }, ); assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block", command); assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Bash .* target .*not workflow state\/ledger\/mailbox\/handoff metadata|target /); } } finally { await rm(cwd, { recursive: true, force: true }); } }); it("allows autopilot rework implementation writes while conductor phases stay guarded", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-rework-write-")); try { const stateDir = join(cwd, ".omx", "state"); const sessionId = "sess-autopilot-rework-write"; await mkdir(join(stateDir, "sessions", sessionId), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: sessionId }); await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "rework"); await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), { active: true, mode: "autopilot", current_phase: "rework", session_id: sessionId, }); const result = await dispatchCodexNativeHook( { hook_event_name: "PreToolUse", cwd, session_id: sessionId, thread_id: "thread-autopilot-rework-write", tool_name: "Edit", tool_input: { file_path: "src/runtime.ts" }, }, { cwd }, ); assert.equal(result.omxEventName, "pre-tool-use"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root team state without team_name when no session is known", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(stateDir, { recursive: true }); await writeJson(join(stateDir, "team-state.json"), { active: true, mode: "team", current_phase: "starting", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root team state without team_name for a foreign session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, mode: "team", current_phase: "starting", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "stale-root-thread-team", session_id: "sess-current", thread_id: "thread-other", }); await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root team state with matching session but missing thread ownership", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "root-missing-thread-team", session_id: "sess-current", }); await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from root team state when canonical phase is missing", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "root-missing-phase-team", session_id: "sess-current", thread_id: "thread-current", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from session-scoped team state owned by another thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), { active: true, current_phase: "starting", team_name: "scoped-other-thread-team", session_id: "sess-current", thread_id: "thread-other", }); await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("blocks Stop from session-scoped team state owned by the current session and thread", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), { active: true, current_phase: "starting", team_name: "scoped-current-team", session_id: "sess-current", thread_id: "thread-current", }); await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", thread_id: "thread-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (scoped-current-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-exec", systemMessage: "OMX team pipeline is still active at phase team-exec.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "stale-root-team", session_id: "sess-other", }); await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not block Stop from orphaned team mode state after cleanup removed canonical team artifacts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-orphaned-team-state-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "cleaned-team", session_id: "sess-current", }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("prefers the current session team state over a stale root team fallback during Stop", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-current-session-team-preferred-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), { active: true, current_phase: "starting", team_name: "current-team", session_id: "sess-current", }); await writeJson(join(stateDir, "team", "current-team", "phase.json"), { current_phase: "team-verify", max_fix_attempts: 3, current_fix_attempt: 1, transitions: [], updated_at: new Date().toISOString(), }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "stale-root-team", session_id: "sess-other", }); await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.deepEqual(result.outputJson, { decision: "block", reason: `OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`, stopReason: "team_team-verify", systemMessage: "OMX team pipeline is still active at phase team-verify.", }); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not fall back to active root team state when the current scoped team state is inactive", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-inactive-scoped-team-")); try { const stateDir = join(cwd, ".omx", "state"); await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true }); await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" }); await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), { active: false, current_phase: "complete", team_name: "scoped-finished-team", session_id: "sess-current", }); await writeJson(join(stateDir, "team-state.json"), { active: true, current_phase: "starting", team_name: "root-fallback-team", session_id: "sess-current", }); await writeJson(join(stateDir, "team", "root-fallback-team", "phase.json"), { current_phase: "team-exec", max_fix_attempts: 3, current_fix_attempt: 0, transitions: [], updated_at: new Date().toISOString(), }); const result = await dispatchCodexNativeHook( { hook_event_name: "Stop", cwd, session_id: "sess-current", }, { cwd }, ); assert.equal(result.omxEventName, "stop"); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); }); // --------------------------------------------------------------------------- // Triage layer integration tests // --------------------------------------------------------------------------- describe("codex native hook triage integration", () => { const priorCodexHome = process.env.CODEX_HOME; beforeEach(() => { resetTriageConfigCache(); }); afterEach(() => { if (typeof priorCodexHome === "string") process.env.CODEX_HOME = priorCodexHome; else delete process.env.CODEX_HOME; resetTriageConfigCache(); }); // ── Group 1: Keyword bypass (triage must NOT run) ──────────────────────── it("does not inject triage advisory for $ralplan keyword prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-ralplan-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-kw-ralplan-1", thread_id: "thread-triage-kw-1", turn_id: "turn-triage-kw-1", prompt: "$ralplan implement issue #1307", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); assert.doesNotMatch(additionalContext, /read-only\/question-shaped/); assert.doesNotMatch(additionalContext, /narrow edit-shaped/); assert.doesNotMatch(additionalContext, /visual\/style request/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-kw-ralplan-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-")); const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-")); const originalOmxRoot = process.env.OMX_ROOT; const originalOmxStateRoot = process.env.OMX_STATE_ROOT; const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT; try { process.env.OMX_ROOT = boxedRoot; delete process.env.OMX_STATE_ROOT; delete process.env.OMX_TEAM_STATE_ROOT; const boxedStateDir = getBaseStateDir(cwd); await mkdir(boxedStateDir, { recursive: true }); await writeJson(join(boxedStateDir, "subagent-tracking.json"), { schemaVersion: 1, sessions: { "omx-parent-session": { session_id: "omx-parent-session", leader_thread_id: "parent-native-thread", updated_at: "2026-05-21T19:04:40.000Z", threads: { "parent-native-thread": { thread_id: "parent-native-thread", kind: "leader", first_seen_at: "2026-05-21T19:04:40.000Z", last_seen_at: "2026-05-21T19:04:40.000Z", turn_count: 1, }, "child-native-session": { thread_id: "child-native-session", kind: "subagent", first_seen_at: "2026-05-21T19:04:41.000Z", last_seen_at: "2026-05-21T19:04:41.000Z", turn_count: 1, mode: "review", }, }, }, }, }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "child-native-session", thread_id: "child-native-session", turn_id: "turn-subagent-review", prompt: [ "Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.", "Context: The user asked for $autopilot, and this subagent must only review the patch.", ].join("\n\n"), }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.equal(additionalContext, ""); assert.equal( existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")), false, ); assert.equal( existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")), false, ); assert.equal( existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")), false, "subagent tracking must not leak into the source worktree when OMX_ROOT is boxed", ); } finally { if (originalOmxRoot === undefined) delete process.env.OMX_ROOT; else process.env.OMX_ROOT = originalOmxRoot; if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT; else process.env.OMX_STATE_ROOT = originalOmxStateRoot; if (originalTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT; else process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot; await rm(cwd, { recursive: true, force: true }); await rm(boxedRoot, { recursive: true, force: true }); } }); it("does not inject triage advisory for autopilot keyword prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-kw-autopilot-1", thread_id: "thread-triage-kw-ap-1", turn_id: "turn-triage-kw-ap-1", prompt: "$autopilot build this", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); assert.doesNotMatch(additionalContext, /read-only\/question-shaped/); assert.doesNotMatch(additionalContext, /narrow edit-shaped/); assert.doesNotMatch(additionalContext, /visual\/style request/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-kw-autopilot-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeSessionStart(cwd, "sess-autopilot-observable"); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-autopilot-observable", thread_id: "thread-autopilot-observable", turn_id: "turn-autopilot-observable", prompt: "$autopilot implement issue #2430", }, { cwd }, ); assert.equal(result.skillState?.skill, "autopilot"); assert.equal(result.skillState?.phase, "deep-interview"); assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/); assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/); assert.match(additionalContext, /deep_interview_gate\.skip_reason/); assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/); assert.match(additionalContext, /Codex goal-mode handoff guidance/); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json"); const modeState = JSON.parse(await readFile(statePath, "utf-8")) as { active: boolean; current_phase: string; state?: { phase_cycle?: string[]; deep_interview_gate?: { status?: string; skip_reason?: string | null } }; }; assert.equal(modeState.active, true); assert.equal(modeState.current_phase, "deep-interview"); assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]); assert.deepEqual(modeState.state?.deep_interview_gate, { status: "required", skip_reason: null, rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.", }); const hudState = await readAllState(cwd); assert.equal(hudState.autopilot?.active, true); assert.equal(hudState.autopilot?.current_phase, "deep-interview"); assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("omits Team handoff guidance from autopilot prompt context when Team mode is disabled", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-no-team-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeJson(join(cwd, ".omx", "setup-scope.json"), { scope: "project", teamMode: "disabled", }); await writeSessionStart(cwd, "sess-autopilot-observable-no-team"); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-autopilot-observable-no-team", thread_id: "thread-autopilot-observable-no-team", turn_id: "turn-autopilot-observable-no-team", prompt: "$autopilot implement issue #2430", }, { cwd }, ); assert.equal(result.skillState?.skill, "autopilot"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/); assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal -> \$code-review -> \$ultraqa/); assert.doesNotMatch(additionalContext, /\$team/); assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("ignores disabled $team before outside-tmux Team blocking so later workflows can activate", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-disabled-team-primary-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeJson(join(cwd, ".omx", "setup-scope.json"), { scope: "project", teamMode: "disabled", }); await writeSessionStart(cwd, "sess-disabled-team-primary"); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-disabled-team-primary", thread_id: "thread-disabled-team-primary", turn_id: "turn-disabled-team-primary", prompt: "$team $ralph fix this", }, { cwd }, ); assert.equal(result.skillState?.skill, "ralph"); assert.equal(result.skillState?.transition_error, undefined); assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", "sess-disabled-team-primary", "ralph-state.json")), true, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /detected workflow keyword "\$ralph" -> ralph/); assert.doesNotMatch(additionalContext, /Codex App\/native outside-tmux sessions cannot activate/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("makes bare autopilot command activation observable in state and prompt guidance", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-bare-observable-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await writeSessionStart(cwd, "sess-autopilot-bare-observable"); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-autopilot-bare-observable", thread_id: "thread-autopilot-bare-observable", turn_id: "turn-autopilot-bare-observable", prompt: "run autopilot", }, { cwd }, ); assert.equal(result.skillState?.skill, "autopilot"); assert.equal(result.skillState?.phase, "deep-interview"); assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-bare-observable/autopilot-state.json"); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /detected workflow keyword "autopilot" -> autopilot/); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-bare-observable", "autopilot-state.json"); const modeState = JSON.parse(await readFile(statePath, "utf-8")) as { active: boolean; current_phase: string; }; assert.equal(modeState.active, true); assert.equal(modeState.current_phase, "deep-interview"); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 2: HEAVY injection ───────────────────────────────────────────── it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-heavy-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-heavy-1", thread_id: "thread-triage-heavy-1", turn_id: "turn-triage-heavy-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /multi-step goal with no workflow keyword/); assert.match(additionalContext, /Prefer the existing autopilot-style workflow/); // skill-active-state.json must NOT be written (triage is advisory only) assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); // prompt-routing-state.json must be written with lane=HEAVY const stateFile = join(cwd, ".omx", "state", "sessions", "triage-heavy-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { version?: number; last_triage?: { lane?: string; destination?: string }; suppress_followup?: boolean; }; assert.equal(state.version, 1); assert.equal(state.last_triage?.lane, "HEAVY"); assert.equal(state.last_triage?.destination, "autopilot"); assert.equal(state.suppress_followup, true); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 3: LIGHT/explore ──────────────────────────────────────────────── it("injects LIGHT/explore advisory and writes state for a question-shaped prompt", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-light-explore-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-explore-1", thread_id: "thread-triage-explore-1", turn_id: "turn-triage-explore-1", prompt: "explain this function", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /read-only\/question-shaped/); assert.match(additionalContext, /Prefer the explore role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-explore-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; suppress_followup?: boolean; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "explore"); assert.equal(state.suppress_followup, true); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 4: LIGHT/executor ─────────────────────────────────────────────── it("injects LIGHT/executor advisory and writes state for a narrow edit-shaped prompt", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-light-executor-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-executor-1", thread_id: "thread-triage-executor-1", turn_id: "turn-triage-executor-1", prompt: "fix typo in src/foo.ts", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /narrow edit-shaped/); assert.match(additionalContext, /Prefer the executor role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-executor-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "executor"); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 5: LIGHT/designer ─────────────────────────────────────────────── it("injects LIGHT/designer advisory and writes state for a visual/style prompt", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-light-designer-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-designer-1", thread_id: "thread-triage-designer-1", turn_id: "turn-triage-designer-1", prompt: "make the button blue", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /visual\/style request/); assert.match(additionalContext, /Prefer the designer role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-designer-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "designer"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("injects LIGHT/researcher advisory and writes state for an official-doc lookup prompt", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-light-researcher-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-researcher-1", thread_id: "thread-triage-researcher-1", turn_id: "turn-triage-researcher-1", prompt: "Find the official docs and version compatibility notes for this SDK", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /external documentation\/reference research request/); assert.match(additionalContext, /Prefer the researcher role surface/); assert.doesNotMatch(additionalContext, /skill: researcher activated/); assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-researcher-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; suppress_followup?: boolean; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); assert.equal(state.last_triage?.reason, "external_reference_research"); assert.equal(state.suppress_followup, true); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes Korean external lookup phrasing to researcher without treating it as workflow activation", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-light-researcher-ko-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-researcher-ko-1", thread_id: "thread-triage-researcher-ko-1", turn_id: "turn-triage-researcher-ko-1", prompt: "OpenAI Responses API 공식 문서 찾아줘", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /Prefer the researcher role surface/); assert.equal(result.skillState, null); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-researcher-ko-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes official-doc question prompts to researcher instead of explore", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-question-researcher-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-question-researcher-1", thread_id: "thread-triage-question-researcher-1", turn_id: "turn-triage-question-researcher-1", prompt: "where can I find official docs for OpenAI Responses API?", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the explore role surface/); assert.match(additionalContext, /Prefer the researcher role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-question-researcher-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); assert.equal(state.last_triage?.reason, "external_reference_research"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes endpoint-shaped official-doc lookups to researcher instead of local explore", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-endpoint-researcher-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-endpoint-researcher-1", thread_id: "thread-triage-endpoint-researcher-1", turn_id: "turn-triage-endpoint-researcher-1", prompt: "find official docs for api/v1/responses", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the explore role surface/); assert.match(additionalContext, /Prefer the researcher role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-endpoint-researcher-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); assert.equal(state.last_triage?.reason, "external_reference_research"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes dotted technology official-doc lookups to researcher instead of local explore", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-dotted-tech-researcher-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-dotted-tech-researcher-1", thread_id: "thread-triage-dotted-tech-researcher-1", turn_id: "turn-triage-dotted-tech-researcher-1", prompt: "find official docs for Node.js", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the explore role surface/); assert.match(additionalContext, /Prefer the researcher role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-dotted-tech-researcher-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); assert.equal(state.last_triage?.reason, "external_reference_research"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes URL-shaped official-doc lookups with repo paths to researcher instead of local routes", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-url-path-researcher-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-url-path-researcher-1", thread_id: "thread-triage-url-path-researcher-1", turn_id: "turn-triage-url-path-researcher-1", prompt: "find official docs for github.com/org/repo/src/foo.ts", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the executor role surface/); assert.doesNotMatch(additionalContext, /Prefer the explore role surface/); assert.match(additionalContext, /Prefer the researcher role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-url-path-researcher-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "researcher"); assert.equal(state.last_triage?.reason, "external_reference_research"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps implementation-shaped official-doc prompts on HEAVY instead of researcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-researcher-implementation-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-researcher-implementation-1", thread_id: "thread-triage-researcher-implementation-1", turn_id: "turn-triage-researcher-implementation-1", prompt: "implement auth using official docs for the SDK", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /multi-step goal with no workflow keyword/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-researcher-implementation-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "HEAVY"); assert.equal(state.last_triage?.destination, "autopilot"); assert.equal(state.last_triage?.reason, "implementation_research_goal"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps planning-shaped official-doc prompts on HEAVY instead of researcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-researcher-planning-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-researcher-planning-1", thread_id: "thread-triage-researcher-planning-1", turn_id: "turn-triage-researcher-planning-1", prompt: "research and plan auth migration using official docs for the SDK", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /multi-step goal with no workflow keyword/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-researcher-planning-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "HEAVY"); assert.equal(state.last_triage?.destination, "autopilot"); assert.equal(state.last_triage?.reason, "implementation_research_goal"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps local source lookup prompts off researcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-local-source-explore-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-local-source-1", thread_id: "thread-triage-local-source-1", turn_id: "turn-triage-local-source-1", prompt: "search source for parseConfig in src/config.ts", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /Prefer the executor role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-local-source-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "executor"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps anchored local API usage prompts on executor instead of researcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-local-api-executor-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-local-api-1", thread_id: "thread-triage-local-api-1", turn_id: "turn-triage-local-api-1", prompt: "find API usage in src/foo.ts", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /Prefer the executor role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-local-api-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "executor"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps project-scoped local API usage prompts on explore instead of researcher", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-project-api-explore-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-project-api-1", thread_id: "thread-triage-project-api-1", turn_id: "turn-triage-project-api-1", prompt: "find API usage in this project", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /Prefer the explore role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-project-api-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "explore"); assert.equal(state.last_triage?.reason, "local_reference_lookup"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("keeps repository changelog lookup prompts on explore despite generic docs terms", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-repo-changelog-explore-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-repo-changelog-1", thread_id: "thread-triage-repo-changelog-1", turn_id: "turn-triage-repo-changelog-1", prompt: "find changelog in this repository", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the researcher role surface/); assert.match(additionalContext, /Prefer the explore role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-repo-changelog-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "explore"); assert.equal(state.last_triage?.reason, "local_reference_lookup"); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("routes anchored read-only questions through explore before executor", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-anchored-question-explore-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-anchored-question-1", thread_id: "thread-triage-anchored-question-1", turn_id: "turn-triage-anchored-question-1", prompt: "what does src/foo.ts do?", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /Prefer the executor role surface/); assert.match(additionalContext, /Prefer the explore role surface/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-anchored-question-1", "prompt-routing-state.json"); const state = JSON.parse(await readFile(stateFile, "utf-8")) as { last_triage?: { lane?: string; destination?: string; reason?: string }; }; assert.equal(state.last_triage?.lane, "LIGHT"); assert.equal(state.last_triage?.destination, "explore"); assert.equal(state.last_triage?.reason, "question_or_explanation"); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 6: PASS (no triage injection, no state) ──────────────────────── it("produces no triage advisory and no state for trivial greeting prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-pass-hello-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-pass-hello-1", thread_id: "thread-triage-pass-1", turn_id: "turn-triage-pass-1", prompt: "hello", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); assert.doesNotMatch(additionalContext, /read-only\/question-shaped/); assert.doesNotMatch(additionalContext, /narrow edit-shaped/); assert.doesNotMatch(additionalContext, /visual\/style request/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-pass-hello-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("produces no triage advisory and no state for ambiguous short prompts", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-pass-short-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-pass-short-1", thread_id: "thread-triage-pass-short-1", turn_id: "turn-triage-pass-short-1", prompt: "fix the thing", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); assert.doesNotMatch(additionalContext, /read-only\/question-shaped/); assert.doesNotMatch(additionalContext, /narrow edit-shaped/); assert.doesNotMatch(additionalContext, /visual\/style request/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-pass-short-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 7: Turn-2 suppression (same session across two invocations) ──── it("suppresses HEAVY triage re-injection on a short follow-up in the same session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-suppress-heavy-")); const sessionId = "triage-suppress-heavy-1"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); // Turn 1: HEAVY fires const turn1 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-suppress-heavy-1", turn_id: "turn-suppress-heavy-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const ctx1 = String( (turn1.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(ctx1, /multi-step goal with no workflow keyword/); // Turn 2: short follow-up — triage suppressed const turn2 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-suppress-heavy-1", turn_id: "turn-suppress-heavy-2", prompt: "yes, settings page", }, { cwd }, ); const ctx2 = String( (turn2.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(ctx2, /multi-step goal/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("suppresses LIGHT/explore triage re-injection on a short follow-up in the same session", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-suppress-explore-")); const sessionId = "triage-suppress-explore-1"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); // Turn 1: LIGHT/explore fires await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-suppress-explore-1", turn_id: "turn-suppress-explore-1", prompt: "explain this function", }, { cwd }, ); // Turn 2: short follow-up — no duplicate LIGHT injection const turn2 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-suppress-explore-1", turn_id: "turn-suppress-explore-2", prompt: "the auth helper", }, { cwd }, ); const ctx2 = String( (turn2.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(ctx2, /read-only\/question-shaped/); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 8: First-turn PASS does NOT block later triage ───────────────── it("still applies triage on turn 2 when turn 1 was a PASS with no state written", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-pass-then-light-")); const sessionId = "triage-pass-then-light-1"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); // Turn 1: PASS — no state written await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-pass-then-light-1", turn_id: "turn-pass-then-light-1", prompt: "hello", }, { cwd }, ); assert.equal( existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "prompt-routing-state.json")), false, ); // Turn 2: LIGHT/executor should fire normally const turn2 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-pass-then-light-1", turn_id: "turn-pass-then-light-2", prompt: "fix typo in src/foo.ts", }, { cwd }, ); const ctx2 = String( (turn2.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(ctx2, /narrow edit-shaped/); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 9: Opt-out forces PASS ───────────────────────────────────────── it("produces no triage advisory when prompt contains 'just chat' opt-out", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-optout-chat-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-optout-chat-1", thread_id: "thread-optout-chat-1", turn_id: "turn-optout-chat-1", prompt: "add dark mode toggle to the settings page, but just chat about it", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); assert.doesNotMatch(additionalContext, /read-only\/question-shaped/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-optout-chat-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("produces no triage advisory when prompt contains 'no workflow' opt-out", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-optout-noworkflow-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-optout-noworkflow-1", thread_id: "thread-optout-noworkflow-1", turn_id: "turn-optout-noworkflow-1", prompt: "make the button blue, no workflow", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /visual\/style request/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-optout-noworkflow-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 10: Keyword on follow-up turn wins cleanly ───────────────────── it("keyword on turn 2 suppresses triage and writes no triage state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-kw-followup-")); const sessionId = "triage-kw-followup-1"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); // Turn 1: neutral prompt — triage may or may not fire, doesn't matter await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-kw-followup-1", turn_id: "turn-kw-followup-1", prompt: "hello", }, { cwd }, ); // Turn 2: keyword prompt — keyword fast-path runs, triage does NOT add extra advisory const turn2 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-kw-followup-1", turn_id: "turn-kw-followup-2", prompt: "$ralph continue", }, { cwd }, ); assert.equal(turn2.skillState?.skill, "ralph"); const ctx2 = String( (turn2.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(ctx2, /multi-step goal with no workflow keyword/); assert.doesNotMatch(ctx2, /read-only\/question-shaped/); assert.doesNotMatch(ctx2, /narrow edit-shaped/); assert.doesNotMatch(ctx2, /visual\/style request/); // No triage state written on the keyword turn const triageState = join(cwd, ".omx", "state", "sessions", sessionId, "prompt-routing-state.json"); // The state from turn 1 (if any) must not have been created either (hello = PASS) assert.equal(existsSync(triageState), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); // ── Group 11: Config-disabled path ─────────────────────────────────────── it("produces no triage advisory and no state when triage is disabled in config", async () => { const tmpHome = await mkdtemp(join(tmpdir(), "omx-triage-config-disabled-home-")); const cwd = await mkdtemp(join(tmpdir(), "omx-triage-config-disabled-cwd-")); try { // Write a .omx-config.json in the fake CODEX_HOME that disables triage await writeJson(join(tmpHome, ".omx-config.json"), { promptRouting: { triage: { enabled: false } }, }); process.env.CODEX_HOME = tmpHome; resetTriageConfigCache(); await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-disabled-1", thread_id: "thread-triage-disabled-1", turn_id: "turn-triage-disabled-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-disabled-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), false); } finally { await rm(tmpHome, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true }); } }); it("keeps triage default-enabled when config omits promptRouting.triage.enabled", async () => { const tmpHome = await mkdtemp(join(tmpdir(), "omx-triage-config-omitted-home-")); const cwd = await mkdtemp(join(tmpdir(), "omx-triage-config-omitted-cwd-")); const previousCodexHome = process.env.CODEX_HOME; try { await writeJson(join(tmpHome, ".omx-config.json"), { promptRouting: {}, }); process.env.CODEX_HOME = tmpHome; resetTriageConfigCache(); await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "triage-defaulted-1", thread_id: "thread-triage-defaulted-1", turn_id: "turn-triage-defaulted-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /multi-step goal with no workflow keyword/); const stateFile = join(cwd, ".omx", "state", "sessions", "triage-defaulted-1", "prompt-routing-state.json"); assert.equal(existsSync(stateFile), true); } finally { if (typeof previousCodexHome === "string") process.env.CODEX_HOME = previousCodexHome; else delete process.env.CODEX_HOME; resetTriageConfigCache(); await rm(tmpHome, { recursive: true, force: true }); await rm(cwd, { recursive: true, force: true }); } }); it("does not suppress a short anchored follow-up that is a new request", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-short-new-request-")); const sessionId = "triage-short-new-request-1"; try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-short-new-request-1", turn_id: "turn-short-new-request-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const turn2 = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: sessionId, thread_id: "thread-short-new-request-1", turn_id: "turn-short-new-request-2", prompt: "fix typo in src/foo.ts", }, { cwd }, ); const ctx2 = String( (turn2.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(ctx2, /narrow edit-shaped/); } finally { await rm(cwd, { recursive: true, force: true }); } }); it("skips triage state persistence for malformed explicit session ids without writing root state", async () => { const cwd = await mkdtemp(join(tmpdir(), "omx-triage-invalid-session-")); try { await mkdir(join(cwd, ".omx", "state"), { recursive: true }); const result = await dispatchCodexNativeHook( { hook_event_name: "UserPromptSubmit", cwd, session_id: "bad/session", thread_id: "thread-triage-invalid-session-1", turn_id: "turn-triage-invalid-session-1", prompt: "add dark mode toggle to the settings page", }, { cwd }, ); const additionalContext = String( (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "", ); assert.match(additionalContext, /multi-step goal with no workflow keyword/); assert.equal(existsSync(join(cwd, ".omx", "state", "prompt-routing-state.json")), false); } finally { await rm(cwd, { recursive: true, force: true }); } }); }); describe('native Stop autopilot deep-interview wait', () => { it('does not force continued execution while autopilot is waiting on a deep-interview omx question', async () => { const cwd = await mkdtemp(join(tmpdir(), 'omx-native-hook-autopilot-question-wait-')); try { const sessionId = 'sess-autopilot-wait'; const sessionDir = join(cwd, '.omx', 'state', 'sessions', sessionId); await writeJson(join(cwd, '.omx', 'state', 'session.json'), { session_id: sessionId }); await writeJson(join(sessionDir, 'autopilot-state.json'), { mode: 'autopilot', active: true, current_phase: 'waiting-for-user', run_outcome: 'blocked_on_user', lifecycle_outcome: 'askuserQuestion', session_id: sessionId, state: { deep_interview_question: { status: 'waiting_for_user', source: 'omx-question', obligation_id: 'obligation-stop-1', previous_phase: 'deep-interview', }, }, }); await writeJson(join(sessionDir, 'deep-interview-state.json'), { mode: 'deep-interview', active: false, current_phase: 'intent-first', lifecycle_outcome: 'askuserQuestion', run_outcome: 'blocked_on_user', session_id: sessionId, question_enforcement: { obligation_id: 'obligation-stop-1', source: 'omx-question', status: 'pending', lifecycle_outcome: 'askuserQuestion', requested_at: '2026-04-19T00:00:00.000Z', }, }); await writeJson(join(sessionDir, 'skill-active-state.json'), { active: true, skill: 'autopilot', phase: 'deep-interview', session_id: sessionId, active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }], }); const result = await dispatchCodexNativeHook({ hook_event_name: 'Stop', session_id: sessionId, thread_id: 'thread-autopilot-wait', }, { cwd }); assert.equal(result.outputJson, null); } finally { await rm(cwd, { recursive: true, force: true }); } }); });