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
|
@@ -15,9 +15,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
|
15
15
|
import { executeStateOperation } from "../../state/operations.js";
|
|
16
16
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
17
17
|
import { readAllState } from "../../hud/state.js";
|
|
18
|
+
import { renderHud } from "../../hud/render.js";
|
|
18
19
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
19
20
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
20
21
|
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
22
|
+
import { getBaseStateDir } from "../../state/paths.js";
|
|
21
23
|
function nativeHookScriptPath() {
|
|
22
24
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
23
25
|
}
|
|
@@ -222,7 +224,7 @@ describe("codex native hook config", () => {
|
|
|
222
224
|
assert.equal(sessionStart.matcher, "startup|resume|clear");
|
|
223
225
|
assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
|
|
224
226
|
const preToolUse = config.hooks.PreToolUse[0];
|
|
225
|
-
assert.equal(preToolUse.matcher,
|
|
227
|
+
assert.equal(preToolUse.matcher, undefined);
|
|
226
228
|
assert.match(String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
|
|
227
229
|
assert.equal(preToolUse.hooks?.[0]?.statusMessage, undefined);
|
|
228
230
|
const postToolUse = config.hooks.PostToolUse[0];
|
|
@@ -1593,6 +1595,9 @@ describe("codex native hook dispatch", () => {
|
|
|
1593
1595
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1594
1596
|
assert.match(output, /--status blocked/);
|
|
1595
1597
|
assert.match(output, /Codex goal context/);
|
|
1598
|
+
assert.match(output, /no such table: thread_goals/);
|
|
1599
|
+
assert.match(output, /unavailable get_goal error JSON or path/);
|
|
1600
|
+
assert.match(output, /safe-recovery blocker/);
|
|
1596
1601
|
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1597
1602
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1598
1603
|
}
|
|
@@ -1864,6 +1869,30 @@ describe("codex native hook dispatch", () => {
|
|
|
1864
1869
|
await rm(cwd, { recursive: true, force: true });
|
|
1865
1870
|
}
|
|
1866
1871
|
});
|
|
1872
|
+
it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
|
|
1873
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
|
|
1874
|
+
try {
|
|
1875
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1876
|
+
const result = await dispatchCodexNativeHook({
|
|
1877
|
+
hook_event_name: "UserPromptSubmit",
|
|
1878
|
+
cwd,
|
|
1879
|
+
session_id: "sess-autopilot-ralplan-gate",
|
|
1880
|
+
thread_id: "thread-autopilot-ralplan-gate",
|
|
1881
|
+
turn_id: "turn-autopilot-ralplan-gate",
|
|
1882
|
+
prompt: "$autopilot implement issue #2430",
|
|
1883
|
+
}, { cwd });
|
|
1884
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1885
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
1886
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1887
|
+
assert.match(message, /Autopilot protocol:/);
|
|
1888
|
+
assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
|
|
1889
|
+
assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
|
|
1890
|
+
assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
|
|
1891
|
+
}
|
|
1892
|
+
finally {
|
|
1893
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1867
1896
|
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
1868
1897
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
1869
1898
|
try {
|
|
@@ -1878,7 +1907,8 @@ describe("codex native hook dispatch", () => {
|
|
|
1878
1907
|
}, { cwd });
|
|
1879
1908
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
1880
1909
|
assert.equal(result.skillState?.skill, "ultragoal");
|
|
1881
|
-
assert.equal(result.skillState?.initialized_mode,
|
|
1910
|
+
assert.equal(result.skillState?.initialized_mode, "ultragoal");
|
|
1911
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json");
|
|
1882
1912
|
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1883
1913
|
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
1884
1914
|
assert.match(message, /Ultragoal protocol:/);
|
|
@@ -1887,7 +1917,65 @@ describe("codex native hook dispatch", () => {
|
|
|
1887
1917
|
assert.match(message, /update_goal/);
|
|
1888
1918
|
assert.match(message, /does not call `\/goal clear`/);
|
|
1889
1919
|
assert.match(message, /multiple sequential ultragoal runs/);
|
|
1890
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")),
|
|
1920
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
|
|
1921
|
+
}
|
|
1922
|
+
finally {
|
|
1923
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
|
|
1927
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
|
|
1928
|
+
try {
|
|
1929
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1930
|
+
const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
|
|
1931
|
+
await mkdir(sessionDir, { recursive: true });
|
|
1932
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
1933
|
+
version: 1,
|
|
1934
|
+
active: true,
|
|
1935
|
+
skill: "deep-interview",
|
|
1936
|
+
phase: "planning",
|
|
1937
|
+
session_id: "sess-ultragoal-handoff",
|
|
1938
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
|
|
1939
|
+
});
|
|
1940
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
1941
|
+
active: true,
|
|
1942
|
+
mode: "deep-interview",
|
|
1943
|
+
current_phase: "intent-first",
|
|
1944
|
+
session_id: "sess-ultragoal-handoff",
|
|
1945
|
+
question_enforcement: {
|
|
1946
|
+
obligation_id: "obligation-ultragoal-handoff",
|
|
1947
|
+
source: "omx-question",
|
|
1948
|
+
status: "pending",
|
|
1949
|
+
requested_at: "2026-05-21T03:00:00.000Z",
|
|
1950
|
+
},
|
|
1951
|
+
});
|
|
1952
|
+
const result = await dispatchCodexNativeHook({
|
|
1953
|
+
hook_event_name: "UserPromptSubmit",
|
|
1954
|
+
cwd,
|
|
1955
|
+
session_id: "sess-ultragoal-handoff",
|
|
1956
|
+
thread_id: "thread-ultragoal-handoff",
|
|
1957
|
+
turn_id: "turn-ultragoal-handoff",
|
|
1958
|
+
prompt: "$ultragoal turn the clarified spec into goals",
|
|
1959
|
+
}, { cwd });
|
|
1960
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1961
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
1962
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
|
|
1963
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
1964
|
+
assert.equal(completed.active, false);
|
|
1965
|
+
assert.equal(completed.current_phase, "completed");
|
|
1966
|
+
assert.equal(completed.question_enforcement?.status, "cleared");
|
|
1967
|
+
assert.equal(completed.question_enforcement?.clear_reason, "handoff");
|
|
1968
|
+
assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
|
|
1969
|
+
const edit = await dispatchCodexNativeHook({
|
|
1970
|
+
hook_event_name: "PreToolUse",
|
|
1971
|
+
cwd,
|
|
1972
|
+
session_id: "sess-ultragoal-handoff",
|
|
1973
|
+
thread_id: "thread-ultragoal-handoff",
|
|
1974
|
+
tool_name: "Edit",
|
|
1975
|
+
tool_use_id: "tool-ultragoal-post-handoff-edit",
|
|
1976
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
1977
|
+
}, { cwd });
|
|
1978
|
+
assert.equal(edit.outputJson, null);
|
|
1891
1979
|
}
|
|
1892
1980
|
finally {
|
|
1893
1981
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3248,6 +3336,153 @@ exit 0
|
|
|
3248
3336
|
await rm(cwd, { recursive: true, force: true });
|
|
3249
3337
|
}
|
|
3250
3338
|
});
|
|
3339
|
+
it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
|
|
3340
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
|
|
3341
|
+
try {
|
|
3342
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3343
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
|
|
3344
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3345
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
|
|
3346
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3347
|
+
version: 1,
|
|
3348
|
+
active: true,
|
|
3349
|
+
skill: "deep-interview",
|
|
3350
|
+
phase: "planning",
|
|
3351
|
+
session_id: "sess-di-edit-block",
|
|
3352
|
+
thread_id: "thread-di-edit-block",
|
|
3353
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
|
|
3354
|
+
});
|
|
3355
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3356
|
+
active: true,
|
|
3357
|
+
mode: "deep-interview",
|
|
3358
|
+
current_phase: "intent-first",
|
|
3359
|
+
session_id: "sess-di-edit-block",
|
|
3360
|
+
thread_id: "thread-di-edit-block",
|
|
3361
|
+
rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
|
|
3362
|
+
});
|
|
3363
|
+
const result = await dispatchCodexNativeHook({
|
|
3364
|
+
hook_event_name: "PreToolUse",
|
|
3365
|
+
cwd,
|
|
3366
|
+
session_id: "sess-di-edit-block",
|
|
3367
|
+
thread_id: "thread-di-edit-block",
|
|
3368
|
+
tool_name: "Edit",
|
|
3369
|
+
tool_use_id: "tool-di-edit-block",
|
|
3370
|
+
tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
|
|
3371
|
+
}, { cwd });
|
|
3372
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3373
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3374
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active/);
|
|
3375
|
+
assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
|
|
3376
|
+
assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
|
|
3377
|
+
}
|
|
3378
|
+
finally {
|
|
3379
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
3383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
3384
|
+
try {
|
|
3385
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3386
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
|
|
3387
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3388
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
|
|
3389
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3390
|
+
version: 1,
|
|
3391
|
+
active: true,
|
|
3392
|
+
skill: "deep-interview",
|
|
3393
|
+
phase: "planning",
|
|
3394
|
+
session_id: "sess-di-artifact",
|
|
3395
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
|
|
3396
|
+
});
|
|
3397
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3398
|
+
active: true,
|
|
3399
|
+
mode: "deep-interview",
|
|
3400
|
+
current_phase: "intent-first",
|
|
3401
|
+
session_id: "sess-di-artifact",
|
|
3402
|
+
});
|
|
3403
|
+
const allowedWrite = await dispatchCodexNativeHook({
|
|
3404
|
+
hook_event_name: "PreToolUse",
|
|
3405
|
+
cwd,
|
|
3406
|
+
session_id: "sess-di-artifact",
|
|
3407
|
+
tool_name: "Write",
|
|
3408
|
+
tool_use_id: "tool-di-spec-write",
|
|
3409
|
+
tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
|
|
3410
|
+
}, { cwd });
|
|
3411
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
3412
|
+
const allowedBash = await dispatchCodexNativeHook({
|
|
3413
|
+
hook_event_name: "PreToolUse",
|
|
3414
|
+
cwd,
|
|
3415
|
+
session_id: "sess-di-artifact",
|
|
3416
|
+
tool_name: "Bash",
|
|
3417
|
+
tool_use_id: "tool-di-context-bash",
|
|
3418
|
+
tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
|
|
3419
|
+
}, { cwd });
|
|
3420
|
+
assert.equal(allowedBash.outputJson, null);
|
|
3421
|
+
const allowedAppendBash = await dispatchCodexNativeHook({
|
|
3422
|
+
hook_event_name: "PreToolUse",
|
|
3423
|
+
cwd,
|
|
3424
|
+
session_id: "sess-di-artifact",
|
|
3425
|
+
tool_name: "Bash",
|
|
3426
|
+
tool_use_id: "tool-di-context-append-bash",
|
|
3427
|
+
tool_input: { command: "echo more context >> .omx/context/demo.md" },
|
|
3428
|
+
}, { cwd });
|
|
3429
|
+
assert.equal(allowedAppendBash.outputJson, null);
|
|
3430
|
+
const blockedBash = await dispatchCodexNativeHook({
|
|
3431
|
+
hook_event_name: "PreToolUse",
|
|
3432
|
+
cwd,
|
|
3433
|
+
session_id: "sess-di-artifact",
|
|
3434
|
+
tool_name: "Bash",
|
|
3435
|
+
tool_use_id: "tool-di-src-bash",
|
|
3436
|
+
tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
|
|
3437
|
+
}, { cwd });
|
|
3438
|
+
assert.equal(blockedBash.outputJson?.decision, "block");
|
|
3439
|
+
}
|
|
3440
|
+
finally {
|
|
3441
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3442
|
+
}
|
|
3443
|
+
});
|
|
3444
|
+
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
3445
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
3446
|
+
try {
|
|
3447
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3448
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
|
|
3449
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3450
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3451
|
+
version: 1,
|
|
3452
|
+
active: true,
|
|
3453
|
+
skill: "deep-interview",
|
|
3454
|
+
phase: "planning",
|
|
3455
|
+
session_id: "sess-di-handoff",
|
|
3456
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
|
|
3457
|
+
});
|
|
3458
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3459
|
+
active: true,
|
|
3460
|
+
mode: "deep-interview",
|
|
3461
|
+
current_phase: "intent-first",
|
|
3462
|
+
session_id: "sess-di-handoff",
|
|
3463
|
+
});
|
|
3464
|
+
await dispatchCodexNativeHook({
|
|
3465
|
+
hook_event_name: "UserPromptSubmit",
|
|
3466
|
+
cwd,
|
|
3467
|
+
session_id: "sess-di-handoff",
|
|
3468
|
+
prompt: "$ralph implement the clarified spec in src/implementation.ts",
|
|
3469
|
+
}, { cwd });
|
|
3470
|
+
const result = await dispatchCodexNativeHook({
|
|
3471
|
+
hook_event_name: "PreToolUse",
|
|
3472
|
+
cwd,
|
|
3473
|
+
session_id: "sess-di-handoff",
|
|
3474
|
+
tool_name: "Edit",
|
|
3475
|
+
tool_use_id: "tool-di-post-handoff-edit",
|
|
3476
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
3477
|
+
}, { cwd });
|
|
3478
|
+
assert.equal(result.outputJson, null);
|
|
3479
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
3480
|
+
assert.equal(completed.active, false);
|
|
3481
|
+
}
|
|
3482
|
+
finally {
|
|
3483
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3251
3486
|
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
3252
3487
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
3253
3488
|
try {
|
|
@@ -5423,6 +5658,7 @@ exit 0
|
|
|
5423
5658
|
active: true,
|
|
5424
5659
|
current_phase: "team-exec",
|
|
5425
5660
|
team_name: "review-team",
|
|
5661
|
+
session_id: "sess-stop-team",
|
|
5426
5662
|
});
|
|
5427
5663
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
5428
5664
|
current_phase: "team-verify",
|
|
@@ -6216,6 +6452,63 @@ exit 0
|
|
|
6216
6452
|
await rm(cwd, { recursive: true, force: true });
|
|
6217
6453
|
}
|
|
6218
6454
|
});
|
|
6455
|
+
it("does not block Stop from canonical team state owned by another thread", async () => {
|
|
6456
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
|
|
6457
|
+
try {
|
|
6458
|
+
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" });
|
|
6459
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
|
|
6460
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6461
|
+
await writeJson(manifestPath, {
|
|
6462
|
+
...manifest,
|
|
6463
|
+
leader: {
|
|
6464
|
+
...manifest.leader,
|
|
6465
|
+
thread_id: "thread-other",
|
|
6466
|
+
},
|
|
6467
|
+
});
|
|
6468
|
+
const result = await dispatchCodexNativeHook({
|
|
6469
|
+
hook_event_name: "Stop",
|
|
6470
|
+
cwd,
|
|
6471
|
+
session_id: "sess-stop-team-canonical-thread",
|
|
6472
|
+
thread_id: "thread-current",
|
|
6473
|
+
}, { cwd });
|
|
6474
|
+
assert.equal(result.omxEventName, "stop");
|
|
6475
|
+
assert.equal(result.outputJson, null);
|
|
6476
|
+
}
|
|
6477
|
+
finally {
|
|
6478
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6479
|
+
}
|
|
6480
|
+
});
|
|
6481
|
+
it("blocks Stop from canonical team state owned by the current thread", async () => {
|
|
6482
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
|
|
6483
|
+
try {
|
|
6484
|
+
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" });
|
|
6485
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
|
|
6486
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6487
|
+
await writeJson(manifestPath, {
|
|
6488
|
+
...manifest,
|
|
6489
|
+
leader: {
|
|
6490
|
+
...manifest.leader,
|
|
6491
|
+
thread_id: "thread-current",
|
|
6492
|
+
},
|
|
6493
|
+
});
|
|
6494
|
+
const result = await dispatchCodexNativeHook({
|
|
6495
|
+
hook_event_name: "Stop",
|
|
6496
|
+
cwd,
|
|
6497
|
+
session_id: "sess-stop-team-canonical-current-thread",
|
|
6498
|
+
thread_id: "thread-current",
|
|
6499
|
+
}, { cwd });
|
|
6500
|
+
assert.equal(result.omxEventName, "stop");
|
|
6501
|
+
assert.deepEqual(result.outputJson, {
|
|
6502
|
+
decision: "block",
|
|
6503
|
+
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}`,
|
|
6504
|
+
stopReason: "team_team-exec",
|
|
6505
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
finally {
|
|
6509
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6510
|
+
}
|
|
6511
|
+
});
|
|
6219
6512
|
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 () => {
|
|
6220
6513
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
|
|
6221
6514
|
try {
|
|
@@ -6389,8 +6682,8 @@ exit 0
|
|
|
6389
6682
|
const sharedRoot = join(cwd, "shared-root");
|
|
6390
6683
|
const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
6391
6684
|
try {
|
|
6392
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
6393
|
-
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:
|
|
6685
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
|
|
6686
|
+
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 });
|
|
6394
6687
|
const result = await dispatchCodexNativeHook({
|
|
6395
6688
|
hook_event_name: "Stop",
|
|
6396
6689
|
cwd,
|
|
@@ -6447,13 +6740,14 @@ exit 0
|
|
|
6447
6740
|
});
|
|
6448
6741
|
it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
|
|
6449
6742
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
|
|
6743
|
+
const teamStateRoot = join(cwd, "shared-team-state");
|
|
6450
6744
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
6451
6745
|
try {
|
|
6452
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
6746
|
+
process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
|
|
6453
6747
|
await initTeamState("env-root-team", "env root stop fallback", "executor", 1, cwd, undefined, {
|
|
6454
6748
|
...process.env,
|
|
6455
6749
|
OMX_SESSION_ID: "sess-stop-team-env-root",
|
|
6456
|
-
OMX_TEAM_STATE_ROOT:
|
|
6750
|
+
OMX_TEAM_STATE_ROOT: teamStateRoot,
|
|
6457
6751
|
});
|
|
6458
6752
|
const result = await dispatchCodexNativeHook({
|
|
6459
6753
|
hook_event_name: "Stop",
|
|
@@ -9191,6 +9485,8 @@ exit 0
|
|
|
9191
9485
|
active: true,
|
|
9192
9486
|
current_phase: "team-exec",
|
|
9193
9487
|
team_name: "review-team",
|
|
9488
|
+
session_id: "sess-stop-team-refire",
|
|
9489
|
+
thread_id: "thread-stop-team-refire",
|
|
9194
9490
|
});
|
|
9195
9491
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
9196
9492
|
current_phase: "team-verify",
|
|
@@ -9379,6 +9675,214 @@ exit 0
|
|
|
9379
9675
|
await rm(cwd, { recursive: true, force: true });
|
|
9380
9676
|
}
|
|
9381
9677
|
});
|
|
9678
|
+
it("does not block Stop from root team state without team_name when no session is known", async () => {
|
|
9679
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
|
|
9680
|
+
try {
|
|
9681
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9682
|
+
await mkdir(stateDir, { recursive: true });
|
|
9683
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9684
|
+
active: true,
|
|
9685
|
+
mode: "team",
|
|
9686
|
+
current_phase: "starting",
|
|
9687
|
+
});
|
|
9688
|
+
const result = await dispatchCodexNativeHook({
|
|
9689
|
+
hook_event_name: "Stop",
|
|
9690
|
+
cwd,
|
|
9691
|
+
}, { cwd });
|
|
9692
|
+
assert.equal(result.omxEventName, "stop");
|
|
9693
|
+
assert.equal(result.outputJson, null);
|
|
9694
|
+
}
|
|
9695
|
+
finally {
|
|
9696
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9697
|
+
}
|
|
9698
|
+
});
|
|
9699
|
+
it("does not block Stop from root team state without team_name for a foreign session", async () => {
|
|
9700
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
|
|
9701
|
+
try {
|
|
9702
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9703
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9704
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9705
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9706
|
+
active: true,
|
|
9707
|
+
mode: "team",
|
|
9708
|
+
current_phase: "starting",
|
|
9709
|
+
});
|
|
9710
|
+
const result = await dispatchCodexNativeHook({
|
|
9711
|
+
hook_event_name: "Stop",
|
|
9712
|
+
cwd,
|
|
9713
|
+
session_id: "sess-current",
|
|
9714
|
+
thread_id: "thread-current",
|
|
9715
|
+
}, { cwd });
|
|
9716
|
+
assert.equal(result.omxEventName, "stop");
|
|
9717
|
+
assert.equal(result.outputJson, null);
|
|
9718
|
+
}
|
|
9719
|
+
finally {
|
|
9720
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9721
|
+
}
|
|
9722
|
+
});
|
|
9723
|
+
it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
|
|
9724
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
|
|
9725
|
+
try {
|
|
9726
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9727
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9728
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9729
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9730
|
+
active: true,
|
|
9731
|
+
current_phase: "starting",
|
|
9732
|
+
team_name: "stale-root-thread-team",
|
|
9733
|
+
session_id: "sess-current",
|
|
9734
|
+
thread_id: "thread-other",
|
|
9735
|
+
});
|
|
9736
|
+
await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
|
|
9737
|
+
current_phase: "team-exec",
|
|
9738
|
+
max_fix_attempts: 3,
|
|
9739
|
+
current_fix_attempt: 0,
|
|
9740
|
+
transitions: [],
|
|
9741
|
+
updated_at: new Date().toISOString(),
|
|
9742
|
+
});
|
|
9743
|
+
const result = await dispatchCodexNativeHook({
|
|
9744
|
+
hook_event_name: "Stop",
|
|
9745
|
+
cwd,
|
|
9746
|
+
session_id: "sess-current",
|
|
9747
|
+
thread_id: "thread-current",
|
|
9748
|
+
}, { cwd });
|
|
9749
|
+
assert.equal(result.omxEventName, "stop");
|
|
9750
|
+
assert.equal(result.outputJson, null);
|
|
9751
|
+
}
|
|
9752
|
+
finally {
|
|
9753
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9754
|
+
}
|
|
9755
|
+
});
|
|
9756
|
+
it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
|
|
9757
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
|
|
9758
|
+
try {
|
|
9759
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9760
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9761
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9762
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9763
|
+
active: true,
|
|
9764
|
+
current_phase: "starting",
|
|
9765
|
+
team_name: "root-missing-thread-team",
|
|
9766
|
+
session_id: "sess-current",
|
|
9767
|
+
});
|
|
9768
|
+
await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
|
|
9769
|
+
current_phase: "team-exec",
|
|
9770
|
+
max_fix_attempts: 3,
|
|
9771
|
+
current_fix_attempt: 0,
|
|
9772
|
+
transitions: [],
|
|
9773
|
+
updated_at: new Date().toISOString(),
|
|
9774
|
+
});
|
|
9775
|
+
const result = await dispatchCodexNativeHook({
|
|
9776
|
+
hook_event_name: "Stop",
|
|
9777
|
+
cwd,
|
|
9778
|
+
session_id: "sess-current",
|
|
9779
|
+
thread_id: "thread-current",
|
|
9780
|
+
}, { cwd });
|
|
9781
|
+
assert.equal(result.omxEventName, "stop");
|
|
9782
|
+
assert.equal(result.outputJson, null);
|
|
9783
|
+
}
|
|
9784
|
+
finally {
|
|
9785
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9786
|
+
}
|
|
9787
|
+
});
|
|
9788
|
+
it("does not block Stop from root team state when canonical phase is missing", async () => {
|
|
9789
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
|
|
9790
|
+
try {
|
|
9791
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9792
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9793
|
+
await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
|
|
9794
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9795
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
9796
|
+
active: true,
|
|
9797
|
+
current_phase: "starting",
|
|
9798
|
+
team_name: "root-missing-phase-team",
|
|
9799
|
+
session_id: "sess-current",
|
|
9800
|
+
thread_id: "thread-current",
|
|
9801
|
+
});
|
|
9802
|
+
const result = await dispatchCodexNativeHook({
|
|
9803
|
+
hook_event_name: "Stop",
|
|
9804
|
+
cwd,
|
|
9805
|
+
session_id: "sess-current",
|
|
9806
|
+
thread_id: "thread-current",
|
|
9807
|
+
}, { cwd });
|
|
9808
|
+
assert.equal(result.omxEventName, "stop");
|
|
9809
|
+
assert.equal(result.outputJson, null);
|
|
9810
|
+
}
|
|
9811
|
+
finally {
|
|
9812
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9813
|
+
}
|
|
9814
|
+
});
|
|
9815
|
+
it("does not block Stop from session-scoped team state owned by another thread", async () => {
|
|
9816
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
|
|
9817
|
+
try {
|
|
9818
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9819
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9820
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9821
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
9822
|
+
active: true,
|
|
9823
|
+
current_phase: "starting",
|
|
9824
|
+
team_name: "scoped-other-thread-team",
|
|
9825
|
+
session_id: "sess-current",
|
|
9826
|
+
thread_id: "thread-other",
|
|
9827
|
+
});
|
|
9828
|
+
await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
|
|
9829
|
+
current_phase: "team-exec",
|
|
9830
|
+
max_fix_attempts: 3,
|
|
9831
|
+
current_fix_attempt: 0,
|
|
9832
|
+
transitions: [],
|
|
9833
|
+
updated_at: new Date().toISOString(),
|
|
9834
|
+
});
|
|
9835
|
+
const result = await dispatchCodexNativeHook({
|
|
9836
|
+
hook_event_name: "Stop",
|
|
9837
|
+
cwd,
|
|
9838
|
+
session_id: "sess-current",
|
|
9839
|
+
thread_id: "thread-current",
|
|
9840
|
+
}, { cwd });
|
|
9841
|
+
assert.equal(result.omxEventName, "stop");
|
|
9842
|
+
assert.equal(result.outputJson, null);
|
|
9843
|
+
}
|
|
9844
|
+
finally {
|
|
9845
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9846
|
+
}
|
|
9847
|
+
});
|
|
9848
|
+
it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
|
|
9849
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
|
|
9850
|
+
try {
|
|
9851
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9852
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
9853
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
9854
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
9855
|
+
active: true,
|
|
9856
|
+
current_phase: "starting",
|
|
9857
|
+
team_name: "scoped-current-team",
|
|
9858
|
+
session_id: "sess-current",
|
|
9859
|
+
thread_id: "thread-current",
|
|
9860
|
+
});
|
|
9861
|
+
await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
|
|
9862
|
+
current_phase: "team-exec",
|
|
9863
|
+
max_fix_attempts: 3,
|
|
9864
|
+
current_fix_attempt: 0,
|
|
9865
|
+
transitions: [],
|
|
9866
|
+
updated_at: new Date().toISOString(),
|
|
9867
|
+
});
|
|
9868
|
+
const result = await dispatchCodexNativeHook({
|
|
9869
|
+
hook_event_name: "Stop",
|
|
9870
|
+
cwd,
|
|
9871
|
+
session_id: "sess-current",
|
|
9872
|
+
thread_id: "thread-current",
|
|
9873
|
+
}, { cwd });
|
|
9874
|
+
assert.equal(result.omxEventName, "stop");
|
|
9875
|
+
assert.deepEqual(result.outputJson, {
|
|
9876
|
+
decision: "block",
|
|
9877
|
+
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}`,
|
|
9878
|
+
stopReason: "team_team-exec",
|
|
9879
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
9880
|
+
});
|
|
9881
|
+
}
|
|
9882
|
+
finally {
|
|
9883
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9884
|
+
}
|
|
9885
|
+
});
|
|
9382
9886
|
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
9383
9887
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
9384
9888
|
try {
|
|
@@ -9561,6 +10065,79 @@ describe("codex native hook triage integration", () => {
|
|
|
9561
10065
|
await rm(cwd, { recursive: true, force: true });
|
|
9562
10066
|
}
|
|
9563
10067
|
});
|
|
10068
|
+
it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
|
|
10069
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
|
|
10070
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
|
|
10071
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
10072
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
10073
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
10074
|
+
try {
|
|
10075
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
10076
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10077
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10078
|
+
const boxedStateDir = getBaseStateDir(cwd);
|
|
10079
|
+
await mkdir(boxedStateDir, { recursive: true });
|
|
10080
|
+
await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
|
|
10081
|
+
schemaVersion: 1,
|
|
10082
|
+
sessions: {
|
|
10083
|
+
"omx-parent-session": {
|
|
10084
|
+
session_id: "omx-parent-session",
|
|
10085
|
+
leader_thread_id: "parent-native-thread",
|
|
10086
|
+
updated_at: "2026-05-21T19:04:40.000Z",
|
|
10087
|
+
threads: {
|
|
10088
|
+
"parent-native-thread": {
|
|
10089
|
+
thread_id: "parent-native-thread",
|
|
10090
|
+
kind: "leader",
|
|
10091
|
+
first_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10092
|
+
last_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10093
|
+
turn_count: 1,
|
|
10094
|
+
},
|
|
10095
|
+
"child-native-session": {
|
|
10096
|
+
thread_id: "child-native-session",
|
|
10097
|
+
kind: "subagent",
|
|
10098
|
+
first_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10099
|
+
last_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10100
|
+
turn_count: 1,
|
|
10101
|
+
mode: "review",
|
|
10102
|
+
},
|
|
10103
|
+
},
|
|
10104
|
+
},
|
|
10105
|
+
},
|
|
10106
|
+
});
|
|
10107
|
+
const result = await dispatchCodexNativeHook({
|
|
10108
|
+
hook_event_name: "UserPromptSubmit",
|
|
10109
|
+
cwd,
|
|
10110
|
+
session_id: "child-native-session",
|
|
10111
|
+
thread_id: "child-native-session",
|
|
10112
|
+
turn_id: "turn-subagent-review",
|
|
10113
|
+
prompt: [
|
|
10114
|
+
"Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
|
|
10115
|
+
"Context: The user asked for $autopilot, and this subagent must only review the patch.",
|
|
10116
|
+
].join("\n\n"),
|
|
10117
|
+
}, { cwd });
|
|
10118
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10119
|
+
assert.equal(additionalContext, "");
|
|
10120
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")), false);
|
|
10121
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")), false);
|
|
10122
|
+
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");
|
|
10123
|
+
}
|
|
10124
|
+
finally {
|
|
10125
|
+
if (originalOmxRoot === undefined)
|
|
10126
|
+
delete process.env.OMX_ROOT;
|
|
10127
|
+
else
|
|
10128
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
10129
|
+
if (originalOmxStateRoot === undefined)
|
|
10130
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10131
|
+
else
|
|
10132
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
10133
|
+
if (originalTeamStateRoot === undefined)
|
|
10134
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10135
|
+
else
|
|
10136
|
+
process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
10137
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10138
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
10139
|
+
}
|
|
10140
|
+
});
|
|
9564
10141
|
it("does not inject triage advisory for autopilot keyword prompts", async () => {
|
|
9565
10142
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
|
|
9566
10143
|
try {
|
|
@@ -9585,6 +10162,48 @@ describe("codex native hook triage integration", () => {
|
|
|
9585
10162
|
await rm(cwd, { recursive: true, force: true });
|
|
9586
10163
|
}
|
|
9587
10164
|
});
|
|
10165
|
+
it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
|
|
10166
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
|
|
10167
|
+
try {
|
|
10168
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
10169
|
+
await writeSessionStart(cwd, "sess-autopilot-observable");
|
|
10170
|
+
const result = await dispatchCodexNativeHook({
|
|
10171
|
+
hook_event_name: "UserPromptSubmit",
|
|
10172
|
+
cwd,
|
|
10173
|
+
session_id: "sess-autopilot-observable",
|
|
10174
|
+
thread_id: "thread-autopilot-observable",
|
|
10175
|
+
turn_id: "turn-autopilot-observable",
|
|
10176
|
+
prompt: "$autopilot implement issue #2430",
|
|
10177
|
+
}, { cwd });
|
|
10178
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
10179
|
+
assert.equal(result.skillState?.phase, "deep-interview");
|
|
10180
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
|
|
10181
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10182
|
+
assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
|
|
10183
|
+
assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
|
|
10184
|
+
assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
|
|
10185
|
+
assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
|
|
10186
|
+
assert.match(additionalContext, /Codex goal-mode handoff guidance/);
|
|
10187
|
+
assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
|
|
10188
|
+
const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
|
|
10189
|
+
const modeState = JSON.parse(await readFile(statePath, "utf-8"));
|
|
10190
|
+
assert.equal(modeState.active, true);
|
|
10191
|
+
assert.equal(modeState.current_phase, "deep-interview");
|
|
10192
|
+
assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
|
|
10193
|
+
assert.deepEqual(modeState.state?.deep_interview_gate, {
|
|
10194
|
+
status: "required",
|
|
10195
|
+
skip_reason: null,
|
|
10196
|
+
rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
|
|
10197
|
+
});
|
|
10198
|
+
const hudState = await readAllState(cwd);
|
|
10199
|
+
assert.equal(hudState.autopilot?.active, true);
|
|
10200
|
+
assert.equal(hudState.autopilot?.current_phase, "deep-interview");
|
|
10201
|
+
assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
|
|
10202
|
+
}
|
|
10203
|
+
finally {
|
|
10204
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10205
|
+
}
|
|
10206
|
+
});
|
|
9588
10207
|
// ── Group 2: HEAVY injection ─────────────────────────────────────────────
|
|
9589
10208
|
it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
|
|
9590
10209
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-heavy-"));
|