oh-my-codex 0.18.1 → 0.18.3
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 +23 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +20 -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 +40 -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/auth/__tests__/config-sessions.test.d.ts +2 -0
- package/dist/auth/__tests__/config-sessions.test.d.ts.map +1 -0
- package/dist/auth/__tests__/config-sessions.test.js +48 -0
- package/dist/auth/__tests__/config-sessions.test.js.map +1 -0
- package/dist/auth/__tests__/quota-rotation.test.d.ts +2 -0
- package/dist/auth/__tests__/quota-rotation.test.d.ts.map +1 -0
- package/dist/auth/__tests__/quota-rotation.test.js +33 -0
- package/dist/auth/__tests__/quota-rotation.test.js.map +1 -0
- package/dist/auth/__tests__/redact.test.d.ts +2 -0
- package/dist/auth/__tests__/redact.test.d.ts.map +1 -0
- package/dist/auth/__tests__/redact.test.js +20 -0
- package/dist/auth/__tests__/redact.test.js.map +1 -0
- package/dist/auth/__tests__/storage.test.d.ts +2 -0
- package/dist/auth/__tests__/storage.test.d.ts.map +1 -0
- package/dist/auth/__tests__/storage.test.js +108 -0
- package/dist/auth/__tests__/storage.test.js.map +1 -0
- package/dist/auth/config.d.ts +9 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +77 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/hotswap.d.ts +36 -0
- package/dist/auth/hotswap.d.ts.map +1 -0
- package/dist/auth/hotswap.js +159 -0
- package/dist/auth/hotswap.js.map +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/paths.d.ts +12 -0
- package/dist/auth/paths.d.ts.map +1 -0
- package/dist/auth/paths.js +78 -0
- package/dist/auth/paths.js.map +1 -0
- package/dist/auth/quota-detector.d.ts +10 -0
- package/dist/auth/quota-detector.d.ts.map +1 -0
- package/dist/auth/quota-detector.js +40 -0
- package/dist/auth/quota-detector.js.map +1 -0
- package/dist/auth/redact.d.ts +2 -0
- package/dist/auth/redact.d.ts.map +1 -0
- package/dist/auth/redact.js +26 -0
- package/dist/auth/redact.js.map +1 -0
- package/dist/auth/rotation.d.ts +9 -0
- package/dist/auth/rotation.d.ts.map +1 -0
- package/dist/auth/rotation.js +26 -0
- package/dist/auth/rotation.js.map +1 -0
- package/dist/auth/sessions.d.ts +15 -0
- package/dist/auth/sessions.d.ts.map +1 -0
- package/dist/auth/sessions.js +62 -0
- package/dist/auth/sessions.js.map +1 -0
- package/dist/auth/storage.d.ts +27 -0
- package/dist/auth/storage.d.ts.map +1 -0
- package/dist/auth/storage.js +111 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/catalog/__tests__/generator.test.js +4 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.d.ts +2 -0
- package/dist/cli/__tests__/auth.test.d.ts.map +1 -0
- package/dist/cli/__tests__/auth.test.js +168 -0
- package/dist/cli/__tests__/auth.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +112 -5
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +20 -0
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +171 -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__/nested-help-routing.test.js +1 -0
- package/dist/cli/__tests__/nested-help-routing.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/__tests__/setup-agents-overwrite.test.js +30 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +47 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/auth.d.ts +4 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +89 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +190 -7
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +12 -0
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +27 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +245 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +11 -3
- package/dist/cli/setup.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/__tests__/deep-interview.test.d.ts +2 -0
- package/dist/config/__tests__/deep-interview.test.d.ts.map +1 -0
- package/dist/config/__tests__/deep-interview.test.js +239 -0
- package/dist/config/__tests__/deep-interview.test.js.map +1 -0
- package/dist/config/__tests__/generator-idempotent.test.js +123 -0
- package/dist/config/__tests__/generator-idempotent.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/deep-interview.d.ts +22 -0
- package/dist/config/deep-interview.d.ts.map +1 -0
- package/dist/config/deep-interview.js +151 -0
- package/dist/config/deep-interview.js.map +1 -0
- package/dist/config/generator.d.ts +19 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +198 -29
- 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__/agents-overlay.test.js +2 -0
- package/dist/hooks/__tests__/agents-overlay.test.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__/explore-routing.test.js +1 -0
- package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +471 -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/deep-interview-config-instruction.d.ts +3 -0
- package/dist/hooks/deep-interview-config-instruction.d.ts.map +1 -0
- package/dist/hooks/deep-interview-config-instruction.js +47 -0
- package/dist/hooks/deep-interview-config-instruction.js.map +1 -0
- package/dist/hooks/explore-routing.d.ts.map +1 -1
- package/dist/hooks/explore-routing.js +1 -0
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +6 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +80 -14
- 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 +213 -17
- 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 +171 -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 +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +14 -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 +17 -3
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +96 -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/deep-interview.d.ts +2 -0
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.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 +1034 -28
- 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 +238 -36
- 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__/planning-gate.test.d.ts +2 -0
- package/dist/state/__tests__/planning-gate.test.d.ts.map +1 -0
- package/dist/state/__tests__/planning-gate.test.js +219 -0
- package/dist/state/__tests__/planning-gate.test.js.map +1 -0
- 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 +24 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +70 -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 +144 -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 +22 -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/deep-interview/SKILL.md +10 -0
- 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 +24 -5
- 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/prompts/scholastic.md +11 -0
- 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/deep-interview/SKILL.md +10 -0
- 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 +24 -5
- package/src/scripts/__tests__/codex-native-hook.test.ts +1307 -61
- 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 +260 -31
- package/src/scripts/notify-dispatcher.ts +202 -4
- package/src/scripts/run-test-files.ts +13 -0
- package/templates/catalog-manifest.json +27 -0
|
@@ -14,10 +14,13 @@ import { writeSessionStart } from "../../hooks/session.js";
|
|
|
14
14
|
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
|
+
import { OMX_TMUX_HUD_LEADER_PANE_ENV } from "../../hud/tmux.js";
|
|
17
18
|
import { readAllState } from "../../hud/state.js";
|
|
19
|
+
import { renderHud } from "../../hud/render.js";
|
|
18
20
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
19
21
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
20
22
|
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
23
|
+
import { getBaseStateDir } from "../../state/paths.js";
|
|
21
24
|
function nativeHookScriptPath() {
|
|
22
25
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
23
26
|
}
|
|
@@ -40,6 +43,21 @@ async function writeJson(path, value) {
|
|
|
40
43
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
41
44
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
42
45
|
}
|
|
46
|
+
async function withIsolatedHome(prefix, run) {
|
|
47
|
+
const homeDir = await mkdtemp(join(tmpdir(), `omx-native-hook-home-${prefix}-`));
|
|
48
|
+
const previousHome = process.env.HOME;
|
|
49
|
+
try {
|
|
50
|
+
process.env.HOME = homeDir;
|
|
51
|
+
return await run(homeDir);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
if (typeof previousHome === "string")
|
|
55
|
+
process.env.HOME = previousHome;
|
|
56
|
+
else
|
|
57
|
+
delete process.env.HOME;
|
|
58
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
async function withLoreGuardConfig(value, prefix, run) {
|
|
44
62
|
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
|
|
45
63
|
const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
|
|
@@ -222,7 +240,7 @@ describe("codex native hook config", () => {
|
|
|
222
240
|
assert.equal(sessionStart.matcher, "startup|resume|clear");
|
|
223
241
|
assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
|
|
224
242
|
const preToolUse = config.hooks.PreToolUse[0];
|
|
225
|
-
assert.equal(preToolUse.matcher,
|
|
243
|
+
assert.equal(preToolUse.matcher, undefined);
|
|
226
244
|
assert.match(String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
|
|
227
245
|
assert.equal(preToolUse.hooks?.[0]?.statusMessage, undefined);
|
|
228
246
|
const postToolUse = config.hooks.PostToolUse[0];
|
|
@@ -1289,6 +1307,308 @@ describe("codex native hook dispatch", () => {
|
|
|
1289
1307
|
await rm(cwd, { recursive: true, force: true });
|
|
1290
1308
|
}
|
|
1291
1309
|
});
|
|
1310
|
+
it("injects deep-interview config overrides into UserPromptSubmit developer context", async () => {
|
|
1311
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-"));
|
|
1312
|
+
try {
|
|
1313
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1314
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1315
|
+
defaultProfile = "standard"
|
|
1316
|
+
standardThreshold = 0.05
|
|
1317
|
+
standardMaxRounds = 15
|
|
1318
|
+
enableChallengeModes = false
|
|
1319
|
+
`);
|
|
1320
|
+
const result = await dispatchCodexNativeHook({
|
|
1321
|
+
hook_event_name: "UserPromptSubmit",
|
|
1322
|
+
cwd,
|
|
1323
|
+
session_id: "sess-deep-interview-config",
|
|
1324
|
+
thread_id: "thread-1",
|
|
1325
|
+
turn_id: "turn-1",
|
|
1326
|
+
prompt: "$deep-interview prove config reflection",
|
|
1327
|
+
}, { cwd });
|
|
1328
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1329
|
+
assert.equal(result.skillState?.skill, "deep-interview");
|
|
1330
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1331
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1332
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
1333
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
1334
|
+
assert.match(serializedOutput, /enableChallengeModes=false/);
|
|
1335
|
+
const modeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", "sess-deep-interview-config", "deep-interview-state.json"), "utf-8"));
|
|
1336
|
+
assert.equal(modeState.profile, "standard");
|
|
1337
|
+
assert.equal(modeState.threshold, 0.05);
|
|
1338
|
+
assert.equal(modeState.max_rounds, 15);
|
|
1339
|
+
}
|
|
1340
|
+
finally {
|
|
1341
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
it("proves UserPromptSubmit context changes before and after adding deep-interview config", async () => {
|
|
1345
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-before-after-"));
|
|
1346
|
+
const sessionId = "sess-deep-interview-config-before-after";
|
|
1347
|
+
try {
|
|
1348
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1349
|
+
const before = await withIsolatedHome("deep-interview-config-before-after", async () => (dispatchCodexNativeHook({
|
|
1350
|
+
hook_event_name: "UserPromptSubmit",
|
|
1351
|
+
cwd,
|
|
1352
|
+
session_id: sessionId,
|
|
1353
|
+
thread_id: "thread-before-after",
|
|
1354
|
+
turn_id: "turn-before",
|
|
1355
|
+
prompt: "$deep-interview prove before config context",
|
|
1356
|
+
}, { cwd })));
|
|
1357
|
+
const beforeOutput = JSON.stringify(before.outputJson);
|
|
1358
|
+
const beforeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1359
|
+
assert.equal(before.skillState?.skill, "deep-interview");
|
|
1360
|
+
assert.doesNotMatch(beforeOutput, /Deep-interview config override active/);
|
|
1361
|
+
assert.equal(before.skillState?.deep_interview_config, undefined);
|
|
1362
|
+
assert.equal(beforeState.deep_interview_config, undefined);
|
|
1363
|
+
assert.equal(beforeState.threshold, undefined);
|
|
1364
|
+
assert.equal(beforeState.max_rounds, undefined);
|
|
1365
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1366
|
+
defaultProfile = "standard"
|
|
1367
|
+
standardThreshold = 0.05
|
|
1368
|
+
standardMaxRounds = 15
|
|
1369
|
+
`);
|
|
1370
|
+
const after = await dispatchCodexNativeHook({
|
|
1371
|
+
hook_event_name: "UserPromptSubmit",
|
|
1372
|
+
cwd,
|
|
1373
|
+
session_id: sessionId,
|
|
1374
|
+
thread_id: "thread-before-after",
|
|
1375
|
+
turn_id: "turn-after",
|
|
1376
|
+
prompt: "$deep-interview prove after config context",
|
|
1377
|
+
}, { cwd });
|
|
1378
|
+
const afterOutput = JSON.stringify(after.outputJson);
|
|
1379
|
+
const afterState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1380
|
+
assert.equal(after.skillState?.deep_interview_config?.profile, "standard");
|
|
1381
|
+
assert.match(afterOutput, /Deep-interview config override active/);
|
|
1382
|
+
assert.match(afterOutput, /threshold=0\.05/);
|
|
1383
|
+
assert.match(afterOutput, /max_rounds=15/);
|
|
1384
|
+
assert.equal(afterState.deep_interview_config?.profile, "standard");
|
|
1385
|
+
assert.equal(afterState.threshold, 0.05);
|
|
1386
|
+
assert.equal(afterState.max_rounds, 15);
|
|
1387
|
+
}
|
|
1388
|
+
finally {
|
|
1389
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
it("injects deep-interview config for mixed workflow prompts that defer execution modes", async () => {
|
|
1393
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-mixed-"));
|
|
1394
|
+
const sessionId = "sess-deep-interview-config-mixed";
|
|
1395
|
+
try {
|
|
1396
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1397
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1398
|
+
defaultProfile = "deep"
|
|
1399
|
+
deepThreshold = 0.13
|
|
1400
|
+
deepMaxRounds = 21
|
|
1401
|
+
enableChallengeModes = false
|
|
1402
|
+
`);
|
|
1403
|
+
const result = await withIsolatedHome("deep-interview-config-mixed", async () => (dispatchCodexNativeHook({
|
|
1404
|
+
hook_event_name: "UserPromptSubmit",
|
|
1405
|
+
cwd,
|
|
1406
|
+
session_id: sessionId,
|
|
1407
|
+
thread_id: "thread-mixed-config",
|
|
1408
|
+
turn_id: "turn-mixed-config",
|
|
1409
|
+
prompt: "$autopilot $deep-interview prove mixed config context",
|
|
1410
|
+
}, { cwd })));
|
|
1411
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1412
|
+
const modeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1413
|
+
assert.equal(result.skillState?.skill, "deep-interview");
|
|
1414
|
+
assert.deepEqual(result.skillState?.deferred_skills, ["autopilot"]);
|
|
1415
|
+
assert.equal(result.skillState?.deep_interview_config?.profile, "deep");
|
|
1416
|
+
assert.equal(result.skillState?.deep_interview_config?.threshold, 0.13);
|
|
1417
|
+
assert.equal(result.skillState?.deep_interview_config?.maxRounds, 21);
|
|
1418
|
+
assert.equal(result.skillState?.deep_interview_config?.enableChallengeModes, false);
|
|
1419
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1420
|
+
assert.match(serializedOutput, /profile=deep/);
|
|
1421
|
+
assert.match(serializedOutput, /threshold=0\.13/);
|
|
1422
|
+
assert.match(serializedOutput, /max_rounds=21/);
|
|
1423
|
+
assert.match(serializedOutput, /enableChallengeModes=false/);
|
|
1424
|
+
assert.equal(modeState.deep_interview_config?.profile, "deep");
|
|
1425
|
+
assert.equal(modeState.profile, "deep");
|
|
1426
|
+
assert.equal(modeState.threshold, 0.13);
|
|
1427
|
+
assert.equal(modeState.max_rounds, 21);
|
|
1428
|
+
assert.equal(modeState.enable_challenge_modes, false);
|
|
1429
|
+
}
|
|
1430
|
+
finally {
|
|
1431
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
it("keeps deep-interview config override context on continuation prompts", async () => {
|
|
1435
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-continuation-"));
|
|
1436
|
+
const sessionId = "sess-deep-interview-config-continuation";
|
|
1437
|
+
try {
|
|
1438
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1439
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1440
|
+
defaultProfile = "standard"
|
|
1441
|
+
standardThreshold = 0.05
|
|
1442
|
+
standardMaxRounds = 15
|
|
1443
|
+
`);
|
|
1444
|
+
await dispatchCodexNativeHook({
|
|
1445
|
+
hook_event_name: "UserPromptSubmit",
|
|
1446
|
+
cwd,
|
|
1447
|
+
session_id: sessionId,
|
|
1448
|
+
thread_id: "thread-continuation",
|
|
1449
|
+
turn_id: "turn-start",
|
|
1450
|
+
prompt: "$deep-interview prove config continuation",
|
|
1451
|
+
}, { cwd });
|
|
1452
|
+
const continued = await dispatchCodexNativeHook({
|
|
1453
|
+
hook_event_name: "UserPromptSubmit",
|
|
1454
|
+
cwd,
|
|
1455
|
+
session_id: sessionId,
|
|
1456
|
+
thread_id: "thread-continuation",
|
|
1457
|
+
turn_id: "turn-continue",
|
|
1458
|
+
prompt: "continue",
|
|
1459
|
+
}, { cwd });
|
|
1460
|
+
const serializedOutput = JSON.stringify(continued.outputJson);
|
|
1461
|
+
const modeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1462
|
+
assert.equal(continued.skillState?.skill, "deep-interview");
|
|
1463
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1464
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
1465
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
1466
|
+
assert.equal(modeState.profile, "standard");
|
|
1467
|
+
assert.equal(modeState.threshold, 0.05);
|
|
1468
|
+
assert.equal(modeState.max_rounds, 15);
|
|
1469
|
+
}
|
|
1470
|
+
finally {
|
|
1471
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
it("keeps explicit deep-interview profile flags reflected on continuation prompts", async () => {
|
|
1475
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-profile-continuation-"));
|
|
1476
|
+
const sessionId = "sess-deep-interview-config-profile-continuation";
|
|
1477
|
+
try {
|
|
1478
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1479
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1480
|
+
defaultProfile = "standard"
|
|
1481
|
+
standardThreshold = 0.22
|
|
1482
|
+
standardMaxRounds = 13
|
|
1483
|
+
deepThreshold = 0.13
|
|
1484
|
+
deepMaxRounds = 21
|
|
1485
|
+
`);
|
|
1486
|
+
await dispatchCodexNativeHook({
|
|
1487
|
+
hook_event_name: "UserPromptSubmit",
|
|
1488
|
+
cwd,
|
|
1489
|
+
session_id: sessionId,
|
|
1490
|
+
thread_id: "thread-profile-continuation",
|
|
1491
|
+
turn_id: "turn-start",
|
|
1492
|
+
prompt: "$deep-interview --deep prove explicit profile continuation",
|
|
1493
|
+
}, { cwd });
|
|
1494
|
+
const continued = await dispatchCodexNativeHook({
|
|
1495
|
+
hook_event_name: "UserPromptSubmit",
|
|
1496
|
+
cwd,
|
|
1497
|
+
session_id: sessionId,
|
|
1498
|
+
thread_id: "thread-profile-continuation",
|
|
1499
|
+
turn_id: "turn-continue",
|
|
1500
|
+
prompt: "continue",
|
|
1501
|
+
}, { cwd });
|
|
1502
|
+
const serializedOutput = JSON.stringify(continued.outputJson);
|
|
1503
|
+
const modeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1504
|
+
assert.equal(continued.skillState?.skill, "deep-interview");
|
|
1505
|
+
assert.equal(continued.skillState?.deep_interview_config?.profile, "deep");
|
|
1506
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1507
|
+
assert.match(serializedOutput, /profile=deep/);
|
|
1508
|
+
assert.match(serializedOutput, /threshold=0\.13/);
|
|
1509
|
+
assert.match(serializedOutput, /max_rounds=21/);
|
|
1510
|
+
assert.equal(modeState.deep_interview_config?.profile, "deep");
|
|
1511
|
+
assert.equal(modeState.profile, "deep");
|
|
1512
|
+
assert.equal(modeState.threshold, 0.13);
|
|
1513
|
+
assert.equal(modeState.max_rounds, 21);
|
|
1514
|
+
}
|
|
1515
|
+
finally {
|
|
1516
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
it("keeps the documented deep-interview Suggested Config reflected in UserPromptSubmit context", async () => {
|
|
1520
|
+
const skillDoc = await readFile(join(process.cwd(), "skills", "deep-interview", "SKILL.md"), "utf-8");
|
|
1521
|
+
const markerIndex = skillDoc.indexOf("## Suggested Config (optional)");
|
|
1522
|
+
assert.notEqual(markerIndex, -1);
|
|
1523
|
+
const configMatch = skillDoc.slice(markerIndex).match(/```toml\n([\s\S]*?)\n```/);
|
|
1524
|
+
assert.ok(configMatch);
|
|
1525
|
+
const documentedConfig = configMatch[1]?.trimEnd();
|
|
1526
|
+
assert.ok(documentedConfig);
|
|
1527
|
+
assert.match(documentedConfig, /standardThreshold = 0\.20/);
|
|
1528
|
+
assert.match(documentedConfig, /standardMaxRounds = 12/);
|
|
1529
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-doc-config-"));
|
|
1530
|
+
const sessionId = "sess-deep-interview-doc-config";
|
|
1531
|
+
try {
|
|
1532
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1533
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `${documentedConfig}\n`);
|
|
1534
|
+
const result = await dispatchCodexNativeHook({
|
|
1535
|
+
hook_event_name: "UserPromptSubmit",
|
|
1536
|
+
cwd,
|
|
1537
|
+
session_id: sessionId,
|
|
1538
|
+
thread_id: "thread-doc-config",
|
|
1539
|
+
turn_id: "turn-doc-config",
|
|
1540
|
+
prompt: "$deep-interview prove documented config context",
|
|
1541
|
+
}, { cwd });
|
|
1542
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1543
|
+
const modeState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"));
|
|
1544
|
+
assert.equal(result.skillState?.deep_interview_config?.profile, "standard");
|
|
1545
|
+
assert.equal(result.skillState?.deep_interview_config?.threshold, 0.2);
|
|
1546
|
+
assert.equal(result.skillState?.deep_interview_config?.maxRounds, 12);
|
|
1547
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1548
|
+
assert.match(serializedOutput, /profile=standard/);
|
|
1549
|
+
assert.match(serializedOutput, /threshold=0\.2/);
|
|
1550
|
+
assert.match(serializedOutput, /max_rounds=12/);
|
|
1551
|
+
assert.equal(modeState.deep_interview_config?.profile, "standard");
|
|
1552
|
+
assert.equal(modeState.profile, "standard");
|
|
1553
|
+
assert.equal(modeState.threshold, 0.2);
|
|
1554
|
+
assert.equal(modeState.max_rounds, 12);
|
|
1555
|
+
}
|
|
1556
|
+
finally {
|
|
1557
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
it("injects deep-interview config overrides when state is boxed under OMX_ROOT", async () => {
|
|
1561
|
+
const root = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-boxed-"));
|
|
1562
|
+
const cwd = join(root, "source");
|
|
1563
|
+
const omxRoot = join(root, "box");
|
|
1564
|
+
const sessionId = "sess-boxed-deep-interview-config";
|
|
1565
|
+
const previousOmxRoot = process.env.OMX_ROOT;
|
|
1566
|
+
const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
1567
|
+
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
1568
|
+
try {
|
|
1569
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1570
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `[omx.deepInterview]
|
|
1571
|
+
defaultProfile = "standard"
|
|
1572
|
+
standardThreshold = 0.05
|
|
1573
|
+
standardMaxRounds = 15
|
|
1574
|
+
`);
|
|
1575
|
+
process.env.OMX_ROOT = omxRoot;
|
|
1576
|
+
delete process.env.OMX_STATE_ROOT;
|
|
1577
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
1578
|
+
const result = await dispatchCodexNativeHook({
|
|
1579
|
+
hook_event_name: "UserPromptSubmit",
|
|
1580
|
+
cwd,
|
|
1581
|
+
session_id: sessionId,
|
|
1582
|
+
thread_id: "thread-boxed",
|
|
1583
|
+
turn_id: "turn-boxed",
|
|
1584
|
+
prompt: "$deep-interview prove boxed config reflection",
|
|
1585
|
+
}, { cwd });
|
|
1586
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1587
|
+
assert.equal(result.skillState?.initialized_state_path, `.omx/state/sessions/${sessionId}/deep-interview-state.json`);
|
|
1588
|
+
const boxedStatePath = join(omxRoot, ".omx", "state", "sessions", sessionId, "deep-interview-state.json");
|
|
1589
|
+
assert.equal(existsSync(boxedStatePath), true);
|
|
1590
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json")), false);
|
|
1591
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1592
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1593
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
1594
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
1595
|
+
}
|
|
1596
|
+
finally {
|
|
1597
|
+
if (typeof previousOmxRoot === "string")
|
|
1598
|
+
process.env.OMX_ROOT = previousOmxRoot;
|
|
1599
|
+
else
|
|
1600
|
+
delete process.env.OMX_ROOT;
|
|
1601
|
+
if (typeof previousOmxStateRoot === "string")
|
|
1602
|
+
process.env.OMX_STATE_ROOT = previousOmxStateRoot;
|
|
1603
|
+
else
|
|
1604
|
+
delete process.env.OMX_STATE_ROOT;
|
|
1605
|
+
if (typeof previousTeamStateRoot === "string")
|
|
1606
|
+
process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
|
|
1607
|
+
else
|
|
1608
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
1609
|
+
await rm(root, { recursive: true, force: true });
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1292
1612
|
it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => {
|
|
1293
1613
|
const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-"));
|
|
1294
1614
|
const cwd = join(root, "source");
|
|
@@ -1593,6 +1913,9 @@ describe("codex native hook dispatch", () => {
|
|
|
1593
1913
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1594
1914
|
assert.match(output, /--status blocked/);
|
|
1595
1915
|
assert.match(output, /Codex goal context/);
|
|
1916
|
+
assert.match(output, /no such table: thread_goals/);
|
|
1917
|
+
assert.match(output, /unavailable get_goal error JSON or path/);
|
|
1918
|
+
assert.match(output, /safe-recovery blocker/);
|
|
1596
1919
|
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1597
1920
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1598
1921
|
}
|
|
@@ -1864,6 +2187,30 @@ describe("codex native hook dispatch", () => {
|
|
|
1864
2187
|
await rm(cwd, { recursive: true, force: true });
|
|
1865
2188
|
}
|
|
1866
2189
|
});
|
|
2190
|
+
it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
|
|
2191
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
|
|
2192
|
+
try {
|
|
2193
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2194
|
+
const result = await dispatchCodexNativeHook({
|
|
2195
|
+
hook_event_name: "UserPromptSubmit",
|
|
2196
|
+
cwd,
|
|
2197
|
+
session_id: "sess-autopilot-ralplan-gate",
|
|
2198
|
+
thread_id: "thread-autopilot-ralplan-gate",
|
|
2199
|
+
turn_id: "turn-autopilot-ralplan-gate",
|
|
2200
|
+
prompt: "$autopilot implement issue #2430",
|
|
2201
|
+
}, { cwd });
|
|
2202
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2203
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
2204
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
2205
|
+
assert.match(message, /Autopilot protocol:/);
|
|
2206
|
+
assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
|
|
2207
|
+
assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
|
|
2208
|
+
assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
|
|
2209
|
+
}
|
|
2210
|
+
finally {
|
|
2211
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
1867
2214
|
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
1868
2215
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
1869
2216
|
try {
|
|
@@ -1878,7 +2225,8 @@ describe("codex native hook dispatch", () => {
|
|
|
1878
2225
|
}, { cwd });
|
|
1879
2226
|
assert.equal(result.omxEventName, "keyword-detector");
|
|
1880
2227
|
assert.equal(result.skillState?.skill, "ultragoal");
|
|
1881
|
-
assert.equal(result.skillState?.initialized_mode,
|
|
2228
|
+
assert.equal(result.skillState?.initialized_mode, "ultragoal");
|
|
2229
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json");
|
|
1882
2230
|
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1883
2231
|
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
1884
2232
|
assert.match(message, /Ultragoal protocol:/);
|
|
@@ -1887,7 +2235,65 @@ describe("codex native hook dispatch", () => {
|
|
|
1887
2235
|
assert.match(message, /update_goal/);
|
|
1888
2236
|
assert.match(message, /does not call `\/goal clear`/);
|
|
1889
2237
|
assert.match(message, /multiple sequential ultragoal runs/);
|
|
1890
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")),
|
|
2238
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
|
|
2239
|
+
}
|
|
2240
|
+
finally {
|
|
2241
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2242
|
+
}
|
|
2243
|
+
});
|
|
2244
|
+
it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
|
|
2245
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
|
|
2246
|
+
try {
|
|
2247
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2248
|
+
const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
|
|
2249
|
+
await mkdir(sessionDir, { recursive: true });
|
|
2250
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
2251
|
+
version: 1,
|
|
2252
|
+
active: true,
|
|
2253
|
+
skill: "deep-interview",
|
|
2254
|
+
phase: "planning",
|
|
2255
|
+
session_id: "sess-ultragoal-handoff",
|
|
2256
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
|
|
2257
|
+
});
|
|
2258
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
2259
|
+
active: true,
|
|
2260
|
+
mode: "deep-interview",
|
|
2261
|
+
current_phase: "intent-first",
|
|
2262
|
+
session_id: "sess-ultragoal-handoff",
|
|
2263
|
+
question_enforcement: {
|
|
2264
|
+
obligation_id: "obligation-ultragoal-handoff",
|
|
2265
|
+
source: "omx-question",
|
|
2266
|
+
status: "pending",
|
|
2267
|
+
requested_at: "2026-05-21T03:00:00.000Z",
|
|
2268
|
+
},
|
|
2269
|
+
});
|
|
2270
|
+
const result = await dispatchCodexNativeHook({
|
|
2271
|
+
hook_event_name: "UserPromptSubmit",
|
|
2272
|
+
cwd,
|
|
2273
|
+
session_id: "sess-ultragoal-handoff",
|
|
2274
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2275
|
+
turn_id: "turn-ultragoal-handoff",
|
|
2276
|
+
prompt: "$ultragoal turn the clarified spec into goals",
|
|
2277
|
+
}, { cwd });
|
|
2278
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2279
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2280
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
|
|
2281
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
2282
|
+
assert.equal(completed.active, false);
|
|
2283
|
+
assert.equal(completed.current_phase, "completed");
|
|
2284
|
+
assert.equal(completed.question_enforcement?.status, "cleared");
|
|
2285
|
+
assert.equal(completed.question_enforcement?.clear_reason, "handoff");
|
|
2286
|
+
assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
|
|
2287
|
+
const edit = await dispatchCodexNativeHook({
|
|
2288
|
+
hook_event_name: "PreToolUse",
|
|
2289
|
+
cwd,
|
|
2290
|
+
session_id: "sess-ultragoal-handoff",
|
|
2291
|
+
thread_id: "thread-ultragoal-handoff",
|
|
2292
|
+
tool_name: "Edit",
|
|
2293
|
+
tool_use_id: "tool-ultragoal-post-handoff-edit",
|
|
2294
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
2295
|
+
}, { cwd });
|
|
2296
|
+
assert.equal(edit.outputJson, null);
|
|
1891
2297
|
}
|
|
1892
2298
|
finally {
|
|
1893
2299
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -2837,6 +3243,75 @@ esac
|
|
|
2837
3243
|
await rm(cwd, { recursive: true, force: true });
|
|
2838
3244
|
}
|
|
2839
3245
|
});
|
|
3246
|
+
it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
|
|
3247
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
|
|
3248
|
+
const originalTmux = process.env.TMUX;
|
|
3249
|
+
const originalTmuxPane = process.env.TMUX_PANE;
|
|
3250
|
+
const originalPath = process.env.PATH;
|
|
3251
|
+
const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
3252
|
+
try {
|
|
3253
|
+
process.env.TMUX = "1";
|
|
3254
|
+
process.env.TMUX_PANE = "%1";
|
|
3255
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
3256
|
+
const canonicalSessionId = "omx-canonical-hud-reuse";
|
|
3257
|
+
const nativeSessionId = "codex-native-hud-reuse";
|
|
3258
|
+
await mkdir(join(cwd, ".omx", "state", "sessions", canonicalSessionId), { recursive: true });
|
|
3259
|
+
await writeSessionStart(cwd, canonicalSessionId);
|
|
3260
|
+
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-bin-"));
|
|
3261
|
+
const tmuxLog = join(cwd, "tmux.log");
|
|
3262
|
+
await writeFile(join(binDir, "tmux"), `#!/usr/bin/env bash
|
|
3263
|
+
set -euo pipefail
|
|
3264
|
+
printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)}
|
|
3265
|
+
case "$1" in
|
|
3266
|
+
list-panes)
|
|
3267
|
+
printf '%%1\tcodex\tcodex\n'
|
|
3268
|
+
printf '%%2\tnode\texec env OMX_TMUX_HUD_OWNER='"'"'1'"'"' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='"'"'%%1'"'"' /node /omx.js hud --watch\n'
|
|
3269
|
+
;;
|
|
3270
|
+
display-message)
|
|
3271
|
+
printf '80\t24\n'
|
|
3272
|
+
;;
|
|
3273
|
+
resize-pane)
|
|
3274
|
+
;;
|
|
3275
|
+
split-window)
|
|
3276
|
+
printf '%%9\n'
|
|
3277
|
+
;;
|
|
3278
|
+
esac
|
|
3279
|
+
`);
|
|
3280
|
+
await chmod(join(binDir, "tmux"), 0o755);
|
|
3281
|
+
process.env.PATH = `${binDir}:${originalPath}`;
|
|
3282
|
+
const result = await dispatchCodexNativeHook({
|
|
3283
|
+
hook_event_name: "UserPromptSubmit",
|
|
3284
|
+
cwd,
|
|
3285
|
+
session_id: nativeSessionId,
|
|
3286
|
+
thread_id: "thread-hud-reuse",
|
|
3287
|
+
turn_id: "turn-hud-reuse",
|
|
3288
|
+
prompt: "$ralplan prepare plan",
|
|
3289
|
+
}, { cwd });
|
|
3290
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
3291
|
+
const tmuxCalls = await readFile(tmuxLog, "utf-8");
|
|
3292
|
+
assert.match(tmuxCalls, /list-panes -t %1 -F/);
|
|
3293
|
+
assert.match(tmuxCalls, /resize-pane -t %2 -y 3/);
|
|
3294
|
+
assert.doesNotMatch(tmuxCalls, /split-window/);
|
|
3295
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
|
|
3296
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
|
|
3297
|
+
}
|
|
3298
|
+
finally {
|
|
3299
|
+
if (originalTmux === undefined)
|
|
3300
|
+
delete process.env.TMUX;
|
|
3301
|
+
else
|
|
3302
|
+
process.env.TMUX = originalTmux;
|
|
3303
|
+
if (originalTmuxPane === undefined)
|
|
3304
|
+
delete process.env.TMUX_PANE;
|
|
3305
|
+
else
|
|
3306
|
+
process.env.TMUX_PANE = originalTmuxPane;
|
|
3307
|
+
if (originalHudOwner === undefined)
|
|
3308
|
+
delete process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
3309
|
+
else
|
|
3310
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
|
|
3311
|
+
process.env.PATH = originalPath;
|
|
3312
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
2840
3315
|
it("skips prompt-submit HUD reconciliation inside unowned tmux panes", async () => {
|
|
2841
3316
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-"));
|
|
2842
3317
|
const originalTmux = process.env.TMUX;
|
|
@@ -3248,6 +3723,153 @@ exit 0
|
|
|
3248
3723
|
await rm(cwd, { recursive: true, force: true });
|
|
3249
3724
|
}
|
|
3250
3725
|
});
|
|
3726
|
+
it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
|
|
3727
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
|
|
3728
|
+
try {
|
|
3729
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3730
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
|
|
3731
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3732
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
|
|
3733
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3734
|
+
version: 1,
|
|
3735
|
+
active: true,
|
|
3736
|
+
skill: "deep-interview",
|
|
3737
|
+
phase: "planning",
|
|
3738
|
+
session_id: "sess-di-edit-block",
|
|
3739
|
+
thread_id: "thread-di-edit-block",
|
|
3740
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
|
|
3741
|
+
});
|
|
3742
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3743
|
+
active: true,
|
|
3744
|
+
mode: "deep-interview",
|
|
3745
|
+
current_phase: "intent-first",
|
|
3746
|
+
session_id: "sess-di-edit-block",
|
|
3747
|
+
thread_id: "thread-di-edit-block",
|
|
3748
|
+
rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
|
|
3749
|
+
});
|
|
3750
|
+
const result = await dispatchCodexNativeHook({
|
|
3751
|
+
hook_event_name: "PreToolUse",
|
|
3752
|
+
cwd,
|
|
3753
|
+
session_id: "sess-di-edit-block",
|
|
3754
|
+
thread_id: "thread-di-edit-block",
|
|
3755
|
+
tool_name: "Edit",
|
|
3756
|
+
tool_use_id: "tool-di-edit-block",
|
|
3757
|
+
tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
|
|
3758
|
+
}, { cwd });
|
|
3759
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3760
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3761
|
+
assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active/);
|
|
3762
|
+
assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
|
|
3763
|
+
assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
|
|
3764
|
+
}
|
|
3765
|
+
finally {
|
|
3766
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
|
|
3770
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
|
|
3771
|
+
try {
|
|
3772
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3773
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
|
|
3774
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3775
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
|
|
3776
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3777
|
+
version: 1,
|
|
3778
|
+
active: true,
|
|
3779
|
+
skill: "deep-interview",
|
|
3780
|
+
phase: "planning",
|
|
3781
|
+
session_id: "sess-di-artifact",
|
|
3782
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
|
|
3783
|
+
});
|
|
3784
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3785
|
+
active: true,
|
|
3786
|
+
mode: "deep-interview",
|
|
3787
|
+
current_phase: "intent-first",
|
|
3788
|
+
session_id: "sess-di-artifact",
|
|
3789
|
+
});
|
|
3790
|
+
const allowedWrite = await dispatchCodexNativeHook({
|
|
3791
|
+
hook_event_name: "PreToolUse",
|
|
3792
|
+
cwd,
|
|
3793
|
+
session_id: "sess-di-artifact",
|
|
3794
|
+
tool_name: "Write",
|
|
3795
|
+
tool_use_id: "tool-di-spec-write",
|
|
3796
|
+
tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
|
|
3797
|
+
}, { cwd });
|
|
3798
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
3799
|
+
const allowedBash = await dispatchCodexNativeHook({
|
|
3800
|
+
hook_event_name: "PreToolUse",
|
|
3801
|
+
cwd,
|
|
3802
|
+
session_id: "sess-di-artifact",
|
|
3803
|
+
tool_name: "Bash",
|
|
3804
|
+
tool_use_id: "tool-di-context-bash",
|
|
3805
|
+
tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
|
|
3806
|
+
}, { cwd });
|
|
3807
|
+
assert.equal(allowedBash.outputJson, null);
|
|
3808
|
+
const allowedAppendBash = await dispatchCodexNativeHook({
|
|
3809
|
+
hook_event_name: "PreToolUse",
|
|
3810
|
+
cwd,
|
|
3811
|
+
session_id: "sess-di-artifact",
|
|
3812
|
+
tool_name: "Bash",
|
|
3813
|
+
tool_use_id: "tool-di-context-append-bash",
|
|
3814
|
+
tool_input: { command: "echo more context >> .omx/context/demo.md" },
|
|
3815
|
+
}, { cwd });
|
|
3816
|
+
assert.equal(allowedAppendBash.outputJson, null);
|
|
3817
|
+
const blockedBash = await dispatchCodexNativeHook({
|
|
3818
|
+
hook_event_name: "PreToolUse",
|
|
3819
|
+
cwd,
|
|
3820
|
+
session_id: "sess-di-artifact",
|
|
3821
|
+
tool_name: "Bash",
|
|
3822
|
+
tool_use_id: "tool-di-src-bash",
|
|
3823
|
+
tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
|
|
3824
|
+
}, { cwd });
|
|
3825
|
+
assert.equal(blockedBash.outputJson?.decision, "block");
|
|
3826
|
+
}
|
|
3827
|
+
finally {
|
|
3828
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
|
|
3832
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
|
|
3833
|
+
try {
|
|
3834
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3835
|
+
const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
|
|
3836
|
+
await mkdir(sessionDir, { recursive: true });
|
|
3837
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
3838
|
+
version: 1,
|
|
3839
|
+
active: true,
|
|
3840
|
+
skill: "deep-interview",
|
|
3841
|
+
phase: "planning",
|
|
3842
|
+
session_id: "sess-di-handoff",
|
|
3843
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
|
|
3844
|
+
});
|
|
3845
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
3846
|
+
active: true,
|
|
3847
|
+
mode: "deep-interview",
|
|
3848
|
+
current_phase: "intent-first",
|
|
3849
|
+
session_id: "sess-di-handoff",
|
|
3850
|
+
});
|
|
3851
|
+
await dispatchCodexNativeHook({
|
|
3852
|
+
hook_event_name: "UserPromptSubmit",
|
|
3853
|
+
cwd,
|
|
3854
|
+
session_id: "sess-di-handoff",
|
|
3855
|
+
prompt: "$ralph implement the clarified spec in src/implementation.ts",
|
|
3856
|
+
}, { cwd });
|
|
3857
|
+
const result = await dispatchCodexNativeHook({
|
|
3858
|
+
hook_event_name: "PreToolUse",
|
|
3859
|
+
cwd,
|
|
3860
|
+
session_id: "sess-di-handoff",
|
|
3861
|
+
tool_name: "Edit",
|
|
3862
|
+
tool_use_id: "tool-di-post-handoff-edit",
|
|
3863
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
3864
|
+
}, { cwd });
|
|
3865
|
+
assert.equal(result.outputJson, null);
|
|
3866
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
|
|
3867
|
+
assert.equal(completed.active, false);
|
|
3868
|
+
}
|
|
3869
|
+
finally {
|
|
3870
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3871
|
+
}
|
|
3872
|
+
});
|
|
3251
3873
|
it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
|
|
3252
3874
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
|
|
3253
3875
|
try {
|
|
@@ -5423,6 +6045,7 @@ exit 0
|
|
|
5423
6045
|
active: true,
|
|
5424
6046
|
current_phase: "team-exec",
|
|
5425
6047
|
team_name: "review-team",
|
|
6048
|
+
session_id: "sess-stop-team",
|
|
5426
6049
|
});
|
|
5427
6050
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
5428
6051
|
current_phase: "team-verify",
|
|
@@ -6176,38 +6799,95 @@ exit 0
|
|
|
6176
6799
|
assert.doesNotMatch(tmuxLog, /native Stop allowed/);
|
|
6177
6800
|
}
|
|
6178
6801
|
finally {
|
|
6179
|
-
if (typeof prevTeamWorker === "string")
|
|
6180
|
-
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
6181
|
-
else
|
|
6182
|
-
delete process.env.OMX_TEAM_WORKER;
|
|
6183
|
-
if (typeof prevInternalTeamWorker === "string")
|
|
6184
|
-
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
6185
|
-
else
|
|
6186
|
-
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
6187
|
-
if (typeof prevTeamStateRoot === "string")
|
|
6188
|
-
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
6189
|
-
else
|
|
6190
|
-
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
6191
|
-
if (typeof prevPath === "string")
|
|
6192
|
-
process.env.PATH = prevPath;
|
|
6193
|
-
else
|
|
6194
|
-
delete process.env.PATH;
|
|
6802
|
+
if (typeof prevTeamWorker === "string")
|
|
6803
|
+
process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
6804
|
+
else
|
|
6805
|
+
delete process.env.OMX_TEAM_WORKER;
|
|
6806
|
+
if (typeof prevInternalTeamWorker === "string")
|
|
6807
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = prevInternalTeamWorker;
|
|
6808
|
+
else
|
|
6809
|
+
delete process.env.OMX_TEAM_INTERNAL_WORKER;
|
|
6810
|
+
if (typeof prevTeamStateRoot === "string")
|
|
6811
|
+
process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
6812
|
+
else
|
|
6813
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
6814
|
+
if (typeof prevPath === "string")
|
|
6815
|
+
process.env.PATH = prevPath;
|
|
6816
|
+
else
|
|
6817
|
+
delete process.env.PATH;
|
|
6818
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6819
|
+
}
|
|
6820
|
+
});
|
|
6821
|
+
it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
|
|
6822
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
|
|
6823
|
+
try {
|
|
6824
|
+
await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
|
|
6825
|
+
const result = await dispatchCodexNativeHook({
|
|
6826
|
+
hook_event_name: "Stop",
|
|
6827
|
+
cwd,
|
|
6828
|
+
session_id: "sess-stop-team-canonical",
|
|
6829
|
+
}, { cwd });
|
|
6830
|
+
assert.equal(result.omxEventName, "stop");
|
|
6831
|
+
assert.deepEqual(result.outputJson, {
|
|
6832
|
+
decision: "block",
|
|
6833
|
+
reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
6834
|
+
stopReason: "team_team-exec",
|
|
6835
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
6836
|
+
});
|
|
6837
|
+
}
|
|
6838
|
+
finally {
|
|
6839
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6840
|
+
}
|
|
6841
|
+
});
|
|
6842
|
+
it("does not block Stop from canonical team state owned by another thread", async () => {
|
|
6843
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
|
|
6844
|
+
try {
|
|
6845
|
+
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" });
|
|
6846
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
|
|
6847
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6848
|
+
await writeJson(manifestPath, {
|
|
6849
|
+
...manifest,
|
|
6850
|
+
leader: {
|
|
6851
|
+
...manifest.leader,
|
|
6852
|
+
thread_id: "thread-other",
|
|
6853
|
+
},
|
|
6854
|
+
});
|
|
6855
|
+
const result = await dispatchCodexNativeHook({
|
|
6856
|
+
hook_event_name: "Stop",
|
|
6857
|
+
cwd,
|
|
6858
|
+
session_id: "sess-stop-team-canonical-thread",
|
|
6859
|
+
thread_id: "thread-current",
|
|
6860
|
+
}, { cwd });
|
|
6861
|
+
assert.equal(result.omxEventName, "stop");
|
|
6862
|
+
assert.equal(result.outputJson, null);
|
|
6863
|
+
}
|
|
6864
|
+
finally {
|
|
6195
6865
|
await rm(cwd, { recursive: true, force: true });
|
|
6196
6866
|
}
|
|
6197
6867
|
});
|
|
6198
|
-
it("
|
|
6199
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
|
|
6868
|
+
it("blocks Stop from canonical team state owned by the current thread", async () => {
|
|
6869
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
|
|
6200
6870
|
try {
|
|
6201
|
-
await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
|
|
6871
|
+
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" });
|
|
6872
|
+
const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
|
|
6873
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
6874
|
+
await writeJson(manifestPath, {
|
|
6875
|
+
...manifest,
|
|
6876
|
+
leader: {
|
|
6877
|
+
...manifest.leader,
|
|
6878
|
+
thread_id: "thread-current",
|
|
6879
|
+
},
|
|
6880
|
+
});
|
|
6202
6881
|
const result = await dispatchCodexNativeHook({
|
|
6203
6882
|
hook_event_name: "Stop",
|
|
6204
6883
|
cwd,
|
|
6205
|
-
session_id: "sess-stop-team-canonical",
|
|
6884
|
+
session_id: "sess-stop-team-canonical-current-thread",
|
|
6885
|
+
thread_id: "thread-current",
|
|
6206
6886
|
}, { cwd });
|
|
6207
6887
|
assert.equal(result.omxEventName, "stop");
|
|
6208
6888
|
assert.deepEqual(result.outputJson, {
|
|
6209
6889
|
decision: "block",
|
|
6210
|
-
reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
6890
|
+
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}`,
|
|
6211
6891
|
stopReason: "team_team-exec",
|
|
6212
6892
|
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
6213
6893
|
});
|
|
@@ -6389,8 +7069,8 @@ exit 0
|
|
|
6389
7069
|
const sharedRoot = join(cwd, "shared-root");
|
|
6390
7070
|
const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
6391
7071
|
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:
|
|
7072
|
+
process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
|
|
7073
|
+
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
7074
|
const result = await dispatchCodexNativeHook({
|
|
6395
7075
|
hook_event_name: "Stop",
|
|
6396
7076
|
cwd,
|
|
@@ -6447,13 +7127,14 @@ exit 0
|
|
|
6447
7127
|
});
|
|
6448
7128
|
it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
|
|
6449
7129
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
|
|
7130
|
+
const teamStateRoot = join(cwd, "shared-team-state");
|
|
6450
7131
|
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
6451
7132
|
try {
|
|
6452
|
-
process.env.OMX_TEAM_STATE_ROOT =
|
|
7133
|
+
process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
|
|
6453
7134
|
await initTeamState("env-root-team", "env root stop fallback", "executor", 1, cwd, undefined, {
|
|
6454
7135
|
...process.env,
|
|
6455
7136
|
OMX_SESSION_ID: "sess-stop-team-env-root",
|
|
6456
|
-
OMX_TEAM_STATE_ROOT:
|
|
7137
|
+
OMX_TEAM_STATE_ROOT: teamStateRoot,
|
|
6457
7138
|
});
|
|
6458
7139
|
const result = await dispatchCodexNativeHook({
|
|
6459
7140
|
hook_event_name: "Stop",
|
|
@@ -9191,6 +9872,8 @@ exit 0
|
|
|
9191
9872
|
active: true,
|
|
9192
9873
|
current_phase: "team-exec",
|
|
9193
9874
|
team_name: "review-team",
|
|
9875
|
+
session_id: "sess-stop-team-refire",
|
|
9876
|
+
thread_id: "thread-stop-team-refire",
|
|
9194
9877
|
});
|
|
9195
9878
|
await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
|
|
9196
9879
|
current_phase: "team-verify",
|
|
@@ -9379,6 +10062,214 @@ exit 0
|
|
|
9379
10062
|
await rm(cwd, { recursive: true, force: true });
|
|
9380
10063
|
}
|
|
9381
10064
|
});
|
|
10065
|
+
it("does not block Stop from root team state without team_name when no session is known", async () => {
|
|
10066
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
|
|
10067
|
+
try {
|
|
10068
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10069
|
+
await mkdir(stateDir, { recursive: true });
|
|
10070
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
10071
|
+
active: true,
|
|
10072
|
+
mode: "team",
|
|
10073
|
+
current_phase: "starting",
|
|
10074
|
+
});
|
|
10075
|
+
const result = await dispatchCodexNativeHook({
|
|
10076
|
+
hook_event_name: "Stop",
|
|
10077
|
+
cwd,
|
|
10078
|
+
}, { cwd });
|
|
10079
|
+
assert.equal(result.omxEventName, "stop");
|
|
10080
|
+
assert.equal(result.outputJson, null);
|
|
10081
|
+
}
|
|
10082
|
+
finally {
|
|
10083
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10084
|
+
}
|
|
10085
|
+
});
|
|
10086
|
+
it("does not block Stop from root team state without team_name for a foreign session", async () => {
|
|
10087
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
|
|
10088
|
+
try {
|
|
10089
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10090
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10091
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10092
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
10093
|
+
active: true,
|
|
10094
|
+
mode: "team",
|
|
10095
|
+
current_phase: "starting",
|
|
10096
|
+
});
|
|
10097
|
+
const result = await dispatchCodexNativeHook({
|
|
10098
|
+
hook_event_name: "Stop",
|
|
10099
|
+
cwd,
|
|
10100
|
+
session_id: "sess-current",
|
|
10101
|
+
thread_id: "thread-current",
|
|
10102
|
+
}, { cwd });
|
|
10103
|
+
assert.equal(result.omxEventName, "stop");
|
|
10104
|
+
assert.equal(result.outputJson, null);
|
|
10105
|
+
}
|
|
10106
|
+
finally {
|
|
10107
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10108
|
+
}
|
|
10109
|
+
});
|
|
10110
|
+
it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
|
|
10111
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
|
|
10112
|
+
try {
|
|
10113
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10114
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10115
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10116
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
10117
|
+
active: true,
|
|
10118
|
+
current_phase: "starting",
|
|
10119
|
+
team_name: "stale-root-thread-team",
|
|
10120
|
+
session_id: "sess-current",
|
|
10121
|
+
thread_id: "thread-other",
|
|
10122
|
+
});
|
|
10123
|
+
await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
|
|
10124
|
+
current_phase: "team-exec",
|
|
10125
|
+
max_fix_attempts: 3,
|
|
10126
|
+
current_fix_attempt: 0,
|
|
10127
|
+
transitions: [],
|
|
10128
|
+
updated_at: new Date().toISOString(),
|
|
10129
|
+
});
|
|
10130
|
+
const result = await dispatchCodexNativeHook({
|
|
10131
|
+
hook_event_name: "Stop",
|
|
10132
|
+
cwd,
|
|
10133
|
+
session_id: "sess-current",
|
|
10134
|
+
thread_id: "thread-current",
|
|
10135
|
+
}, { cwd });
|
|
10136
|
+
assert.equal(result.omxEventName, "stop");
|
|
10137
|
+
assert.equal(result.outputJson, null);
|
|
10138
|
+
}
|
|
10139
|
+
finally {
|
|
10140
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10141
|
+
}
|
|
10142
|
+
});
|
|
10143
|
+
it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
|
|
10144
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
|
|
10145
|
+
try {
|
|
10146
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10147
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10148
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10149
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
10150
|
+
active: true,
|
|
10151
|
+
current_phase: "starting",
|
|
10152
|
+
team_name: "root-missing-thread-team",
|
|
10153
|
+
session_id: "sess-current",
|
|
10154
|
+
});
|
|
10155
|
+
await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
|
|
10156
|
+
current_phase: "team-exec",
|
|
10157
|
+
max_fix_attempts: 3,
|
|
10158
|
+
current_fix_attempt: 0,
|
|
10159
|
+
transitions: [],
|
|
10160
|
+
updated_at: new Date().toISOString(),
|
|
10161
|
+
});
|
|
10162
|
+
const result = await dispatchCodexNativeHook({
|
|
10163
|
+
hook_event_name: "Stop",
|
|
10164
|
+
cwd,
|
|
10165
|
+
session_id: "sess-current",
|
|
10166
|
+
thread_id: "thread-current",
|
|
10167
|
+
}, { cwd });
|
|
10168
|
+
assert.equal(result.omxEventName, "stop");
|
|
10169
|
+
assert.equal(result.outputJson, null);
|
|
10170
|
+
}
|
|
10171
|
+
finally {
|
|
10172
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10173
|
+
}
|
|
10174
|
+
});
|
|
10175
|
+
it("does not block Stop from root team state when canonical phase is missing", async () => {
|
|
10176
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
|
|
10177
|
+
try {
|
|
10178
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10179
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10180
|
+
await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
|
|
10181
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10182
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
10183
|
+
active: true,
|
|
10184
|
+
current_phase: "starting",
|
|
10185
|
+
team_name: "root-missing-phase-team",
|
|
10186
|
+
session_id: "sess-current",
|
|
10187
|
+
thread_id: "thread-current",
|
|
10188
|
+
});
|
|
10189
|
+
const result = await dispatchCodexNativeHook({
|
|
10190
|
+
hook_event_name: "Stop",
|
|
10191
|
+
cwd,
|
|
10192
|
+
session_id: "sess-current",
|
|
10193
|
+
thread_id: "thread-current",
|
|
10194
|
+
}, { cwd });
|
|
10195
|
+
assert.equal(result.omxEventName, "stop");
|
|
10196
|
+
assert.equal(result.outputJson, null);
|
|
10197
|
+
}
|
|
10198
|
+
finally {
|
|
10199
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10200
|
+
}
|
|
10201
|
+
});
|
|
10202
|
+
it("does not block Stop from session-scoped team state owned by another thread", async () => {
|
|
10203
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
|
|
10204
|
+
try {
|
|
10205
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10206
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10207
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10208
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
10209
|
+
active: true,
|
|
10210
|
+
current_phase: "starting",
|
|
10211
|
+
team_name: "scoped-other-thread-team",
|
|
10212
|
+
session_id: "sess-current",
|
|
10213
|
+
thread_id: "thread-other",
|
|
10214
|
+
});
|
|
10215
|
+
await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
|
|
10216
|
+
current_phase: "team-exec",
|
|
10217
|
+
max_fix_attempts: 3,
|
|
10218
|
+
current_fix_attempt: 0,
|
|
10219
|
+
transitions: [],
|
|
10220
|
+
updated_at: new Date().toISOString(),
|
|
10221
|
+
});
|
|
10222
|
+
const result = await dispatchCodexNativeHook({
|
|
10223
|
+
hook_event_name: "Stop",
|
|
10224
|
+
cwd,
|
|
10225
|
+
session_id: "sess-current",
|
|
10226
|
+
thread_id: "thread-current",
|
|
10227
|
+
}, { cwd });
|
|
10228
|
+
assert.equal(result.omxEventName, "stop");
|
|
10229
|
+
assert.equal(result.outputJson, null);
|
|
10230
|
+
}
|
|
10231
|
+
finally {
|
|
10232
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10233
|
+
}
|
|
10234
|
+
});
|
|
10235
|
+
it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
|
|
10236
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
|
|
10237
|
+
try {
|
|
10238
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10239
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
10240
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
10241
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
10242
|
+
active: true,
|
|
10243
|
+
current_phase: "starting",
|
|
10244
|
+
team_name: "scoped-current-team",
|
|
10245
|
+
session_id: "sess-current",
|
|
10246
|
+
thread_id: "thread-current",
|
|
10247
|
+
});
|
|
10248
|
+
await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
|
|
10249
|
+
current_phase: "team-exec",
|
|
10250
|
+
max_fix_attempts: 3,
|
|
10251
|
+
current_fix_attempt: 0,
|
|
10252
|
+
transitions: [],
|
|
10253
|
+
updated_at: new Date().toISOString(),
|
|
10254
|
+
});
|
|
10255
|
+
const result = await dispatchCodexNativeHook({
|
|
10256
|
+
hook_event_name: "Stop",
|
|
10257
|
+
cwd,
|
|
10258
|
+
session_id: "sess-current",
|
|
10259
|
+
thread_id: "thread-current",
|
|
10260
|
+
}, { cwd });
|
|
10261
|
+
assert.equal(result.omxEventName, "stop");
|
|
10262
|
+
assert.deepEqual(result.outputJson, {
|
|
10263
|
+
decision: "block",
|
|
10264
|
+
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}`,
|
|
10265
|
+
stopReason: "team_team-exec",
|
|
10266
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
10267
|
+
});
|
|
10268
|
+
}
|
|
10269
|
+
finally {
|
|
10270
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10271
|
+
}
|
|
10272
|
+
});
|
|
9382
10273
|
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
9383
10274
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
9384
10275
|
try {
|
|
@@ -9561,6 +10452,79 @@ describe("codex native hook triage integration", () => {
|
|
|
9561
10452
|
await rm(cwd, { recursive: true, force: true });
|
|
9562
10453
|
}
|
|
9563
10454
|
});
|
|
10455
|
+
it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
|
|
10456
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
|
|
10457
|
+
const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
|
|
10458
|
+
const originalOmxRoot = process.env.OMX_ROOT;
|
|
10459
|
+
const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
10460
|
+
const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
10461
|
+
try {
|
|
10462
|
+
process.env.OMX_ROOT = boxedRoot;
|
|
10463
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10464
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10465
|
+
const boxedStateDir = getBaseStateDir(cwd);
|
|
10466
|
+
await mkdir(boxedStateDir, { recursive: true });
|
|
10467
|
+
await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
|
|
10468
|
+
schemaVersion: 1,
|
|
10469
|
+
sessions: {
|
|
10470
|
+
"omx-parent-session": {
|
|
10471
|
+
session_id: "omx-parent-session",
|
|
10472
|
+
leader_thread_id: "parent-native-thread",
|
|
10473
|
+
updated_at: "2026-05-21T19:04:40.000Z",
|
|
10474
|
+
threads: {
|
|
10475
|
+
"parent-native-thread": {
|
|
10476
|
+
thread_id: "parent-native-thread",
|
|
10477
|
+
kind: "leader",
|
|
10478
|
+
first_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10479
|
+
last_seen_at: "2026-05-21T19:04:40.000Z",
|
|
10480
|
+
turn_count: 1,
|
|
10481
|
+
},
|
|
10482
|
+
"child-native-session": {
|
|
10483
|
+
thread_id: "child-native-session",
|
|
10484
|
+
kind: "subagent",
|
|
10485
|
+
first_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10486
|
+
last_seen_at: "2026-05-21T19:04:41.000Z",
|
|
10487
|
+
turn_count: 1,
|
|
10488
|
+
mode: "review",
|
|
10489
|
+
},
|
|
10490
|
+
},
|
|
10491
|
+
},
|
|
10492
|
+
},
|
|
10493
|
+
});
|
|
10494
|
+
const result = await dispatchCodexNativeHook({
|
|
10495
|
+
hook_event_name: "UserPromptSubmit",
|
|
10496
|
+
cwd,
|
|
10497
|
+
session_id: "child-native-session",
|
|
10498
|
+
thread_id: "child-native-session",
|
|
10499
|
+
turn_id: "turn-subagent-review",
|
|
10500
|
+
prompt: [
|
|
10501
|
+
"Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
|
|
10502
|
+
"Context: The user asked for $autopilot, and this subagent must only review the patch.",
|
|
10503
|
+
].join("\n\n"),
|
|
10504
|
+
}, { cwd });
|
|
10505
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10506
|
+
assert.equal(additionalContext, "");
|
|
10507
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")), false);
|
|
10508
|
+
assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")), false);
|
|
10509
|
+
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");
|
|
10510
|
+
}
|
|
10511
|
+
finally {
|
|
10512
|
+
if (originalOmxRoot === undefined)
|
|
10513
|
+
delete process.env.OMX_ROOT;
|
|
10514
|
+
else
|
|
10515
|
+
process.env.OMX_ROOT = originalOmxRoot;
|
|
10516
|
+
if (originalOmxStateRoot === undefined)
|
|
10517
|
+
delete process.env.OMX_STATE_ROOT;
|
|
10518
|
+
else
|
|
10519
|
+
process.env.OMX_STATE_ROOT = originalOmxStateRoot;
|
|
10520
|
+
if (originalTeamStateRoot === undefined)
|
|
10521
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
10522
|
+
else
|
|
10523
|
+
process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
|
|
10524
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10525
|
+
await rm(boxedRoot, { recursive: true, force: true });
|
|
10526
|
+
}
|
|
10527
|
+
});
|
|
9564
10528
|
it("does not inject triage advisory for autopilot keyword prompts", async () => {
|
|
9565
10529
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
|
|
9566
10530
|
try {
|
|
@@ -9585,6 +10549,48 @@ describe("codex native hook triage integration", () => {
|
|
|
9585
10549
|
await rm(cwd, { recursive: true, force: true });
|
|
9586
10550
|
}
|
|
9587
10551
|
});
|
|
10552
|
+
it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
|
|
10553
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
|
|
10554
|
+
try {
|
|
10555
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
10556
|
+
await writeSessionStart(cwd, "sess-autopilot-observable");
|
|
10557
|
+
const result = await dispatchCodexNativeHook({
|
|
10558
|
+
hook_event_name: "UserPromptSubmit",
|
|
10559
|
+
cwd,
|
|
10560
|
+
session_id: "sess-autopilot-observable",
|
|
10561
|
+
thread_id: "thread-autopilot-observable",
|
|
10562
|
+
turn_id: "turn-autopilot-observable",
|
|
10563
|
+
prompt: "$autopilot implement issue #2430",
|
|
10564
|
+
}, { cwd });
|
|
10565
|
+
assert.equal(result.skillState?.skill, "autopilot");
|
|
10566
|
+
assert.equal(result.skillState?.phase, "deep-interview");
|
|
10567
|
+
assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
|
|
10568
|
+
const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
|
|
10569
|
+
assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
|
|
10570
|
+
assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
|
|
10571
|
+
assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
|
|
10572
|
+
assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
|
|
10573
|
+
assert.match(additionalContext, /Codex goal-mode handoff guidance/);
|
|
10574
|
+
assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
|
|
10575
|
+
const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
|
|
10576
|
+
const modeState = JSON.parse(await readFile(statePath, "utf-8"));
|
|
10577
|
+
assert.equal(modeState.active, true);
|
|
10578
|
+
assert.equal(modeState.current_phase, "deep-interview");
|
|
10579
|
+
assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
|
|
10580
|
+
assert.deepEqual(modeState.state?.deep_interview_gate, {
|
|
10581
|
+
status: "required",
|
|
10582
|
+
skip_reason: null,
|
|
10583
|
+
rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
|
|
10584
|
+
});
|
|
10585
|
+
const hudState = await readAllState(cwd);
|
|
10586
|
+
assert.equal(hudState.autopilot?.active, true);
|
|
10587
|
+
assert.equal(hudState.autopilot?.current_phase, "deep-interview");
|
|
10588
|
+
assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
|
|
10589
|
+
}
|
|
10590
|
+
finally {
|
|
10591
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10592
|
+
}
|
|
10593
|
+
});
|
|
9588
10594
|
// ── Group 2: HEAVY injection ─────────────────────────────────────────────
|
|
9589
10595
|
it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
|
|
9590
10596
|
const cwd = await mkdtemp(join(tmpdir(), "omx-triage-heavy-"));
|