oh-my-codex 0.18.1 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +4 -2
- package/dist/agents/__tests__/definitions.test.js +14 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +19 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +30 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +1 -0
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +4 -0
- package/dist/agents/native-config.js.map +1 -1
- package/dist/catalog/__tests__/generator.test.js +4 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +61 -5
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +161 -21
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +51 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +2 -2
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +178 -7
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +143 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +3 -3
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +1 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +2 -4
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +14 -0
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +100 -1
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +3 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +45 -2
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +17 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +170 -15
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
- package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
- package/dist/hooks/keyword-detector.d.ts +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +28 -6
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +11 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +22 -0
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +121 -10
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +84 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +51 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +69 -23
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/index.d.ts +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +8 -3
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +6 -3
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +26 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts +2 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +62 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +10 -3
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +59 -10
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +22 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +63 -1
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +410 -4
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +29 -2
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +41 -6
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +43 -10
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/ui.d.ts +12 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +83 -46
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +200 -10
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -0
- package/dist/ralplan/consensus-gate.js +212 -0
- package/dist/ralplan/consensus-gate.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +25 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +144 -8
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +626 -7
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
- package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
- package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +57 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +2 -2
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +214 -34
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +188 -4
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/run-test-files.js +13 -0
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +6 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +7 -0
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +4 -3
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +36 -44
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +58 -18
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +10 -20
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +15 -6
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +50 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +28 -2
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +16 -4
- package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
- package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +18 -3
- package/prompts/prometheus-strict-metis.md +274 -0
- package/prompts/prometheus-strict-momus.md +82 -0
- package/prompts/prometheus-strict-oracle.md +107 -0
- package/prompts/researcher.md +22 -3
- package/skills/autopilot/SKILL.md +16 -4
- package/skills/autoresearch/SKILL.md +4 -0
- package/skills/autoresearch-goal/SKILL.md +1 -1
- package/skills/best-practice-research/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/prometheus-strict/README.md +35 -0
- package/skills/prometheus-strict/SKILL.md +219 -0
- package/skills/ralplan/SKILL.md +18 -3
- package/src/scripts/__tests__/codex-native-hook.test.ts +769 -8
- package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
- package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
- package/src/scripts/__tests__/run-test-files.test.ts +67 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +2 -2
- package/src/scripts/codex-native-hook.ts +237 -30
- package/src/scripts/notify-dispatcher.ts +202 -4
- package/src/scripts/run-test-files.ts +13 -0
- package/templates/catalog-manifest.json +22 -0
|
@@ -26,9 +26,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
|
26
26
|
import { executeStateOperation } from "../../state/operations.js";
|
|
27
27
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
28
28
|
import { readAllState } from "../../hud/state.js";
|
|
29
|
+
import { renderHud } from "../../hud/render.js";
|
|
29
30
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
30
31
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
31
32
|
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
33
|
+
import { getBaseStateDir } from "../../state/paths.js";
|
|
32
34
|
|
|
33
35
|
function nativeHookScriptPath(): string {
|
|
34
36
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
@@ -285,7 +287,7 @@ describe("codex native hook config", () => {
|
|
|
285
287
|
matcher?: string;
|
|
286
288
|
hooks?: Array<Record<string, unknown>>;
|
|
287
289
|
};
|
|
288
|
-
assert.equal(preToolUse.matcher,
|
|
290
|
+
assert.equal(preToolUse.matcher, undefined);
|
|
289
291
|
assert.match(
|
|
290
292
|
String(preToolUse.hooks?.[0]?.command || ""),
|
|
291
293
|
/codex-native-hook\.js"?$/,
|
|
@@ -1937,6 +1939,9 @@ describe("codex native hook dispatch", () => {
|
|
|
1937
1939
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1938
1940
|
assert.match(output, /--status blocked/);
|
|
1939
1941
|
assert.match(output, /Codex goal context/);
|
|
1942
|
+
assert.match(output, /no such table: thread_goals/);
|
|
1943
|
+
assert.match(output, /unavailable get_goal error JSON or path/);
|
|
1944
|
+
assert.match(output, /safe-recovery blocker/);
|
|
1940
1945
|
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1941
1946
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1942
1947
|
} finally {
|
|
@@ -2233,6 +2238,36 @@ describe("codex native hook dispatch", () => {
|
|
|
2233
2238
|
}
|
|
2234
2239
|
});
|
|
2235
2240
|
|
|
2241
|
+
it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
|
|
2242
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
|
|
2243
|
+
try {
|
|
2244
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2245
|
+
const result = await dispatchCodexNativeHook(
|
|
2246
|
+
{
|
|
2247
|
+
hook_event_name: "UserPromptSubmit",
|
|
2248
|
+
cwd,
|
|
2249
|
+
session_id: "sess-autopilot-ralplan-gate",
|
|
2250
|
+
thread_id: "thread-autopilot-ralplan-gate",
|
|
2251
|
+
turn_id: "turn-autopilot-ralplan-gate",
|
|
2252
|
+
prompt: "$autopilot implement issue #2430",
|
|
2253
|
+
},
|
|
2254
|
+
{ cwd },
|
|
2255
|
+
);
|
|
2256
|
+
|
|
2257
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2258
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
2259
|
+
const message = String(
|
|
2260
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2261
|
+
);
|
|
2262
|
+
assert.match(message, /Autopilot protocol:/);
|
|
2263
|
+
assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
|
|
2264
|
+
assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
|
|
2265
|
+
assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
|
|
2266
|
+
} finally {
|
|
2267
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2236
2271
|
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
2237
2272
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
2238
2273
|
try {
|
|
@@ -2251,7 +2286,11 @@ describe("codex native hook dispatch", () => {
|
|
|
2251
2286
|
|
|
2252
2287
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
2253
2288
|
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2254
|
-
assert.equal(result.skillState?.initialized_mode,
|
|
2289
|
+
assert.equal(result.skillState?.initialized_mode, "ultragoal");
|
|
2290
|
+
assert.equal(
|
|
2291
|
+
result.skillState?.initialized_state_path,
|
|
2292
|
+
".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json",
|
|
2293
|
+
);
|
|
2255
2294
|
const message = String(
|
|
2256
2295
|
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2257
2296
|
);
|
|
@@ -2262,7 +2301,79 @@ describe("codex native hook dispatch", () => {
|
|
|
2262
2301
|
assert.match(message, /update_goal/);
|
|
2263
2302
|
assert.match(message, /does not call `\/goal clear`/);
|
|
2264
2303
|
assert.match(message, /multiple sequential ultragoal runs/);
|
|
2265
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")),
|
|
2304
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
|
|
2305
|
+
} finally {
|
|
2306
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
|
|
2311
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
|
|
2312
|
+
try {
|
|
2313
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2314
|
+
const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
|
|
2315
|
+
await mkdir(sessionDir, { recursive: true });
|
|
2316
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
2317
|
+
version: 1,
|
|
2318
|
+
active: true,
|
|
2319
|
+
skill: "deep-interview",
|
|
2320
|
+
phase: "planning",
|
|
2321
|
+
session_id: "sess-ultragoal-handoff",
|
|
2322
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
|
|
2323
|
+
});
|
|
2324
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
2325
|
+
active: true,
|
|
2326
|
+
mode: "deep-interview",
|
|
2327
|
+
current_phase: "intent-first",
|
|
2328
|
+
session_id: "sess-ultragoal-handoff",
|
|
2329
|
+
question_enforcement: {
|
|
2330
|
+
obligation_id: "obligation-ultragoal-handoff",
|
|
2331
|
+
source: "omx-question",
|
|
2332
|
+
status: "pending",
|
|
2333
|
+
requested_at: "2026-05-21T03:00:00.000Z",
|
|
2334
|
+
},
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
const result = await dispatchCodexNativeHook(
|
|
2338
|
+
{
|
|
2339
|
+
hook_event_name: "UserPromptSubmit",
|
|
2340
|
+
cwd,
|
|
2341
|
+
session_id: "sess-ultragoal-handoff",
|
|
2342
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2343
|
+
turn_id: "turn-ultragoal-handoff",
|
|
2344
|
+
prompt: "$ultragoal turn the clarified spec into goals",
|
|
2345
|
+
},
|
|
2346
|
+
{ cwd },
|
|
2347
|
+
);
|
|
2348
|
+
|
|
2349
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2350
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2351
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
|
|
2352
|
+
|
|
2353
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as {
|
|
2354
|
+
active?: boolean;
|
|
2355
|
+
current_phase?: string;
|
|
2356
|
+
question_enforcement?: { status?: string; clear_reason?: string };
|
|
2357
|
+
};
|
|
2358
|
+
assert.equal(completed.active, false);
|
|
2359
|
+
assert.equal(completed.current_phase, "completed");
|
|
2360
|
+
assert.equal(completed.question_enforcement?.status, "cleared");
|
|
2361
|
+
assert.equal(completed.question_enforcement?.clear_reason, "handoff");
|
|
2362
|
+
assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
|
|
2363
|
+
|
|
2364
|
+
const edit = await dispatchCodexNativeHook(
|
|
2365
|
+
{
|
|
2366
|
+
hook_event_name: "PreToolUse",
|
|
2367
|
+
cwd,
|
|
2368
|
+
session_id: "sess-ultragoal-handoff",
|
|
2369
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2370
|
+
tool_name: "Edit",
|
|
2371
|
+
tool_use_id: "tool-ultragoal-post-handoff-edit",
|
|
2372
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
2373
|
+
},
|
|
2374
|
+
{ cwd },
|
|
2375
|
+
);
|
|
2376
|
+
assert.equal(edit.outputJson, null);
|
|
2266
2377
|
} finally {
|
|
2267
2378
|
await rm(cwd, { recursive: true, force: true });
|
|
2268
2379
|
}
|
|
@@ -3889,6 +4000,183 @@ exit 0
|
|
|
3889
4000
|
}
|
|
3890
4001
|
});
|
|
3891
4002
|
|
|
4003
|
+
it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
|
|
4004
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
|
|
4005
|
+
try {
|
|
4006
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4007
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
|
|
4008
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4009
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
|
|
4010
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4011
|
+
version: 1,
|
|
4012
|
+
active: true,
|
|
4013
|
+
skill: "deep-interview",
|
|
4014
|
+
phase: "planning",
|
|
4015
|
+
session_id: "sess-di-edit-block",
|
|
4016
|
+
thread_id: "thread-di-edit-block",
|
|
4017
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
|
|
4018
|
+
});
|
|
4019
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4020
|
+
active: true,
|
|
4021
|
+
mode: "deep-interview",
|
|
4022
|
+
current_phase: "intent-first",
|
|
4023
|
+
session_id: "sess-di-edit-block",
|
|
4024
|
+
thread_id: "thread-di-edit-block",
|
|
4025
|
+
rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
|
|
4026
|
+
});
|
|
4027
|
+
|
|
4028
|
+
const result = await dispatchCodexNativeHook(
|
|
4029
|
+
{
|
|
4030
|
+
hook_event_name: "PreToolUse",
|
|
4031
|
+
cwd,
|
|
4032
|
+
session_id: "sess-di-edit-block",
|
|
4033
|
+
thread_id: "thread-di-edit-block",
|
|
4034
|
+
tool_name: "Edit",
|
|
4035
|
+
tool_use_id: "tool-di-edit-block",
|
|
4036
|
+
tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
|
|
4037
|
+
},
|
|
4038
|
+
{ cwd },
|
|
4039
|
+
);
|
|
4040
|
+
|
|
4041
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4042
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4043
|
+
assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Deep-interview is active/);
|
|
4044
|
+
assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
|
|
4045
|
+
assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
|
|
4046
|
+
} finally {
|
|
4047
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4048
|
+
}
|
|
4049
|
+
});
|
|
4050
|
+
|
|
4051
|
+
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
4052
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
4053
|
+
try {
|
|
4054
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4055
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
|
|
4056
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4057
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
|
|
4058
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4059
|
+
version: 1,
|
|
4060
|
+
active: true,
|
|
4061
|
+
skill: "deep-interview",
|
|
4062
|
+
phase: "planning",
|
|
4063
|
+
session_id: "sess-di-artifact",
|
|
4064
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
|
|
4065
|
+
});
|
|
4066
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4067
|
+
active: true,
|
|
4068
|
+
mode: "deep-interview",
|
|
4069
|
+
current_phase: "intent-first",
|
|
4070
|
+
session_id: "sess-di-artifact",
|
|
4071
|
+
});
|
|
4072
|
+
|
|
4073
|
+
const allowedWrite = await dispatchCodexNativeHook(
|
|
4074
|
+
{
|
|
4075
|
+
hook_event_name: "PreToolUse",
|
|
4076
|
+
cwd,
|
|
4077
|
+
session_id: "sess-di-artifact",
|
|
4078
|
+
tool_name: "Write",
|
|
4079
|
+
tool_use_id: "tool-di-spec-write",
|
|
4080
|
+
tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
|
|
4081
|
+
},
|
|
4082
|
+
{ cwd },
|
|
4083
|
+
);
|
|
4084
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
4085
|
+
|
|
4086
|
+
const allowedBash = await dispatchCodexNativeHook(
|
|
4087
|
+
{
|
|
4088
|
+
hook_event_name: "PreToolUse",
|
|
4089
|
+
cwd,
|
|
4090
|
+
session_id: "sess-di-artifact",
|
|
4091
|
+
tool_name: "Bash",
|
|
4092
|
+
tool_use_id: "tool-di-context-bash",
|
|
4093
|
+
tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
|
|
4094
|
+
},
|
|
4095
|
+
{ cwd },
|
|
4096
|
+
);
|
|
4097
|
+
assert.equal(allowedBash.outputJson, null);
|
|
4098
|
+
|
|
4099
|
+
const allowedAppendBash = await dispatchCodexNativeHook(
|
|
4100
|
+
{
|
|
4101
|
+
hook_event_name: "PreToolUse",
|
|
4102
|
+
cwd,
|
|
4103
|
+
session_id: "sess-di-artifact",
|
|
4104
|
+
tool_name: "Bash",
|
|
4105
|
+
tool_use_id: "tool-di-context-append-bash",
|
|
4106
|
+
tool_input: { command: "echo more context >> .omx/context/demo.md" },
|
|
4107
|
+
},
|
|
4108
|
+
{ cwd },
|
|
4109
|
+
);
|
|
4110
|
+
assert.equal(allowedAppendBash.outputJson, null);
|
|
4111
|
+
|
|
4112
|
+
const blockedBash = await dispatchCodexNativeHook(
|
|
4113
|
+
{
|
|
4114
|
+
hook_event_name: "PreToolUse",
|
|
4115
|
+
cwd,
|
|
4116
|
+
session_id: "sess-di-artifact",
|
|
4117
|
+
tool_name: "Bash",
|
|
4118
|
+
tool_use_id: "tool-di-src-bash",
|
|
4119
|
+
tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
|
|
4120
|
+
},
|
|
4121
|
+
{ cwd },
|
|
4122
|
+
);
|
|
4123
|
+
assert.equal((blockedBash.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4124
|
+
} finally {
|
|
4125
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4126
|
+
}
|
|
4127
|
+
});
|
|
4128
|
+
|
|
4129
|
+
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
4130
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
4131
|
+
try {
|
|
4132
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4133
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
|
|
4134
|
+
await mkdir(sessionDir, { recursive: true });
|
|
4135
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
4136
|
+
version: 1,
|
|
4137
|
+
active: true,
|
|
4138
|
+
skill: "deep-interview",
|
|
4139
|
+
phase: "planning",
|
|
4140
|
+
session_id: "sess-di-handoff",
|
|
4141
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
|
|
4142
|
+
});
|
|
4143
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
4144
|
+
active: true,
|
|
4145
|
+
mode: "deep-interview",
|
|
4146
|
+
current_phase: "intent-first",
|
|
4147
|
+
session_id: "sess-di-handoff",
|
|
4148
|
+
});
|
|
4149
|
+
|
|
4150
|
+
await dispatchCodexNativeHook(
|
|
4151
|
+
{
|
|
4152
|
+
hook_event_name: "UserPromptSubmit",
|
|
4153
|
+
cwd,
|
|
4154
|
+
session_id: "sess-di-handoff",
|
|
4155
|
+
prompt: "$ralph implement the clarified spec in src/implementation.ts",
|
|
4156
|
+
},
|
|
4157
|
+
{ cwd },
|
|
4158
|
+
);
|
|
4159
|
+
|
|
4160
|
+
const result = await dispatchCodexNativeHook(
|
|
4161
|
+
{
|
|
4162
|
+
hook_event_name: "PreToolUse",
|
|
4163
|
+
cwd,
|
|
4164
|
+
session_id: "sess-di-handoff",
|
|
4165
|
+
tool_name: "Edit",
|
|
4166
|
+
tool_use_id: "tool-di-post-handoff-edit",
|
|
4167
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
4168
|
+
},
|
|
4169
|
+
{ cwd },
|
|
4170
|
+
);
|
|
4171
|
+
|
|
4172
|
+
assert.equal(result.outputJson, null);
|
|
4173
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean };
|
|
4174
|
+
assert.equal(completed.active, false);
|
|
4175
|
+
} finally {
|
|
4176
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4177
|
+
}
|
|
4178
|
+
});
|
|
4179
|
+
|
|
3892
4180
|
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
3893
4181
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
3894
4182
|
try {
|
|
@@ -6581,6 +6869,7 @@ exit 0
|
|
|
6581
6869
|
active: true,
|
|
6582
6870
|
current_phase: "team-exec",
|
|
6583
6871
|
team_name: "review-team",
|
|
6872
|
+
session_id: "sess-stop-team",
|
|
6584
6873
|
});
|
|
6585
6874
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
6586
6875
|
current_phase: "team-verify",
|
|
@@ -7468,6 +7757,90 @@ exit 0
|
|
|
7468
7757
|
}
|
|
7469
7758
|
});
|
|
7470
7759
|
|
|
7760
|
+
it("does not block Stop from canonical team state owned by another thread", async () => {
|
|
7761
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
|
|
7762
|
+
try {
|
|
7763
|
+
await initTeamState(
|
|
7764
|
+
"canonical-other-thread-team",
|
|
7765
|
+
"canonical other-thread stop fallback",
|
|
7766
|
+
"executor",
|
|
7767
|
+
1,
|
|
7768
|
+
cwd,
|
|
7769
|
+
undefined,
|
|
7770
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" },
|
|
7771
|
+
);
|
|
7772
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
|
|
7773
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
|
|
7774
|
+
await writeJson(manifestPath, {
|
|
7775
|
+
...manifest,
|
|
7776
|
+
leader: {
|
|
7777
|
+
...(manifest.leader as Record<string, unknown> | undefined),
|
|
7778
|
+
thread_id: "thread-other",
|
|
7779
|
+
},
|
|
7780
|
+
});
|
|
7781
|
+
|
|
7782
|
+
const result = await dispatchCodexNativeHook(
|
|
7783
|
+
{
|
|
7784
|
+
hook_event_name: "Stop",
|
|
7785
|
+
cwd,
|
|
7786
|
+
session_id: "sess-stop-team-canonical-thread",
|
|
7787
|
+
thread_id: "thread-current",
|
|
7788
|
+
},
|
|
7789
|
+
{ cwd },
|
|
7790
|
+
);
|
|
7791
|
+
|
|
7792
|
+
assert.equal(result.omxEventName, "stop");
|
|
7793
|
+
assert.equal(result.outputJson, null);
|
|
7794
|
+
} finally {
|
|
7795
|
+
await rm(cwd, { recursive: true, force: true });
|
|
7796
|
+
}
|
|
7797
|
+
});
|
|
7798
|
+
|
|
7799
|
+
it("blocks Stop from canonical team state owned by the current thread", async () => {
|
|
7800
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
|
|
7801
|
+
try {
|
|
7802
|
+
await initTeamState(
|
|
7803
|
+
"canonical-current-thread-team",
|
|
7804
|
+
"canonical current-thread stop fallback",
|
|
7805
|
+
"executor",
|
|
7806
|
+
1,
|
|
7807
|
+
cwd,
|
|
7808
|
+
undefined,
|
|
7809
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" },
|
|
7810
|
+
);
|
|
7811
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
|
|
7812
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
|
|
7813
|
+
await writeJson(manifestPath, {
|
|
7814
|
+
...manifest,
|
|
7815
|
+
leader: {
|
|
7816
|
+
...(manifest.leader as Record<string, unknown> | undefined),
|
|
7817
|
+
thread_id: "thread-current",
|
|
7818
|
+
},
|
|
7819
|
+
});
|
|
7820
|
+
|
|
7821
|
+
const result = await dispatchCodexNativeHook(
|
|
7822
|
+
{
|
|
7823
|
+
hook_event_name: "Stop",
|
|
7824
|
+
cwd,
|
|
7825
|
+
session_id: "sess-stop-team-canonical-current-thread",
|
|
7826
|
+
thread_id: "thread-current",
|
|
7827
|
+
},
|
|
7828
|
+
{ cwd },
|
|
7829
|
+
);
|
|
7830
|
+
|
|
7831
|
+
assert.equal(result.omxEventName, "stop");
|
|
7832
|
+
assert.deepEqual(result.outputJson, {
|
|
7833
|
+
decision: "block",
|
|
7834
|
+
reason:
|
|
7835
|
+
`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}`,
|
|
7836
|
+
stopReason: "team_team-exec",
|
|
7837
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
7838
|
+
});
|
|
7839
|
+
} finally {
|
|
7840
|
+
await rm(cwd, { recursive: true, force: true });
|
|
7841
|
+
}
|
|
7842
|
+
});
|
|
7843
|
+
|
|
7471
7844
|
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 () => {
|
|
7472
7845
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
|
|
7473
7846
|
try {
|
|
@@ -7742,7 +8115,7 @@ exit 0
|
|
|
7742
8115
|
const sharedRoot = join(cwd, "shared-root");
|
|
7743
8116
|
const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
7744
8117
|
try {
|
|
7745
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
8118
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
|
|
7746
8119
|
await initTeamState(
|
|
7747
8120
|
"canonical-root-team",
|
|
7748
8121
|
"canonical stop root fallback",
|
|
@@ -7750,7 +8123,7 @@ exit 0
|
|
|
7750
8123
|
1,
|
|
7751
8124
|
cwd,
|
|
7752
8125
|
undefined,
|
|
7753
|
-
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT:
|
|
8126
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot },
|
|
7754
8127
|
);
|
|
7755
8128
|
|
|
7756
8129
|
const result = await dispatchCodexNativeHook(
|
|
@@ -7815,9 +8188,10 @@ exit 0
|
|
|
7815
8188
|
|
|
7816
8189
|
it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
|
|
7817
8190
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
|
|
8191
|
+
const teamStateRoot = join(cwd, "shared-team-state");
|
|
7818
8192
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
7819
8193
|
try {
|
|
7820
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
8194
|
+
process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
|
|
7821
8195
|
await initTeamState(
|
|
7822
8196
|
"env-root-team",
|
|
7823
8197
|
"env root stop fallback",
|
|
@@ -7828,7 +8202,7 @@ exit 0
|
|
|
7828
8202
|
{
|
|
7829
8203
|
...process.env,
|
|
7830
8204
|
OMX_SESSION_ID: "sess-stop-team-env-root",
|
|
7831
|
-
OMX_TEAM_STATE_ROOT:
|
|
8205
|
+
OMX_TEAM_STATE_ROOT: teamStateRoot,
|
|
7832
8206
|
},
|
|
7833
8207
|
);
|
|
7834
8208
|
|
|
@@ -11052,6 +11426,8 @@ exit 0
|
|
|
11052
11426
|
active: true,
|
|
11053
11427
|
current_phase: "team-exec",
|
|
11054
11428
|
team_name: "review-team",
|
|
11429
|
+
session_id: "sess-stop-team-refire",
|
|
11430
|
+
thread_id: "thread-stop-team-refire",
|
|
11055
11431
|
});
|
|
11056
11432
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
11057
11433
|
current_phase: "team-verify",
|
|
@@ -11291,7 +11667,251 @@ exit 0
|
|
|
11291
11667
|
}
|
|
11292
11668
|
});
|
|
11293
11669
|
|
|
11294
|
-
it("does not block Stop from
|
|
11670
|
+
it("does not block Stop from root team state without team_name when no session is known", async () => {
|
|
11671
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
|
|
11672
|
+
try {
|
|
11673
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11674
|
+
await mkdir(stateDir, { recursive: true });
|
|
11675
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11676
|
+
active: true,
|
|
11677
|
+
mode: "team",
|
|
11678
|
+
current_phase: "starting",
|
|
11679
|
+
});
|
|
11680
|
+
|
|
11681
|
+
const result = await dispatchCodexNativeHook(
|
|
11682
|
+
{
|
|
11683
|
+
hook_event_name: "Stop",
|
|
11684
|
+
cwd,
|
|
11685
|
+
},
|
|
11686
|
+
{ cwd },
|
|
11687
|
+
);
|
|
11688
|
+
|
|
11689
|
+
assert.equal(result.omxEventName, "stop");
|
|
11690
|
+
assert.equal(result.outputJson, null);
|
|
11691
|
+
} finally {
|
|
11692
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11693
|
+
}
|
|
11694
|
+
});
|
|
11695
|
+
|
|
11696
|
+
it("does not block Stop from root team state without team_name for a foreign session", async () => {
|
|
11697
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
|
|
11698
|
+
try {
|
|
11699
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11700
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11701
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11702
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11703
|
+
active: true,
|
|
11704
|
+
mode: "team",
|
|
11705
|
+
current_phase: "starting",
|
|
11706
|
+
});
|
|
11707
|
+
|
|
11708
|
+
const result = await dispatchCodexNativeHook(
|
|
11709
|
+
{
|
|
11710
|
+
hook_event_name: "Stop",
|
|
11711
|
+
cwd,
|
|
11712
|
+
session_id: "sess-current",
|
|
11713
|
+
thread_id: "thread-current",
|
|
11714
|
+
},
|
|
11715
|
+
{ cwd },
|
|
11716
|
+
);
|
|
11717
|
+
|
|
11718
|
+
assert.equal(result.omxEventName, "stop");
|
|
11719
|
+
assert.equal(result.outputJson, null);
|
|
11720
|
+
} finally {
|
|
11721
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11722
|
+
}
|
|
11723
|
+
});
|
|
11724
|
+
|
|
11725
|
+
it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
|
|
11726
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
|
|
11727
|
+
try {
|
|
11728
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11729
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11730
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11731
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11732
|
+
active: true,
|
|
11733
|
+
current_phase: "starting",
|
|
11734
|
+
team_name: "stale-root-thread-team",
|
|
11735
|
+
session_id: "sess-current",
|
|
11736
|
+
thread_id: "thread-other",
|
|
11737
|
+
});
|
|
11738
|
+
await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
|
|
11739
|
+
current_phase: "team-exec",
|
|
11740
|
+
max_fix_attempts: 3,
|
|
11741
|
+
current_fix_attempt: 0,
|
|
11742
|
+
transitions: [],
|
|
11743
|
+
updated_at: new Date().toISOString(),
|
|
11744
|
+
});
|
|
11745
|
+
|
|
11746
|
+
const result = await dispatchCodexNativeHook(
|
|
11747
|
+
{
|
|
11748
|
+
hook_event_name: "Stop",
|
|
11749
|
+
cwd,
|
|
11750
|
+
session_id: "sess-current",
|
|
11751
|
+
thread_id: "thread-current",
|
|
11752
|
+
},
|
|
11753
|
+
{ cwd },
|
|
11754
|
+
);
|
|
11755
|
+
|
|
11756
|
+
assert.equal(result.omxEventName, "stop");
|
|
11757
|
+
assert.equal(result.outputJson, null);
|
|
11758
|
+
} finally {
|
|
11759
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11760
|
+
}
|
|
11761
|
+
});
|
|
11762
|
+
|
|
11763
|
+
it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
|
|
11764
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
|
|
11765
|
+
try {
|
|
11766
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11767
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11768
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11769
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11770
|
+
active: true,
|
|
11771
|
+
current_phase: "starting",
|
|
11772
|
+
team_name: "root-missing-thread-team",
|
|
11773
|
+
session_id: "sess-current",
|
|
11774
|
+
});
|
|
11775
|
+
await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
|
|
11776
|
+
current_phase: "team-exec",
|
|
11777
|
+
max_fix_attempts: 3,
|
|
11778
|
+
current_fix_attempt: 0,
|
|
11779
|
+
transitions: [],
|
|
11780
|
+
updated_at: new Date().toISOString(),
|
|
11781
|
+
});
|
|
11782
|
+
|
|
11783
|
+
const result = await dispatchCodexNativeHook(
|
|
11784
|
+
{
|
|
11785
|
+
hook_event_name: "Stop",
|
|
11786
|
+
cwd,
|
|
11787
|
+
session_id: "sess-current",
|
|
11788
|
+
thread_id: "thread-current",
|
|
11789
|
+
},
|
|
11790
|
+
{ cwd },
|
|
11791
|
+
);
|
|
11792
|
+
|
|
11793
|
+
assert.equal(result.omxEventName, "stop");
|
|
11794
|
+
assert.equal(result.outputJson, null);
|
|
11795
|
+
} finally {
|
|
11796
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11797
|
+
}
|
|
11798
|
+
});
|
|
11799
|
+
|
|
11800
|
+
it("does not block Stop from root team state when canonical phase is missing", async () => {
|
|
11801
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
|
|
11802
|
+
try {
|
|
11803
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11804
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11805
|
+
await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
|
|
11806
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11807
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
11808
|
+
active: true,
|
|
11809
|
+
current_phase: "starting",
|
|
11810
|
+
team_name: "root-missing-phase-team",
|
|
11811
|
+
session_id: "sess-current",
|
|
11812
|
+
thread_id: "thread-current",
|
|
11813
|
+
});
|
|
11814
|
+
|
|
11815
|
+
const result = await dispatchCodexNativeHook(
|
|
11816
|
+
{
|
|
11817
|
+
hook_event_name: "Stop",
|
|
11818
|
+
cwd,
|
|
11819
|
+
session_id: "sess-current",
|
|
11820
|
+
thread_id: "thread-current",
|
|
11821
|
+
},
|
|
11822
|
+
{ cwd },
|
|
11823
|
+
);
|
|
11824
|
+
|
|
11825
|
+
assert.equal(result.omxEventName, "stop");
|
|
11826
|
+
assert.equal(result.outputJson, null);
|
|
11827
|
+
} finally {
|
|
11828
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11829
|
+
}
|
|
11830
|
+
});
|
|
11831
|
+
|
|
11832
|
+
it("does not block Stop from session-scoped team state owned by another thread", async () => {
|
|
11833
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
|
|
11834
|
+
try {
|
|
11835
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11836
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11837
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11838
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
11839
|
+
active: true,
|
|
11840
|
+
current_phase: "starting",
|
|
11841
|
+
team_name: "scoped-other-thread-team",
|
|
11842
|
+
session_id: "sess-current",
|
|
11843
|
+
thread_id: "thread-other",
|
|
11844
|
+
});
|
|
11845
|
+
await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
|
|
11846
|
+
current_phase: "team-exec",
|
|
11847
|
+
max_fix_attempts: 3,
|
|
11848
|
+
current_fix_attempt: 0,
|
|
11849
|
+
transitions: [],
|
|
11850
|
+
updated_at: new Date().toISOString(),
|
|
11851
|
+
});
|
|
11852
|
+
|
|
11853
|
+
const result = await dispatchCodexNativeHook(
|
|
11854
|
+
{
|
|
11855
|
+
hook_event_name: "Stop",
|
|
11856
|
+
cwd,
|
|
11857
|
+
session_id: "sess-current",
|
|
11858
|
+
thread_id: "thread-current",
|
|
11859
|
+
},
|
|
11860
|
+
{ cwd },
|
|
11861
|
+
);
|
|
11862
|
+
|
|
11863
|
+
assert.equal(result.omxEventName, "stop");
|
|
11864
|
+
assert.equal(result.outputJson, null);
|
|
11865
|
+
} finally {
|
|
11866
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11867
|
+
}
|
|
11868
|
+
});
|
|
11869
|
+
|
|
11870
|
+
it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
|
|
11871
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
|
|
11872
|
+
try {
|
|
11873
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
11874
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
11875
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
11876
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
11877
|
+
active: true,
|
|
11878
|
+
current_phase: "starting",
|
|
11879
|
+
team_name: "scoped-current-team",
|
|
11880
|
+
session_id: "sess-current",
|
|
11881
|
+
thread_id: "thread-current",
|
|
11882
|
+
});
|
|
11883
|
+
await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
|
|
11884
|
+
current_phase: "team-exec",
|
|
11885
|
+
max_fix_attempts: 3,
|
|
11886
|
+
current_fix_attempt: 0,
|
|
11887
|
+
transitions: [],
|
|
11888
|
+
updated_at: new Date().toISOString(),
|
|
11889
|
+
});
|
|
11890
|
+
|
|
11891
|
+
const result = await dispatchCodexNativeHook(
|
|
11892
|
+
{
|
|
11893
|
+
hook_event_name: "Stop",
|
|
11894
|
+
cwd,
|
|
11895
|
+
session_id: "sess-current",
|
|
11896
|
+
thread_id: "thread-current",
|
|
11897
|
+
},
|
|
11898
|
+
{ cwd },
|
|
11899
|
+
);
|
|
11900
|
+
|
|
11901
|
+
assert.equal(result.omxEventName, "stop");
|
|
11902
|
+
assert.deepEqual(result.outputJson, {
|
|
11903
|
+
decision: "block",
|
|
11904
|
+
reason:
|
|
11905
|
+
`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}`,
|
|
11906
|
+
stopReason: "team_team-exec",
|
|
11907
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
11908
|
+
});
|
|
11909
|
+
} finally {
|
|
11910
|
+
await rm(cwd, { recursive: true, force: true });
|
|
11911
|
+
}
|
|
11912
|
+
});
|
|
11913
|
+
|
|
11914
|
+
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
11295
11915
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
11296
11916
|
try {
|
|
11297
11917
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -11504,6 +12124,91 @@ describe("codex native hook triage integration", () => {
|
|
|
11504
12124
|
}
|
|
11505
12125
|
});
|
|
11506
12126
|
|
|
12127
|
+
|
|
12128
|
+
it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
|
|
12129
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
|
|
12130
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
|
|
12131
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
12132
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
12133
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
12134
|
+
try {
|
|
12135
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
12136
|
+
delete process.env.OMX_STATE_ROOT;
|
|
12137
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
12138
|
+
const boxedStateDir = getBaseStateDir(cwd);
|
|
12139
|
+
await mkdir(boxedStateDir, { recursive: true });
|
|
12140
|
+
await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
|
|
12141
|
+
schemaVersion: 1,
|
|
12142
|
+
sessions: {
|
|
12143
|
+
"omx-parent-session": {
|
|
12144
|
+
session_id: "omx-parent-session",
|
|
12145
|
+
leader_thread_id: "parent-native-thread",
|
|
12146
|
+
updated_at: "2026-05-21T19:04:40.000Z",
|
|
12147
|
+
threads: {
|
|
12148
|
+
"parent-native-thread": {
|
|
12149
|
+
thread_id: "parent-native-thread",
|
|
12150
|
+
kind: "leader",
|
|
12151
|
+
first_seen_at: "2026-05-21T19:04:40.000Z",
|
|
12152
|
+
last_seen_at: "2026-05-21T19:04:40.000Z",
|
|
12153
|
+
turn_count: 1,
|
|
12154
|
+
},
|
|
12155
|
+
"child-native-session": {
|
|
12156
|
+
thread_id: "child-native-session",
|
|
12157
|
+
kind: "subagent",
|
|
12158
|
+
first_seen_at: "2026-05-21T19:04:41.000Z",
|
|
12159
|
+
last_seen_at: "2026-05-21T19:04:41.000Z",
|
|
12160
|
+
turn_count: 1,
|
|
12161
|
+
mode: "review",
|
|
12162
|
+
},
|
|
12163
|
+
},
|
|
12164
|
+
},
|
|
12165
|
+
},
|
|
12166
|
+
});
|
|
12167
|
+
|
|
12168
|
+
const result = await dispatchCodexNativeHook(
|
|
12169
|
+
{
|
|
12170
|
+
hook_event_name: "UserPromptSubmit",
|
|
12171
|
+
cwd,
|
|
12172
|
+
session_id: "child-native-session",
|
|
12173
|
+
thread_id: "child-native-session",
|
|
12174
|
+
turn_id: "turn-subagent-review",
|
|
12175
|
+
prompt: [
|
|
12176
|
+
"Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
|
|
12177
|
+
"Context: The user asked for $autopilot, and this subagent must only review the patch.",
|
|
12178
|
+
].join("\n\n"),
|
|
12179
|
+
},
|
|
12180
|
+
{ cwd },
|
|
12181
|
+
);
|
|
12182
|
+
|
|
12183
|
+
const additionalContext = String(
|
|
12184
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
|
|
12185
|
+
);
|
|
12186
|
+
assert.equal(additionalContext, "");
|
|
12187
|
+
assert.equal(
|
|
12188
|
+
existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")),
|
|
12189
|
+
false,
|
|
12190
|
+
);
|
|
12191
|
+
assert.equal(
|
|
12192
|
+
existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")),
|
|
12193
|
+
false,
|
|
12194
|
+
);
|
|
12195
|
+
assert.equal(
|
|
12196
|
+
existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")),
|
|
12197
|
+
false,
|
|
12198
|
+
"subagent tracking must not leak into the source worktree when OMX_ROOT is boxed",
|
|
12199
|
+
);
|
|
12200
|
+
} finally {
|
|
12201
|
+
if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
|
|
12202
|
+
else process.env.OMX_ROOT = originalOmxRoot;
|
|
12203
|
+
if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
|
|
12204
|
+
else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
12205
|
+
if (originalTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
|
|
12206
|
+
else process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
12207
|
+
await rm(cwd, { recursive: true, force: true });
|
|
12208
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
12209
|
+
}
|
|
12210
|
+
});
|
|
12211
|
+
|
|
11507
12212
|
it("does not inject triage advisory for autopilot keyword prompts", async () => {
|
|
11508
12213
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
|
|
11509
12214
|
try {
|
|
@@ -11535,6 +12240,62 @@ describe("codex native hook triage integration", () => {
|
|
|
11535
12240
|
}
|
|
11536
12241
|
});
|
|
11537
12242
|
|
|
12243
|
+
it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
|
|
12244
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
|
|
12245
|
+
try {
|
|
12246
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
12247
|
+
await writeSessionStart(cwd, "sess-autopilot-observable");
|
|
12248
|
+
|
|
12249
|
+
const result = await dispatchCodexNativeHook(
|
|
12250
|
+
{
|
|
12251
|
+
hook_event_name: "UserPromptSubmit",
|
|
12252
|
+
cwd,
|
|
12253
|
+
session_id: "sess-autopilot-observable",
|
|
12254
|
+
thread_id: "thread-autopilot-observable",
|
|
12255
|
+
turn_id: "turn-autopilot-observable",
|
|
12256
|
+
prompt: "$autopilot implement issue #2430",
|
|
12257
|
+
},
|
|
12258
|
+
{ cwd },
|
|
12259
|
+
);
|
|
12260
|
+
|
|
12261
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
12262
|
+
assert.equal(result.skillState?.phase, "deep-interview");
|
|
12263
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
|
|
12264
|
+
|
|
12265
|
+
const additionalContext = String(
|
|
12266
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
|
|
12267
|
+
);
|
|
12268
|
+
assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
|
|
12269
|
+
assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
|
|
12270
|
+
assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
|
|
12271
|
+
assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
|
|
12272
|
+
assert.match(additionalContext, /Codex goal-mode handoff guidance/);
|
|
12273
|
+
assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
|
|
12274
|
+
|
|
12275
|
+
const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
|
|
12276
|
+
const modeState = JSON.parse(await readFile(statePath, "utf-8")) as {
|
|
12277
|
+
active: boolean;
|
|
12278
|
+
current_phase: string;
|
|
12279
|
+
state?: { phase_cycle?: string[]; deep_interview_gate?: { status?: string; skip_reason?: string | null } };
|
|
12280
|
+
};
|
|
12281
|
+
assert.equal(modeState.active, true);
|
|
12282
|
+
assert.equal(modeState.current_phase, "deep-interview");
|
|
12283
|
+
assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
|
|
12284
|
+
assert.deepEqual(modeState.state?.deep_interview_gate, {
|
|
12285
|
+
status: "required",
|
|
12286
|
+
skip_reason: null,
|
|
12287
|
+
rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
|
|
12288
|
+
});
|
|
12289
|
+
|
|
12290
|
+
const hudState = await readAllState(cwd);
|
|
12291
|
+
assert.equal(hudState.autopilot?.active, true);
|
|
12292
|
+
assert.equal(hudState.autopilot?.current_phase, "deep-interview");
|
|
12293
|
+
assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
|
|
12294
|
+
} finally {
|
|
12295
|
+
await rm(cwd, { recursive: true, force: true });
|
|
12296
|
+
}
|
|
12297
|
+
});
|
|
12298
|
+
|
|
11538
12299
|
// ── Group 2: HEAVY injection ─────────────────────────────────────────────
|
|
11539
12300
|
|
|
11540
12301
|
it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
|