oh-my-codex 0.18.2 → 0.18.4
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 +1 -0
- package/dist/agents/__tests__/definitions.test.js +9 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +1 -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 +10 -0
- package/dist/agents/definitions.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/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 +88 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +28 -7
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +70 -2
- package/dist/cli/__tests__/index.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__/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 +103 -17
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +2 -2
- package/dist/cli/__tests__/sparkshell-cli.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 +128 -19
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts +1 -0
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +18 -0
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +20 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +114 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +5 -1
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +29 -57
- package/dist/cli/setup.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 +128 -5
- package/dist/config/__tests__/generator-idempotent.test.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 +13 -4
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +154 -40
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +9 -7
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +10 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -0
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-routing.test.js +10 -12
- package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +13 -15
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +301 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +33 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +60 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- 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 +8 -13
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +5 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +52 -8
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +19 -14
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +117 -9
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +103 -1
- 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 +24 -2
- 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 +23 -0
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/tmux.d.ts +7 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +46 -9
- package/dist/hud/tmux.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +80 -7
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/policy.test.js +83 -9
- package/dist/question/__tests__/policy.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts +10 -0
- package/dist/question/autopilot-wait.d.ts.map +1 -0
- package/dist/question/autopilot-wait.js +134 -0
- package/dist/question/autopilot-wait.js.map +1 -0
- 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 +4 -0
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/policy.d.ts +1 -0
- package/dist/question/policy.d.ts.map +1 -1
- package/dist/question/policy.js +19 -0
- package/dist/question/policy.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +718 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +69 -5
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook.js +13 -0
- package/dist/scripts/notify-hook.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/workflow-transition.d.ts +23 -0
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +63 -0
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +69 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +5 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +16 -0
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +86 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +7 -0
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +126 -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 +126 -8
- 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 +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +11 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +4 -4
- package/plugins/oh-my-codex/skills/plan/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +10 -6
- package/prompts/executor.md +1 -1
- package/prompts/explore-harness.md +2 -2
- package/prompts/explore.md +1 -1
- package/prompts/planner.md +1 -1
- package/prompts/scholastic.md +11 -0
- package/prompts/sisyphus-lite.md +1 -1
- package/skills/autopilot/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +11 -1
- package/skills/omx-setup/SKILL.md +4 -4
- package/skills/plan/SKILL.md +5 -5
- package/skills/ralph/SKILL.md +1 -1
- package/skills/ralplan/SKILL.md +10 -6
- package/src/scripts/__tests__/codex-native-hook.test.ts +853 -0
- package/src/scripts/codex-native-hook.ts +73 -3
- package/src/scripts/notify-hook.ts +15 -0
- package/templates/AGENTS.md +3 -3
- package/templates/catalog-manifest.json +5 -0
|
@@ -25,6 +25,7 @@ import { writeSessionStart } from "../../hooks/session.js";
|
|
|
25
25
|
import { resetTriageConfigCache } from "../../hooks/triage-config.js";
|
|
26
26
|
import { executeStateOperation } from "../../state/operations.js";
|
|
27
27
|
import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
28
|
+
import { OMX_TMUX_HUD_LEADER_PANE_ENV } from "../../hud/tmux.js";
|
|
28
29
|
import { readAllState } from "../../hud/state.js";
|
|
29
30
|
import { renderHud } from "../../hud/render.js";
|
|
30
31
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
@@ -65,6 +66,39 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
65
66
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
async function setTeamPaneIds(
|
|
70
|
+
cwd: string,
|
|
71
|
+
teamName: string,
|
|
72
|
+
paneIds: { leaderPaneId: string; workerPaneIds: Record<string, string> },
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
for (const fileName of ["config.json", "manifest.v2.json"]) {
|
|
75
|
+
const filePath = join(cwd, ".omx", "state", "team", teamName, fileName);
|
|
76
|
+
const parsed = JSON.parse(await readFile(filePath, "utf-8")) as {
|
|
77
|
+
leader_pane_id?: string | null;
|
|
78
|
+
workers?: Array<{ name?: string; pane_id?: string | null }>;
|
|
79
|
+
};
|
|
80
|
+
parsed.leader_pane_id = paneIds.leaderPaneId;
|
|
81
|
+
parsed.workers = (parsed.workers ?? []).map((worker) => ({
|
|
82
|
+
...worker,
|
|
83
|
+
pane_id: worker.name ? paneIds.workerPaneIds[worker.name] ?? worker.pane_id ?? null : worker.pane_id ?? null,
|
|
84
|
+
}));
|
|
85
|
+
await writeJson(filePath, parsed);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function withIsolatedHome<T>(prefix: string, run: (homeDir: string) => Promise<T>): Promise<T> {
|
|
90
|
+
const homeDir = await mkdtemp(join(tmpdir(), `omx-native-hook-home-${prefix}-`));
|
|
91
|
+
const previousHome = process.env.HOME;
|
|
92
|
+
try {
|
|
93
|
+
process.env.HOME = homeDir;
|
|
94
|
+
return await run(homeDir);
|
|
95
|
+
} finally {
|
|
96
|
+
if (typeof previousHome === "string") process.env.HOME = previousHome;
|
|
97
|
+
else delete process.env.HOME;
|
|
98
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
68
102
|
async function withLoreGuardConfig<T>(
|
|
69
103
|
value: string,
|
|
70
104
|
prefix: string,
|
|
@@ -229,6 +263,7 @@ const DEFAULT_AUTO_NUDGE_RESPONSE =
|
|
|
229
263
|
|
|
230
264
|
const TEAM_ENV_KEYS = [
|
|
231
265
|
"OMX_TEAM_WORKER",
|
|
266
|
+
"OMX_TEAM_INTERNAL_WORKER",
|
|
232
267
|
"OMX_TEAM_STATE_ROOT",
|
|
233
268
|
"OMX_TEAM_LEADER_CWD",
|
|
234
269
|
"OMX_SESSION_ID",
|
|
@@ -1620,6 +1655,405 @@ describe("codex native hook dispatch", () => {
|
|
|
1620
1655
|
}
|
|
1621
1656
|
});
|
|
1622
1657
|
|
|
1658
|
+
it("injects deep-interview config overrides into UserPromptSubmit developer context", async () => {
|
|
1659
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-"));
|
|
1660
|
+
try {
|
|
1661
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1662
|
+
await writeFile(
|
|
1663
|
+
join(cwd, ".omx", "config.toml"),
|
|
1664
|
+
`[omx.deepInterview]
|
|
1665
|
+
defaultProfile = "standard"
|
|
1666
|
+
standardThreshold = 0.05
|
|
1667
|
+
standardMaxRounds = 15
|
|
1668
|
+
enableChallengeModes = false
|
|
1669
|
+
`,
|
|
1670
|
+
);
|
|
1671
|
+
|
|
1672
|
+
const result = await dispatchCodexNativeHook(
|
|
1673
|
+
{
|
|
1674
|
+
hook_event_name: "UserPromptSubmit",
|
|
1675
|
+
cwd,
|
|
1676
|
+
session_id: "sess-deep-interview-config",
|
|
1677
|
+
thread_id: "thread-1",
|
|
1678
|
+
turn_id: "turn-1",
|
|
1679
|
+
prompt: "$deep-interview prove config reflection",
|
|
1680
|
+
},
|
|
1681
|
+
{ cwd },
|
|
1682
|
+
);
|
|
1683
|
+
|
|
1684
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
1685
|
+
assert.equal(result.skillState?.skill, "deep-interview");
|
|
1686
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1687
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1688
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
1689
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
1690
|
+
assert.match(serializedOutput, /enableChallengeModes=false/);
|
|
1691
|
+
|
|
1692
|
+
const modeState = JSON.parse(
|
|
1693
|
+
await readFile(join(cwd, ".omx", "state", "sessions", "sess-deep-interview-config", "deep-interview-state.json"), "utf-8"),
|
|
1694
|
+
) as { threshold?: number; max_rounds?: number; profile?: string };
|
|
1695
|
+
assert.equal(modeState.profile, "standard");
|
|
1696
|
+
assert.equal(modeState.threshold, 0.05);
|
|
1697
|
+
assert.equal(modeState.max_rounds, 15);
|
|
1698
|
+
} finally {
|
|
1699
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
it("proves UserPromptSubmit context changes before and after adding deep-interview config", async () => {
|
|
1704
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-before-after-"));
|
|
1705
|
+
const sessionId = "sess-deep-interview-config-before-after";
|
|
1706
|
+
try {
|
|
1707
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1708
|
+
|
|
1709
|
+
const before = await withIsolatedHome("deep-interview-config-before-after", async () => (
|
|
1710
|
+
dispatchCodexNativeHook(
|
|
1711
|
+
{
|
|
1712
|
+
hook_event_name: "UserPromptSubmit",
|
|
1713
|
+
cwd,
|
|
1714
|
+
session_id: sessionId,
|
|
1715
|
+
thread_id: "thread-before-after",
|
|
1716
|
+
turn_id: "turn-before",
|
|
1717
|
+
prompt: "$deep-interview prove before config context",
|
|
1718
|
+
},
|
|
1719
|
+
{ cwd },
|
|
1720
|
+
)
|
|
1721
|
+
));
|
|
1722
|
+
const beforeOutput = JSON.stringify(before.outputJson);
|
|
1723
|
+
const beforeState = JSON.parse(
|
|
1724
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1725
|
+
) as {
|
|
1726
|
+
deep_interview_config?: unknown;
|
|
1727
|
+
threshold?: number;
|
|
1728
|
+
max_rounds?: number;
|
|
1729
|
+
};
|
|
1730
|
+
assert.equal(before.skillState?.skill, "deep-interview");
|
|
1731
|
+
assert.doesNotMatch(beforeOutput, /Deep-interview config override active/);
|
|
1732
|
+
assert.equal(before.skillState?.deep_interview_config, undefined);
|
|
1733
|
+
assert.equal(beforeState.deep_interview_config, undefined);
|
|
1734
|
+
assert.equal(beforeState.threshold, undefined);
|
|
1735
|
+
assert.equal(beforeState.max_rounds, undefined);
|
|
1736
|
+
|
|
1737
|
+
await writeFile(
|
|
1738
|
+
join(cwd, ".omx", "config.toml"),
|
|
1739
|
+
`[omx.deepInterview]
|
|
1740
|
+
defaultProfile = "standard"
|
|
1741
|
+
standardThreshold = 0.05
|
|
1742
|
+
standardMaxRounds = 15
|
|
1743
|
+
`,
|
|
1744
|
+
);
|
|
1745
|
+
|
|
1746
|
+
const after = await dispatchCodexNativeHook(
|
|
1747
|
+
{
|
|
1748
|
+
hook_event_name: "UserPromptSubmit",
|
|
1749
|
+
cwd,
|
|
1750
|
+
session_id: sessionId,
|
|
1751
|
+
thread_id: "thread-before-after",
|
|
1752
|
+
turn_id: "turn-after",
|
|
1753
|
+
prompt: "$deep-interview prove after config context",
|
|
1754
|
+
},
|
|
1755
|
+
{ cwd },
|
|
1756
|
+
);
|
|
1757
|
+
const afterOutput = JSON.stringify(after.outputJson);
|
|
1758
|
+
const afterState = JSON.parse(
|
|
1759
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1760
|
+
) as {
|
|
1761
|
+
deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number };
|
|
1762
|
+
threshold?: number;
|
|
1763
|
+
max_rounds?: number;
|
|
1764
|
+
};
|
|
1765
|
+
assert.equal(after.skillState?.deep_interview_config?.profile, "standard");
|
|
1766
|
+
assert.match(afterOutput, /Deep-interview config override active/);
|
|
1767
|
+
assert.match(afterOutput, /threshold=0\.05/);
|
|
1768
|
+
assert.match(afterOutput, /max_rounds=15/);
|
|
1769
|
+
assert.equal(afterState.deep_interview_config?.profile, "standard");
|
|
1770
|
+
assert.equal(afterState.threshold, 0.05);
|
|
1771
|
+
assert.equal(afterState.max_rounds, 15);
|
|
1772
|
+
} finally {
|
|
1773
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
it("injects deep-interview config for mixed workflow prompts that defer execution modes", async () => {
|
|
1778
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-mixed-"));
|
|
1779
|
+
const sessionId = "sess-deep-interview-config-mixed";
|
|
1780
|
+
try {
|
|
1781
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1782
|
+
await writeFile(
|
|
1783
|
+
join(cwd, ".omx", "config.toml"),
|
|
1784
|
+
`[omx.deepInterview]
|
|
1785
|
+
defaultProfile = "deep"
|
|
1786
|
+
deepThreshold = 0.13
|
|
1787
|
+
deepMaxRounds = 21
|
|
1788
|
+
enableChallengeModes = false
|
|
1789
|
+
`,
|
|
1790
|
+
);
|
|
1791
|
+
|
|
1792
|
+
const result = await withIsolatedHome("deep-interview-config-mixed", async () => (
|
|
1793
|
+
dispatchCodexNativeHook(
|
|
1794
|
+
{
|
|
1795
|
+
hook_event_name: "UserPromptSubmit",
|
|
1796
|
+
cwd,
|
|
1797
|
+
session_id: sessionId,
|
|
1798
|
+
thread_id: "thread-mixed-config",
|
|
1799
|
+
turn_id: "turn-mixed-config",
|
|
1800
|
+
prompt: "$autopilot $deep-interview prove mixed config context",
|
|
1801
|
+
},
|
|
1802
|
+
{ cwd },
|
|
1803
|
+
)
|
|
1804
|
+
));
|
|
1805
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1806
|
+
const modeState = JSON.parse(
|
|
1807
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1808
|
+
) as {
|
|
1809
|
+
deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number; enableChallengeModes?: boolean };
|
|
1810
|
+
profile?: string;
|
|
1811
|
+
threshold?: number;
|
|
1812
|
+
max_rounds?: number;
|
|
1813
|
+
enable_challenge_modes?: boolean;
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
assert.equal(result.skillState?.skill, "deep-interview");
|
|
1817
|
+
assert.deepEqual(result.skillState?.deferred_skills, ["autopilot"]);
|
|
1818
|
+
assert.equal(result.skillState?.deep_interview_config?.profile, "deep");
|
|
1819
|
+
assert.equal(result.skillState?.deep_interview_config?.threshold, 0.13);
|
|
1820
|
+
assert.equal(result.skillState?.deep_interview_config?.maxRounds, 21);
|
|
1821
|
+
assert.equal(result.skillState?.deep_interview_config?.enableChallengeModes, false);
|
|
1822
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1823
|
+
assert.match(serializedOutput, /profile=deep/);
|
|
1824
|
+
assert.match(serializedOutput, /threshold=0\.13/);
|
|
1825
|
+
assert.match(serializedOutput, /max_rounds=21/);
|
|
1826
|
+
assert.match(serializedOutput, /enableChallengeModes=false/);
|
|
1827
|
+
assert.equal(modeState.deep_interview_config?.profile, "deep");
|
|
1828
|
+
assert.equal(modeState.profile, "deep");
|
|
1829
|
+
assert.equal(modeState.threshold, 0.13);
|
|
1830
|
+
assert.equal(modeState.max_rounds, 21);
|
|
1831
|
+
assert.equal(modeState.enable_challenge_modes, false);
|
|
1832
|
+
} finally {
|
|
1833
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
it("keeps deep-interview config override context on continuation prompts", async () => {
|
|
1838
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-continuation-"));
|
|
1839
|
+
const sessionId = "sess-deep-interview-config-continuation";
|
|
1840
|
+
try {
|
|
1841
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1842
|
+
await writeFile(
|
|
1843
|
+
join(cwd, ".omx", "config.toml"),
|
|
1844
|
+
`[omx.deepInterview]
|
|
1845
|
+
defaultProfile = "standard"
|
|
1846
|
+
standardThreshold = 0.05
|
|
1847
|
+
standardMaxRounds = 15
|
|
1848
|
+
`,
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1851
|
+
await dispatchCodexNativeHook(
|
|
1852
|
+
{
|
|
1853
|
+
hook_event_name: "UserPromptSubmit",
|
|
1854
|
+
cwd,
|
|
1855
|
+
session_id: sessionId,
|
|
1856
|
+
thread_id: "thread-continuation",
|
|
1857
|
+
turn_id: "turn-start",
|
|
1858
|
+
prompt: "$deep-interview prove config continuation",
|
|
1859
|
+
},
|
|
1860
|
+
{ cwd },
|
|
1861
|
+
);
|
|
1862
|
+
const continued = await dispatchCodexNativeHook(
|
|
1863
|
+
{
|
|
1864
|
+
hook_event_name: "UserPromptSubmit",
|
|
1865
|
+
cwd,
|
|
1866
|
+
session_id: sessionId,
|
|
1867
|
+
thread_id: "thread-continuation",
|
|
1868
|
+
turn_id: "turn-continue",
|
|
1869
|
+
prompt: "continue",
|
|
1870
|
+
},
|
|
1871
|
+
{ cwd },
|
|
1872
|
+
);
|
|
1873
|
+
const serializedOutput = JSON.stringify(continued.outputJson);
|
|
1874
|
+
const modeState = JSON.parse(
|
|
1875
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1876
|
+
) as { threshold?: number; max_rounds?: number; profile?: string };
|
|
1877
|
+
|
|
1878
|
+
assert.equal(continued.skillState?.skill, "deep-interview");
|
|
1879
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1880
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
1881
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
1882
|
+
assert.equal(modeState.profile, "standard");
|
|
1883
|
+
assert.equal(modeState.threshold, 0.05);
|
|
1884
|
+
assert.equal(modeState.max_rounds, 15);
|
|
1885
|
+
} finally {
|
|
1886
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
|
|
1890
|
+
it("keeps explicit deep-interview profile flags reflected on continuation prompts", async () => {
|
|
1891
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-profile-continuation-"));
|
|
1892
|
+
const sessionId = "sess-deep-interview-config-profile-continuation";
|
|
1893
|
+
try {
|
|
1894
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1895
|
+
await writeFile(
|
|
1896
|
+
join(cwd, ".omx", "config.toml"),
|
|
1897
|
+
`[omx.deepInterview]
|
|
1898
|
+
defaultProfile = "standard"
|
|
1899
|
+
standardThreshold = 0.22
|
|
1900
|
+
standardMaxRounds = 13
|
|
1901
|
+
deepThreshold = 0.13
|
|
1902
|
+
deepMaxRounds = 21
|
|
1903
|
+
`,
|
|
1904
|
+
);
|
|
1905
|
+
|
|
1906
|
+
await dispatchCodexNativeHook(
|
|
1907
|
+
{
|
|
1908
|
+
hook_event_name: "UserPromptSubmit",
|
|
1909
|
+
cwd,
|
|
1910
|
+
session_id: sessionId,
|
|
1911
|
+
thread_id: "thread-profile-continuation",
|
|
1912
|
+
turn_id: "turn-start",
|
|
1913
|
+
prompt: "$deep-interview --deep prove explicit profile continuation",
|
|
1914
|
+
},
|
|
1915
|
+
{ cwd },
|
|
1916
|
+
);
|
|
1917
|
+
const continued = await dispatchCodexNativeHook(
|
|
1918
|
+
{
|
|
1919
|
+
hook_event_name: "UserPromptSubmit",
|
|
1920
|
+
cwd,
|
|
1921
|
+
session_id: sessionId,
|
|
1922
|
+
thread_id: "thread-profile-continuation",
|
|
1923
|
+
turn_id: "turn-continue",
|
|
1924
|
+
prompt: "continue",
|
|
1925
|
+
},
|
|
1926
|
+
{ cwd },
|
|
1927
|
+
);
|
|
1928
|
+
const serializedOutput = JSON.stringify(continued.outputJson);
|
|
1929
|
+
const modeState = JSON.parse(
|
|
1930
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1931
|
+
) as { threshold?: number; max_rounds?: number; profile?: string; deep_interview_config?: { profile?: string } };
|
|
1932
|
+
|
|
1933
|
+
assert.equal(continued.skillState?.skill, "deep-interview");
|
|
1934
|
+
assert.equal(continued.skillState?.deep_interview_config?.profile, "deep");
|
|
1935
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1936
|
+
assert.match(serializedOutput, /profile=deep/);
|
|
1937
|
+
assert.match(serializedOutput, /threshold=0\.13/);
|
|
1938
|
+
assert.match(serializedOutput, /max_rounds=21/);
|
|
1939
|
+
assert.equal(modeState.deep_interview_config?.profile, "deep");
|
|
1940
|
+
assert.equal(modeState.profile, "deep");
|
|
1941
|
+
assert.equal(modeState.threshold, 0.13);
|
|
1942
|
+
assert.equal(modeState.max_rounds, 21);
|
|
1943
|
+
} finally {
|
|
1944
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
it("keeps the documented deep-interview Suggested Config reflected in UserPromptSubmit context", async () => {
|
|
1949
|
+
const skillDoc = await readFile(join(process.cwd(), "skills", "deep-interview", "SKILL.md"), "utf-8");
|
|
1950
|
+
const markerIndex = skillDoc.indexOf("## Suggested Config (optional)");
|
|
1951
|
+
assert.notEqual(markerIndex, -1);
|
|
1952
|
+
const configMatch = skillDoc.slice(markerIndex).match(/```toml\n([\s\S]*?)\n```/);
|
|
1953
|
+
assert.ok(configMatch);
|
|
1954
|
+
const documentedConfig = configMatch[1]?.trimEnd();
|
|
1955
|
+
assert.ok(documentedConfig);
|
|
1956
|
+
assert.match(documentedConfig, /standardThreshold = 0\.20/);
|
|
1957
|
+
assert.match(documentedConfig, /standardMaxRounds = 12/);
|
|
1958
|
+
|
|
1959
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-doc-config-"));
|
|
1960
|
+
const sessionId = "sess-deep-interview-doc-config";
|
|
1961
|
+
try {
|
|
1962
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
1963
|
+
await writeFile(join(cwd, ".omx", "config.toml"), `${documentedConfig}\n`);
|
|
1964
|
+
|
|
1965
|
+
const result = await dispatchCodexNativeHook(
|
|
1966
|
+
{
|
|
1967
|
+
hook_event_name: "UserPromptSubmit",
|
|
1968
|
+
cwd,
|
|
1969
|
+
session_id: sessionId,
|
|
1970
|
+
thread_id: "thread-doc-config",
|
|
1971
|
+
turn_id: "turn-doc-config",
|
|
1972
|
+
prompt: "$deep-interview prove documented config context",
|
|
1973
|
+
},
|
|
1974
|
+
{ cwd },
|
|
1975
|
+
);
|
|
1976
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
1977
|
+
const modeState = JSON.parse(
|
|
1978
|
+
await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
|
|
1979
|
+
) as {
|
|
1980
|
+
deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number };
|
|
1981
|
+
profile?: string;
|
|
1982
|
+
threshold?: number;
|
|
1983
|
+
max_rounds?: number;
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
assert.equal(result.skillState?.deep_interview_config?.profile, "standard");
|
|
1987
|
+
assert.equal(result.skillState?.deep_interview_config?.threshold, 0.2);
|
|
1988
|
+
assert.equal(result.skillState?.deep_interview_config?.maxRounds, 12);
|
|
1989
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
1990
|
+
assert.match(serializedOutput, /profile=standard/);
|
|
1991
|
+
assert.match(serializedOutput, /threshold=0\.2/);
|
|
1992
|
+
assert.match(serializedOutput, /max_rounds=12/);
|
|
1993
|
+
assert.equal(modeState.deep_interview_config?.profile, "standard");
|
|
1994
|
+
assert.equal(modeState.profile, "standard");
|
|
1995
|
+
assert.equal(modeState.threshold, 0.2);
|
|
1996
|
+
assert.equal(modeState.max_rounds, 12);
|
|
1997
|
+
} finally {
|
|
1998
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
it("injects deep-interview config overrides when state is boxed under OMX_ROOT", async () => {
|
|
2003
|
+
const root = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-boxed-"));
|
|
2004
|
+
const cwd = join(root, "source");
|
|
2005
|
+
const omxRoot = join(root, "box");
|
|
2006
|
+
const sessionId = "sess-boxed-deep-interview-config";
|
|
2007
|
+
const previousOmxRoot = process.env.OMX_ROOT;
|
|
2008
|
+
const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
|
|
2009
|
+
const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
2010
|
+
try {
|
|
2011
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2012
|
+
await writeFile(
|
|
2013
|
+
join(cwd, ".omx", "config.toml"),
|
|
2014
|
+
`[omx.deepInterview]
|
|
2015
|
+
defaultProfile = "standard"
|
|
2016
|
+
standardThreshold = 0.05
|
|
2017
|
+
standardMaxRounds = 15
|
|
2018
|
+
`,
|
|
2019
|
+
);
|
|
2020
|
+
process.env.OMX_ROOT = omxRoot;
|
|
2021
|
+
delete process.env.OMX_STATE_ROOT;
|
|
2022
|
+
delete process.env.OMX_TEAM_STATE_ROOT;
|
|
2023
|
+
|
|
2024
|
+
const result = await dispatchCodexNativeHook(
|
|
2025
|
+
{
|
|
2026
|
+
hook_event_name: "UserPromptSubmit",
|
|
2027
|
+
cwd,
|
|
2028
|
+
session_id: sessionId,
|
|
2029
|
+
thread_id: "thread-boxed",
|
|
2030
|
+
turn_id: "turn-boxed",
|
|
2031
|
+
prompt: "$deep-interview prove boxed config reflection",
|
|
2032
|
+
},
|
|
2033
|
+
{ cwd },
|
|
2034
|
+
);
|
|
2035
|
+
|
|
2036
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2037
|
+
assert.equal(result.skillState?.initialized_state_path, `.omx/state/sessions/${sessionId}/deep-interview-state.json`);
|
|
2038
|
+
const boxedStatePath = join(omxRoot, ".omx", "state", "sessions", sessionId, "deep-interview-state.json");
|
|
2039
|
+
assert.equal(existsSync(boxedStatePath), true);
|
|
2040
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json")), false);
|
|
2041
|
+
|
|
2042
|
+
const serializedOutput = JSON.stringify(result.outputJson);
|
|
2043
|
+
assert.match(serializedOutput, /Deep-interview config override active/);
|
|
2044
|
+
assert.match(serializedOutput, /threshold=0\.05/);
|
|
2045
|
+
assert.match(serializedOutput, /max_rounds=15/);
|
|
2046
|
+
} finally {
|
|
2047
|
+
if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot;
|
|
2048
|
+
else delete process.env.OMX_ROOT;
|
|
2049
|
+
if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot;
|
|
2050
|
+
else delete process.env.OMX_STATE_ROOT;
|
|
2051
|
+
if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
|
|
2052
|
+
else delete process.env.OMX_TEAM_STATE_ROOT;
|
|
2053
|
+
await rm(root, { recursive: true, force: true });
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
1623
2057
|
it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => {
|
|
1624
2058
|
const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-"));
|
|
1625
2059
|
const cwd = join(root, "source");
|
|
@@ -1949,6 +2383,38 @@ describe("codex native hook dispatch", () => {
|
|
|
1949
2383
|
}
|
|
1950
2384
|
});
|
|
1951
2385
|
|
|
2386
|
+
it("does not repeat ultragoal Stop recovery after a safe completed-aggregate microgoal blocker is recorded", async () => {
|
|
2387
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-aggregate-blocked-stop-"));
|
|
2388
|
+
try {
|
|
2389
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
2390
|
+
version: 1,
|
|
2391
|
+
codexGoalMode: "aggregate",
|
|
2392
|
+
activeGoalId: "G001-demo",
|
|
2393
|
+
goals: [{
|
|
2394
|
+
id: "G001-demo",
|
|
2395
|
+
status: "in_progress",
|
|
2396
|
+
objective: "Demo goal",
|
|
2397
|
+
failureReason: "aggregate Codex goal already complete and unreconcilable while repo-native .omx/ultragoal/goals.json still has an in-progress microgoal; stop the recovery loop",
|
|
2398
|
+
}],
|
|
2399
|
+
});
|
|
2400
|
+
|
|
2401
|
+
const result = await dispatchCodexNativeHook({
|
|
2402
|
+
hook_event_name: "Stop",
|
|
2403
|
+
cwd,
|
|
2404
|
+
session_id: "sess-ultragoal-aggregate-blocked-stop",
|
|
2405
|
+
thread_id: "thread-ultragoal-aggregate-blocked-stop",
|
|
2406
|
+
stop_hook_active: true,
|
|
2407
|
+
last_assistant_message: "Goal complete.",
|
|
2408
|
+
}, { cwd });
|
|
2409
|
+
|
|
2410
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
2411
|
+
assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required");
|
|
2412
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
2413
|
+
} finally {
|
|
2414
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
|
|
1952
2418
|
|
|
1953
2419
|
it("does not block ultragoal Stop after task-scoped reconciliation finishes exploded bookkeeping", async () => {
|
|
1954
2420
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-reconciled-stop-"));
|
|
@@ -3439,6 +3905,198 @@ export async function onHookEvent(event) {
|
|
|
3439
3905
|
}
|
|
3440
3906
|
});
|
|
3441
3907
|
|
|
3908
|
+
it("skips prompt-submit HUD reconciliation for confirmed team worker panes", async () => {
|
|
3909
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-worker-skip-"));
|
|
3910
|
+
try {
|
|
3911
|
+
const teamName = "hud-worker-skip";
|
|
3912
|
+
await initTeamState(teamName, "skip worker HUD reconcile", "executor", 1, cwd);
|
|
3913
|
+
await setTeamPaneIds(cwd, teamName, {
|
|
3914
|
+
leaderPaneId: "%42",
|
|
3915
|
+
workerPaneIds: { "worker-1": "%10" },
|
|
3916
|
+
});
|
|
3917
|
+
process.env.TMUX = "1";
|
|
3918
|
+
process.env.TMUX_PANE = "%10";
|
|
3919
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`;
|
|
3920
|
+
process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`;
|
|
3921
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
3922
|
+
|
|
3923
|
+
let reconcileCalls = 0;
|
|
3924
|
+
const result = await dispatchCodexNativeHook(
|
|
3925
|
+
{
|
|
3926
|
+
hook_event_name: "UserPromptSubmit",
|
|
3927
|
+
cwd,
|
|
3928
|
+
session_id: "sess-hud-team-worker",
|
|
3929
|
+
prompt: "$ralplan prepare plan",
|
|
3930
|
+
},
|
|
3931
|
+
{
|
|
3932
|
+
cwd,
|
|
3933
|
+
reconcileHudForPromptSubmitFn: async () => {
|
|
3934
|
+
reconcileCalls += 1;
|
|
3935
|
+
return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
|
|
3936
|
+
},
|
|
3937
|
+
},
|
|
3938
|
+
);
|
|
3939
|
+
|
|
3940
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
3941
|
+
assert.equal(reconcileCalls, 0);
|
|
3942
|
+
} finally {
|
|
3943
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3944
|
+
}
|
|
3945
|
+
});
|
|
3946
|
+
|
|
3947
|
+
it("preserves prompt-submit HUD reconciliation for team leader panes", async () => {
|
|
3948
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-leader-preserve-"));
|
|
3949
|
+
try {
|
|
3950
|
+
const teamName = "hud-leader-keep";
|
|
3951
|
+
await initTeamState(teamName, "preserve leader HUD reconcile", "executor", 1, cwd);
|
|
3952
|
+
await setTeamPaneIds(cwd, teamName, {
|
|
3953
|
+
leaderPaneId: "%42",
|
|
3954
|
+
workerPaneIds: { "worker-1": "%10" },
|
|
3955
|
+
});
|
|
3956
|
+
process.env.TMUX = "1";
|
|
3957
|
+
process.env.TMUX_PANE = "%42";
|
|
3958
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
3959
|
+
|
|
3960
|
+
let reconcileCall: { cwd: string; sessionId?: string } | null = null;
|
|
3961
|
+
const result = await dispatchCodexNativeHook(
|
|
3962
|
+
{
|
|
3963
|
+
hook_event_name: "UserPromptSubmit",
|
|
3964
|
+
cwd,
|
|
3965
|
+
session_id: "sess-hud-team-leader",
|
|
3966
|
+
prompt: "$ralplan prepare plan",
|
|
3967
|
+
},
|
|
3968
|
+
{
|
|
3969
|
+
cwd,
|
|
3970
|
+
reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
|
|
3971
|
+
reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
|
|
3972
|
+
return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
|
|
3973
|
+
},
|
|
3974
|
+
},
|
|
3975
|
+
);
|
|
3976
|
+
|
|
3977
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
3978
|
+
assert.deepEqual(reconcileCall, { cwd, sessionId: "sess-hud-team-leader" });
|
|
3979
|
+
} finally {
|
|
3980
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
|
|
3984
|
+
it("preserves prompt-submit HUD reconciliation when worker pane detection is ambiguous", async () => {
|
|
3985
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-team-worker-ambiguous-"));
|
|
3986
|
+
try {
|
|
3987
|
+
const teamName = "hud-worker-ambiguous";
|
|
3988
|
+
await initTeamState(teamName, "fail closed for ambiguous worker HUD reconcile", "executor", 1, cwd);
|
|
3989
|
+
await setTeamPaneIds(cwd, teamName, {
|
|
3990
|
+
leaderPaneId: "%42",
|
|
3991
|
+
workerPaneIds: { "worker-1": "%10" },
|
|
3992
|
+
});
|
|
3993
|
+
process.env.TMUX = "1";
|
|
3994
|
+
process.env.TMUX_PANE = "%99";
|
|
3995
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`;
|
|
3996
|
+
process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`;
|
|
3997
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
3998
|
+
|
|
3999
|
+
let reconcileCalls = 0;
|
|
4000
|
+
const result = await dispatchCodexNativeHook(
|
|
4001
|
+
{
|
|
4002
|
+
hook_event_name: "UserPromptSubmit",
|
|
4003
|
+
cwd,
|
|
4004
|
+
session_id: "sess-hud-team-worker-ambiguous",
|
|
4005
|
+
prompt: "$ralplan prepare plan",
|
|
4006
|
+
},
|
|
4007
|
+
{
|
|
4008
|
+
cwd,
|
|
4009
|
+
reconcileHudForPromptSubmitFn: async () => {
|
|
4010
|
+
reconcileCalls += 1;
|
|
4011
|
+
return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
|
|
4012
|
+
},
|
|
4013
|
+
},
|
|
4014
|
+
);
|
|
4015
|
+
|
|
4016
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
4017
|
+
assert.equal(reconcileCalls, 1);
|
|
4018
|
+
} finally {
|
|
4019
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
|
|
4023
|
+
it("preserves prompt-submit HUD reconciliation for native subagents even with worker pane env", async () => {
|
|
4024
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-subagent-worker-preserve-"));
|
|
4025
|
+
try {
|
|
4026
|
+
const teamName = "hud-subagent-keep";
|
|
4027
|
+
await initTeamState(teamName, "preserve subagent HUD reconcile", "executor", 1, cwd);
|
|
4028
|
+
await setTeamPaneIds(cwd, teamName, {
|
|
4029
|
+
leaderPaneId: "%42",
|
|
4030
|
+
workerPaneIds: { "worker-1": "%10" },
|
|
4031
|
+
});
|
|
4032
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4033
|
+
const canonicalSessionId = "sess-subagent-hud-parent";
|
|
4034
|
+
const leaderNativeSessionId = "native-subagent-hud-parent";
|
|
4035
|
+
const childNativeSessionId = "native-subagent-hud-child";
|
|
4036
|
+
const nowIso = new Date().toISOString();
|
|
4037
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
4038
|
+
session_id: canonicalSessionId,
|
|
4039
|
+
native_session_id: leaderNativeSessionId,
|
|
4040
|
+
});
|
|
4041
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
4042
|
+
schemaVersion: 1,
|
|
4043
|
+
sessions: {
|
|
4044
|
+
[canonicalSessionId]: {
|
|
4045
|
+
session_id: canonicalSessionId,
|
|
4046
|
+
leader_thread_id: leaderNativeSessionId,
|
|
4047
|
+
updated_at: nowIso,
|
|
4048
|
+
threads: {
|
|
4049
|
+
[leaderNativeSessionId]: {
|
|
4050
|
+
thread_id: leaderNativeSessionId,
|
|
4051
|
+
kind: "leader",
|
|
4052
|
+
first_seen_at: nowIso,
|
|
4053
|
+
last_seen_at: nowIso,
|
|
4054
|
+
turn_count: 1,
|
|
4055
|
+
},
|
|
4056
|
+
[childNativeSessionId]: {
|
|
4057
|
+
thread_id: childNativeSessionId,
|
|
4058
|
+
kind: "subagent",
|
|
4059
|
+
first_seen_at: nowIso,
|
|
4060
|
+
last_seen_at: nowIso,
|
|
4061
|
+
turn_count: 1,
|
|
4062
|
+
mode: "verifier",
|
|
4063
|
+
},
|
|
4064
|
+
},
|
|
4065
|
+
},
|
|
4066
|
+
},
|
|
4067
|
+
});
|
|
4068
|
+
process.env.TMUX = "1";
|
|
4069
|
+
process.env.TMUX_PANE = "%10";
|
|
4070
|
+
process.env.OMX_TEAM_INTERNAL_WORKER = `${teamName}/worker-1`;
|
|
4071
|
+
process.env.OMX_TEAM_WORKER = `${teamName}/worker-1`;
|
|
4072
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
4073
|
+
|
|
4074
|
+
let reconcileCall: { cwd: string; sessionId?: string } | null = null;
|
|
4075
|
+
const result = await dispatchCodexNativeHook(
|
|
4076
|
+
{
|
|
4077
|
+
hook_event_name: "UserPromptSubmit",
|
|
4078
|
+
cwd,
|
|
4079
|
+
session_id: childNativeSessionId,
|
|
4080
|
+
thread_id: childNativeSessionId,
|
|
4081
|
+
turn_id: "turn-subagent-hud-child",
|
|
4082
|
+
prompt: "Review the worker patch literally; do not activate $ralplan.",
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
cwd,
|
|
4086
|
+
reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
|
|
4087
|
+
reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
|
|
4088
|
+
return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
|
|
4089
|
+
},
|
|
4090
|
+
},
|
|
4091
|
+
);
|
|
4092
|
+
|
|
4093
|
+
assert.equal(result.outputJson, null);
|
|
4094
|
+
assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId });
|
|
4095
|
+
} finally {
|
|
4096
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4097
|
+
}
|
|
4098
|
+
});
|
|
4099
|
+
|
|
3442
4100
|
it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => {
|
|
3443
4101
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-"));
|
|
3444
4102
|
const originalTmux = process.env.TMUX;
|
|
@@ -3521,6 +4179,78 @@ esac
|
|
|
3521
4179
|
}
|
|
3522
4180
|
});
|
|
3523
4181
|
|
|
4182
|
+
it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
|
|
4183
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
|
|
4184
|
+
const originalTmux = process.env.TMUX;
|
|
4185
|
+
const originalTmuxPane = process.env.TMUX_PANE;
|
|
4186
|
+
const originalPath = process.env.PATH;
|
|
4187
|
+
const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
4188
|
+
try {
|
|
4189
|
+
process.env.TMUX = "1";
|
|
4190
|
+
process.env.TMUX_PANE = "%1";
|
|
4191
|
+
process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
|
|
4192
|
+
const canonicalSessionId = "omx-canonical-hud-reuse";
|
|
4193
|
+
const nativeSessionId = "codex-native-hud-reuse";
|
|
4194
|
+
await mkdir(join(cwd, ".omx", "state", "sessions", canonicalSessionId), { recursive: true });
|
|
4195
|
+
await writeSessionStart(cwd, canonicalSessionId);
|
|
4196
|
+
|
|
4197
|
+
const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-bin-"));
|
|
4198
|
+
const tmuxLog = join(cwd, "tmux.log");
|
|
4199
|
+
await writeFile(
|
|
4200
|
+
join(binDir, "tmux"),
|
|
4201
|
+
`#!/usr/bin/env bash
|
|
4202
|
+
set -euo pipefail
|
|
4203
|
+
printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)}
|
|
4204
|
+
case "$1" in
|
|
4205
|
+
list-panes)
|
|
4206
|
+
printf '%%1\tcodex\tcodex\n'
|
|
4207
|
+
printf '%%2\tnode\texec env OMX_TMUX_HUD_OWNER='"'"'1'"'"' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='"'"'%%1'"'"' /node /omx.js hud --watch\n'
|
|
4208
|
+
;;
|
|
4209
|
+
display-message)
|
|
4210
|
+
printf '80\t24\n'
|
|
4211
|
+
;;
|
|
4212
|
+
resize-pane)
|
|
4213
|
+
;;
|
|
4214
|
+
split-window)
|
|
4215
|
+
printf '%%9\n'
|
|
4216
|
+
;;
|
|
4217
|
+
esac
|
|
4218
|
+
`,
|
|
4219
|
+
);
|
|
4220
|
+
await chmod(join(binDir, "tmux"), 0o755);
|
|
4221
|
+
process.env.PATH = `${binDir}:${originalPath}`;
|
|
4222
|
+
|
|
4223
|
+
const result = await dispatchCodexNativeHook(
|
|
4224
|
+
{
|
|
4225
|
+
hook_event_name: "UserPromptSubmit",
|
|
4226
|
+
cwd,
|
|
4227
|
+
session_id: nativeSessionId,
|
|
4228
|
+
thread_id: "thread-hud-reuse",
|
|
4229
|
+
turn_id: "turn-hud-reuse",
|
|
4230
|
+
prompt: "$ralplan prepare plan",
|
|
4231
|
+
},
|
|
4232
|
+
{ cwd },
|
|
4233
|
+
);
|
|
4234
|
+
|
|
4235
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
4236
|
+
const tmuxCalls = await readFile(tmuxLog, "utf-8");
|
|
4237
|
+
assert.match(tmuxCalls, /list-panes -t %1 -F/);
|
|
4238
|
+
assert.match(tmuxCalls, /resize-pane -t %2 -y 3/);
|
|
4239
|
+
assert.doesNotMatch(tmuxCalls, /split-window/);
|
|
4240
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
|
|
4241
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
|
|
4242
|
+
} finally {
|
|
4243
|
+
if (originalTmux === undefined) delete process.env.TMUX;
|
|
4244
|
+
else process.env.TMUX = originalTmux;
|
|
4245
|
+
if (originalTmuxPane === undefined) delete process.env.TMUX_PANE;
|
|
4246
|
+
else process.env.TMUX_PANE = originalTmuxPane;
|
|
4247
|
+
if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV];
|
|
4248
|
+
else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
|
|
4249
|
+
process.env.PATH = originalPath;
|
|
4250
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4251
|
+
}
|
|
4252
|
+
});
|
|
4253
|
+
|
|
3524
4254
|
it("skips prompt-submit HUD reconciliation inside unowned tmux panes", async () => {
|
|
3525
4255
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-"));
|
|
3526
4256
|
const originalTmux = process.env.TMUX;
|
|
@@ -8779,6 +9509,70 @@ exit 0
|
|
|
8779
9509
|
}
|
|
8780
9510
|
});
|
|
8781
9511
|
|
|
9512
|
+
it("does not report ralplan subagent waiting when notify-fallback already recorded completion", async () => {
|
|
9513
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-complete-"));
|
|
9514
|
+
try {
|
|
9515
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
9516
|
+
const now = new Date().toISOString();
|
|
9517
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-skill-subagent-complete"), { recursive: true });
|
|
9518
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-subagent-complete" });
|
|
9519
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent-complete", "skill-active-state.json"), {
|
|
9520
|
+
active: true,
|
|
9521
|
+
skill: "ralplan",
|
|
9522
|
+
phase: "planning",
|
|
9523
|
+
});
|
|
9524
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent-complete", "ralplan-state.json"), {
|
|
9525
|
+
active: true,
|
|
9526
|
+
current_phase: "planning",
|
|
9527
|
+
});
|
|
9528
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
9529
|
+
schemaVersion: 1,
|
|
9530
|
+
sessions: {
|
|
9531
|
+
"sess-stop-skill-subagent-complete": {
|
|
9532
|
+
session_id: "sess-stop-skill-subagent-complete",
|
|
9533
|
+
leader_thread_id: "leader-1",
|
|
9534
|
+
updated_at: now,
|
|
9535
|
+
threads: {
|
|
9536
|
+
"leader-1": {
|
|
9537
|
+
thread_id: "leader-1",
|
|
9538
|
+
kind: "leader",
|
|
9539
|
+
first_seen_at: now,
|
|
9540
|
+
last_seen_at: now,
|
|
9541
|
+
turn_count: 1,
|
|
9542
|
+
},
|
|
9543
|
+
"sub-1": {
|
|
9544
|
+
thread_id: "sub-1",
|
|
9545
|
+
kind: "subagent",
|
|
9546
|
+
first_seen_at: now,
|
|
9547
|
+
last_seen_at: now,
|
|
9548
|
+
completed_at: now,
|
|
9549
|
+
last_completed_turn_id: "turn-complete-1",
|
|
9550
|
+
completion_source: "notify-fallback-watcher",
|
|
9551
|
+
turn_count: 2,
|
|
9552
|
+
},
|
|
9553
|
+
},
|
|
9554
|
+
},
|
|
9555
|
+
},
|
|
9556
|
+
});
|
|
9557
|
+
|
|
9558
|
+
const result = await dispatchCodexNativeHook(
|
|
9559
|
+
{
|
|
9560
|
+
hook_event_name: "Stop",
|
|
9561
|
+
cwd,
|
|
9562
|
+
session_id: "sess-stop-skill-subagent-complete",
|
|
9563
|
+
},
|
|
9564
|
+
{ cwd },
|
|
9565
|
+
);
|
|
9566
|
+
|
|
9567
|
+
assert.equal(result.omxEventName, "stop");
|
|
9568
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
9569
|
+
assert.doesNotMatch(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/);
|
|
9570
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
9571
|
+
} finally {
|
|
9572
|
+
await rm(cwd, { recursive: true, force: true });
|
|
9573
|
+
}
|
|
9574
|
+
});
|
|
9575
|
+
|
|
8782
9576
|
it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => {
|
|
8783
9577
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-"));
|
|
8784
9578
|
try {
|
|
@@ -13348,3 +14142,62 @@ describe("codex native hook triage integration", () => {
|
|
|
13348
14142
|
}
|
|
13349
14143
|
});
|
|
13350
14144
|
});
|
|
14145
|
+
|
|
14146
|
+
describe('native Stop autopilot deep-interview wait', () => {
|
|
14147
|
+
it('does not force continued execution while autopilot is waiting on a deep-interview omx question', async () => {
|
|
14148
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-native-hook-autopilot-question-wait-'));
|
|
14149
|
+
try {
|
|
14150
|
+
const sessionId = 'sess-autopilot-wait';
|
|
14151
|
+
const sessionDir = join(cwd, '.omx', 'state', 'sessions', sessionId);
|
|
14152
|
+
await writeJson(join(cwd, '.omx', 'state', 'session.json'), { session_id: sessionId });
|
|
14153
|
+
await writeJson(join(sessionDir, 'autopilot-state.json'), {
|
|
14154
|
+
mode: 'autopilot',
|
|
14155
|
+
active: true,
|
|
14156
|
+
current_phase: 'waiting-for-user',
|
|
14157
|
+
run_outcome: 'blocked_on_user',
|
|
14158
|
+
lifecycle_outcome: 'askuserQuestion',
|
|
14159
|
+
session_id: sessionId,
|
|
14160
|
+
state: {
|
|
14161
|
+
deep_interview_question: {
|
|
14162
|
+
status: 'waiting_for_user',
|
|
14163
|
+
source: 'omx-question',
|
|
14164
|
+
obligation_id: 'obligation-stop-1',
|
|
14165
|
+
previous_phase: 'deep-interview',
|
|
14166
|
+
},
|
|
14167
|
+
},
|
|
14168
|
+
});
|
|
14169
|
+
await writeJson(join(sessionDir, 'deep-interview-state.json'), {
|
|
14170
|
+
mode: 'deep-interview',
|
|
14171
|
+
active: false,
|
|
14172
|
+
current_phase: 'intent-first',
|
|
14173
|
+
lifecycle_outcome: 'askuserQuestion',
|
|
14174
|
+
run_outcome: 'blocked_on_user',
|
|
14175
|
+
session_id: sessionId,
|
|
14176
|
+
question_enforcement: {
|
|
14177
|
+
obligation_id: 'obligation-stop-1',
|
|
14178
|
+
source: 'omx-question',
|
|
14179
|
+
status: 'pending',
|
|
14180
|
+
lifecycle_outcome: 'askuserQuestion',
|
|
14181
|
+
requested_at: '2026-04-19T00:00:00.000Z',
|
|
14182
|
+
},
|
|
14183
|
+
});
|
|
14184
|
+
await writeJson(join(sessionDir, 'skill-active-state.json'), {
|
|
14185
|
+
active: true,
|
|
14186
|
+
skill: 'autopilot',
|
|
14187
|
+
phase: 'deep-interview',
|
|
14188
|
+
session_id: sessionId,
|
|
14189
|
+
active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
|
|
14190
|
+
});
|
|
14191
|
+
|
|
14192
|
+
const result = await dispatchCodexNativeHook({
|
|
14193
|
+
hook_event_name: 'Stop',
|
|
14194
|
+
session_id: sessionId,
|
|
14195
|
+
thread_id: 'thread-autopilot-wait',
|
|
14196
|
+
}, { cwd });
|
|
14197
|
+
|
|
14198
|
+
assert.equal(result.outputJson, null);
|
|
14199
|
+
} finally {
|
|
14200
|
+
await rm(cwd, { recursive: true, force: true });
|
|
14201
|
+
}
|
|
14202
|
+
});
|
|
14203
|
+
});
|