oh-my-codex 0.18.0 → 0.18.1
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 +43 -19
- package/crates/omx-api/src/lib.rs +66 -9
- package/crates/omx-sparkshell/src/exec.rs +125 -3
- package/crates/omx-sparkshell/src/main.rs +126 -36
- package/crates/omx-sparkshell/tests/execution.rs +225 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +76 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +49 -1
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
- package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +115 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +27 -41
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +94 -35
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +20 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +227 -4
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +72 -1
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +5 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +13 -7
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +119 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +345 -90
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +2 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +15 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +71 -11
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts +7 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +13 -3
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +184 -10
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +14 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +3 -0
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
- package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +19 -8
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
- package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
- package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
- package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
- package/dist/config/codex-feature-flags.d.ts +4 -0
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js +4 -0
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.js +6 -6
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.d.ts +1 -0
- package/dist/config/commit-lore-guard.d.ts.map +1 -1
- package/dist/config/commit-lore-guard.js +29 -3
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +3 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +24 -10
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +5 -1
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +10 -6
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +4 -3
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +29 -14
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +8 -3
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +3 -2
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +14 -8
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +2 -2
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
- package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/hud/index.d.ts +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +10 -4
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/tmux.js +2 -2
- package/dist/hud/tmux.js.map +1 -1
- package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
- package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
- package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
- package/dist/notifications/__tests__/verbosity.test.js +20 -0
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +6 -3
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/http-client.d.ts.map +1 -1
- package/dist/notifications/http-client.js +78 -27
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +7 -4
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +32 -69
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js +65 -3
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +50 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/index.d.ts +8 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +5 -2
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +5 -4
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +56 -15
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/code-review.d.ts +2 -2
- package/dist/pipeline/stages/code-review.d.ts.map +1 -1
- package/dist/pipeline/stages/code-review.js +5 -3
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/deep-interview.d.ts +15 -0
- package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
- package/dist/pipeline/stages/deep-interview.js +32 -0
- package/dist/pipeline/stages/deep-interview.js.map +1 -0
- package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
- package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js +2 -2
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.d.ts +19 -0
- package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
- package/dist/pipeline/stages/ultragoal.js +38 -0
- package/dist/pipeline/stages/ultragoal.js.map +1 -0
- package/dist/pipeline/stages/ultraqa.d.ts +30 -0
- package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
- package/dist/pipeline/stages/ultraqa.js +46 -0
- package/dist/pipeline/stages/ultraqa.js.map +1 -0
- package/dist/pipeline/types.d.ts +8 -6
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/types.js +2 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js +705 -45
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +23 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +16 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js +1 -0
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +158 -10
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +9 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
- package/dist/scripts/notify-hook/process-runner.js +39 -17
- package/dist/scripts/notify-hook/process-runner.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +9 -5
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +7 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +3 -0
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +99 -1
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +2 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.js +2 -2
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
- package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
- package/dist/sidecar/index.d.ts +1 -1
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +29 -12
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +11 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +111 -3
- 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 +39 -18
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +714 -10
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
- package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
- package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
- package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
- package/dist/ultragoal/artifacts.d.ts +97 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +811 -256
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
- package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
- package/dist/utils/__tests__/sleep-resource.test.js +39 -0
- package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +17 -6
- package/dist/utils/sleep.js.map +1 -1
- package/package.json +2 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
- package/plugins/oh-my-codex/hooks/hooks.json +77 -0
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +77 -47
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +22 -11
- package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +4 -4
- package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
- package/prompts/planner.md +1 -1
- package/skills/autopilot/SKILL.md +77 -47
- package/skills/cancel/SKILL.md +2 -2
- package/skills/deep-interview/SKILL.md +8 -8
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/pipeline/SKILL.md +22 -11
- package/skills/plan/SKILL.md +8 -8
- package/skills/ralph/SKILL.md +7 -0
- package/skills/ralplan/SKILL.md +4 -4
- package/skills/team/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +38 -4
- package/skills/ultrawork/SKILL.md +1 -1
- package/src/scripts/__tests__/codex-native-hook.test.ts +867 -81
- package/src/scripts/__tests__/smoke-packed-install.test.ts +31 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +21 -1
- package/src/scripts/cleanup-explore-harness.ts +1 -0
- package/src/scripts/codex-native-hook.ts +156 -10
- package/src/scripts/codex-native-pre-post.ts +16 -1
- package/src/scripts/notify-hook/process-runner.ts +40 -16
- package/src/scripts/notify-hook/team-dispatch.ts +9 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +7 -0
- package/src/scripts/smoke-packed-install.ts +105 -0
- package/src/scripts/sync-plugin-mirror.ts +3 -3
- package/src/scripts/verify-native-agents.ts +2 -2
|
@@ -28,6 +28,7 @@ import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
|
28
28
|
import { readAllState } from "../../hud/state.js";
|
|
29
29
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
30
30
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
31
|
+
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
31
32
|
|
|
32
33
|
function nativeHookScriptPath(): string {
|
|
33
34
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
@@ -62,6 +63,40 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
62
63
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
async function withLoreGuardConfig<T>(
|
|
67
|
+
value: string,
|
|
68
|
+
prefix: string,
|
|
69
|
+
run: (cwd: string) => Promise<T>,
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
|
|
72
|
+
const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
|
|
73
|
+
const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
|
|
74
|
+
const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
|
|
75
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
76
|
+
const originalHome = process.env.HOME;
|
|
77
|
+
try {
|
|
78
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
79
|
+
process.env.CODEX_HOME = codexHome;
|
|
80
|
+
process.env.HOME = defaultHome;
|
|
81
|
+
await writeFile(
|
|
82
|
+
join(codexHome, "config.toml"),
|
|
83
|
+
`[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`,
|
|
84
|
+
"utf-8",
|
|
85
|
+
);
|
|
86
|
+
return await run(cwd);
|
|
87
|
+
} finally {
|
|
88
|
+
if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
89
|
+
else process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
|
|
90
|
+
if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
|
|
91
|
+
else process.env.CODEX_HOME = originalCodexHome;
|
|
92
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
93
|
+
else process.env.HOME = originalHome;
|
|
94
|
+
await rm(cwd, { recursive: true, force: true });
|
|
95
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
96
|
+
await rm(defaultHome, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
function buildWorkerStopFakeTmux(
|
|
66
101
|
tmuxLogPath: string,
|
|
67
102
|
options: { failSend?: boolean; busyLeader?: boolean } = {},
|
|
@@ -729,7 +764,16 @@ describe("codex native hook dispatch", () => {
|
|
|
729
764
|
|
|
730
765
|
it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
|
|
731
766
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
|
|
767
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
732
768
|
try {
|
|
769
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
770
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
771
|
+
notifications: {
|
|
772
|
+
enabled: true,
|
|
773
|
+
verbosity: "session",
|
|
774
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
775
|
+
},
|
|
776
|
+
});
|
|
733
777
|
const stateDir = join(cwd, ".omx", "state");
|
|
734
778
|
const canonicalSessionId = "omx-leader-session";
|
|
735
779
|
const leaderNativeSessionId = "codex-leader-thread";
|
|
@@ -745,6 +789,16 @@ describe("codex native hook dispatch", () => {
|
|
|
745
789
|
iteration: 1,
|
|
746
790
|
max_iterations: 5,
|
|
747
791
|
});
|
|
792
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
793
|
+
await writeFile(
|
|
794
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
795
|
+
[
|
|
796
|
+
"import { appendFileSync } from 'node:fs';",
|
|
797
|
+
"export async function onHookEvent(event) {",
|
|
798
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
|
|
799
|
+
"}",
|
|
800
|
+
].join("\n"),
|
|
801
|
+
);
|
|
748
802
|
const transcriptPath = join(cwd, "subagent-rollout.jsonl");
|
|
749
803
|
await writeFile(
|
|
750
804
|
transcriptPath,
|
|
@@ -794,6 +848,11 @@ describe("codex native hook dispatch", () => {
|
|
|
794
848
|
) as { active?: boolean; current_phase?: string };
|
|
795
849
|
assert.equal(leaderRalph.active, true);
|
|
796
850
|
assert.equal(leaderRalph.current_phase, "executing");
|
|
851
|
+
assert.equal(
|
|
852
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
853
|
+
false,
|
|
854
|
+
"subagent SessionStart must not independently dispatch session-start hook notifications",
|
|
855
|
+
);
|
|
797
856
|
|
|
798
857
|
const tracking = JSON.parse(
|
|
799
858
|
await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"),
|
|
@@ -809,7 +868,257 @@ describe("codex native hook dispatch", () => {
|
|
|
809
868
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
810
869
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
811
870
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
|
|
871
|
+
|
|
872
|
+
await dispatchCodexNativeHook(
|
|
873
|
+
{
|
|
874
|
+
hook_event_name: "Stop",
|
|
875
|
+
cwd,
|
|
876
|
+
session_id: childNativeSessionId,
|
|
877
|
+
thread_id: childNativeSessionId,
|
|
878
|
+
turn_id: "child-stop-turn",
|
|
879
|
+
},
|
|
880
|
+
{ cwd },
|
|
881
|
+
);
|
|
882
|
+
assert.equal(
|
|
883
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
884
|
+
false,
|
|
885
|
+
"subagent Stop must not independently dispatch stop hook notifications",
|
|
886
|
+
);
|
|
887
|
+
} finally {
|
|
888
|
+
if (originalCodexHome === undefined) {
|
|
889
|
+
delete process.env.CODEX_HOME;
|
|
890
|
+
} else {
|
|
891
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
892
|
+
}
|
|
893
|
+
await rm(cwd, { recursive: true, force: true });
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
|
|
898
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
|
|
899
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
900
|
+
try {
|
|
901
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
902
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
903
|
+
notifications: {
|
|
904
|
+
enabled: true,
|
|
905
|
+
verbosity: "minimal",
|
|
906
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
910
|
+
const canonicalSessionId = "omx-leader-session-minimal";
|
|
911
|
+
const leaderNativeSessionId = "codex-leader-thread-minimal";
|
|
912
|
+
const childNativeSessionId = "codex-child-thread-minimal";
|
|
913
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
914
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
915
|
+
nativeSessionId: leaderNativeSessionId,
|
|
916
|
+
});
|
|
917
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
918
|
+
await writeFile(
|
|
919
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
920
|
+
[
|
|
921
|
+
"import { appendFileSync } from 'node:fs';",
|
|
922
|
+
"export async function onHookEvent(event) {",
|
|
923
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
924
|
+
"}",
|
|
925
|
+
].join("\n"),
|
|
926
|
+
);
|
|
927
|
+
const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
|
|
928
|
+
await writeFile(
|
|
929
|
+
transcriptPath,
|
|
930
|
+
`${JSON.stringify({
|
|
931
|
+
type: "session_meta",
|
|
932
|
+
payload: {
|
|
933
|
+
id: childNativeSessionId,
|
|
934
|
+
source: {
|
|
935
|
+
subagent: {
|
|
936
|
+
thread_spawn: {
|
|
937
|
+
parent_thread_id: leaderNativeSessionId,
|
|
938
|
+
agent_role: "verifier",
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
})}\n`,
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
await dispatchCodexNativeHook(
|
|
947
|
+
{
|
|
948
|
+
hook_event_name: "SessionStart",
|
|
949
|
+
cwd,
|
|
950
|
+
session_id: childNativeSessionId,
|
|
951
|
+
transcript_path: transcriptPath,
|
|
952
|
+
},
|
|
953
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
assert.equal(
|
|
957
|
+
existsSync(join(cwd, "hook-events.jsonl")),
|
|
958
|
+
false,
|
|
959
|
+
"subagent SessionStart must be suppressed at minimal verbosity",
|
|
960
|
+
);
|
|
961
|
+
} finally {
|
|
962
|
+
if (originalCodexHome === undefined) {
|
|
963
|
+
delete process.env.CODEX_HOME;
|
|
964
|
+
} else {
|
|
965
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
966
|
+
}
|
|
967
|
+
await rm(cwd, { recursive: true, force: true });
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
|
|
972
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
|
|
973
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
974
|
+
try {
|
|
975
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
976
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
977
|
+
notifications: {
|
|
978
|
+
enabled: true,
|
|
979
|
+
verbosity: "session",
|
|
980
|
+
includeChildAgents: true,
|
|
981
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
985
|
+
const canonicalSessionId = "omx-leader-session-include";
|
|
986
|
+
const leaderNativeSessionId = "codex-leader-thread-include";
|
|
987
|
+
const childNativeSessionId = "codex-child-thread-include";
|
|
988
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
989
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
990
|
+
nativeSessionId: leaderNativeSessionId,
|
|
991
|
+
});
|
|
992
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
993
|
+
await writeFile(
|
|
994
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
995
|
+
[
|
|
996
|
+
"import { appendFileSync } from 'node:fs';",
|
|
997
|
+
"export async function onHookEvent(event) {",
|
|
998
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
999
|
+
"}",
|
|
1000
|
+
].join("\n"),
|
|
1001
|
+
);
|
|
1002
|
+
const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
|
|
1003
|
+
await writeFile(
|
|
1004
|
+
transcriptPath,
|
|
1005
|
+
`${JSON.stringify({
|
|
1006
|
+
type: "session_meta",
|
|
1007
|
+
payload: {
|
|
1008
|
+
id: childNativeSessionId,
|
|
1009
|
+
source: {
|
|
1010
|
+
subagent: {
|
|
1011
|
+
thread_spawn: {
|
|
1012
|
+
parent_thread_id: leaderNativeSessionId,
|
|
1013
|
+
agent_role: "verifier",
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
})}\n`,
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
await dispatchCodexNativeHook(
|
|
1022
|
+
{
|
|
1023
|
+
hook_event_name: "SessionStart",
|
|
1024
|
+
cwd,
|
|
1025
|
+
session_id: childNativeSessionId,
|
|
1026
|
+
transcript_path: transcriptPath,
|
|
1027
|
+
},
|
|
1028
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
await dispatchCodexNativeHook(
|
|
1032
|
+
{
|
|
1033
|
+
hook_event_name: "Stop",
|
|
1034
|
+
cwd,
|
|
1035
|
+
session_id: childNativeSessionId,
|
|
1036
|
+
thread_id: childNativeSessionId,
|
|
1037
|
+
turn_id: "included-child-stop-turn",
|
|
1038
|
+
},
|
|
1039
|
+
{ cwd },
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
1043
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
1044
|
+
assert.match(hookEvents, /"event":"stop"/);
|
|
1045
|
+
} finally {
|
|
1046
|
+
if (originalCodexHome === undefined) {
|
|
1047
|
+
delete process.env.CODEX_HOME;
|
|
1048
|
+
} else {
|
|
1049
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
1050
|
+
}
|
|
1051
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
|
|
1056
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
|
|
1057
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
1058
|
+
try {
|
|
1059
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
1060
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
1061
|
+
notifications: {
|
|
1062
|
+
enabled: true,
|
|
1063
|
+
verbosity: "agent",
|
|
1064
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
1065
|
+
},
|
|
1066
|
+
});
|
|
1067
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1068
|
+
const canonicalSessionId = "omx-leader-session-agent";
|
|
1069
|
+
const leaderNativeSessionId = "codex-leader-thread-agent";
|
|
1070
|
+
const childNativeSessionId = "codex-child-thread-agent";
|
|
1071
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
1072
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
1073
|
+
nativeSessionId: leaderNativeSessionId,
|
|
1074
|
+
});
|
|
1075
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
1076
|
+
await writeFile(
|
|
1077
|
+
join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
|
|
1078
|
+
[
|
|
1079
|
+
"import { appendFileSync } from 'node:fs';",
|
|
1080
|
+
"export async function onHookEvent(event) {",
|
|
1081
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
1082
|
+
"}",
|
|
1083
|
+
].join("\n"),
|
|
1084
|
+
);
|
|
1085
|
+
const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
|
|
1086
|
+
await writeFile(
|
|
1087
|
+
transcriptPath,
|
|
1088
|
+
`${JSON.stringify({
|
|
1089
|
+
type: "session_meta",
|
|
1090
|
+
payload: {
|
|
1091
|
+
id: childNativeSessionId,
|
|
1092
|
+
source: {
|
|
1093
|
+
subagent: {
|
|
1094
|
+
thread_spawn: {
|
|
1095
|
+
parent_thread_id: leaderNativeSessionId,
|
|
1096
|
+
agent_role: "verifier",
|
|
1097
|
+
},
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
})}\n`,
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
await dispatchCodexNativeHook(
|
|
1105
|
+
{
|
|
1106
|
+
hook_event_name: "SessionStart",
|
|
1107
|
+
cwd,
|
|
1108
|
+
session_id: childNativeSessionId,
|
|
1109
|
+
transcript_path: transcriptPath,
|
|
1110
|
+
},
|
|
1111
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
1115
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
812
1116
|
} finally {
|
|
1117
|
+
if (originalCodexHome === undefined) {
|
|
1118
|
+
delete process.env.CODEX_HOME;
|
|
1119
|
+
} else {
|
|
1120
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
1121
|
+
}
|
|
813
1122
|
await rm(cwd, { recursive: true, force: true });
|
|
814
1123
|
}
|
|
815
1124
|
});
|
|
@@ -1498,6 +1807,66 @@ describe("codex native hook dispatch", () => {
|
|
|
1498
1807
|
}
|
|
1499
1808
|
});
|
|
1500
1809
|
|
|
1810
|
+
it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
|
|
1811
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
|
|
1812
|
+
try {
|
|
1813
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1814
|
+
version: 1,
|
|
1815
|
+
workflow: "performance-goal",
|
|
1816
|
+
slug: "latency",
|
|
1817
|
+
objective: "Reduce latency",
|
|
1818
|
+
status: "blocked",
|
|
1819
|
+
lastValidation: {
|
|
1820
|
+
status: "blocked",
|
|
1821
|
+
evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
|
|
1822
|
+
recordedAt: "2026-05-20T00:00:00.000Z",
|
|
1823
|
+
},
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
const result = await dispatchCodexNativeHook({
|
|
1827
|
+
hook_event_name: "Stop",
|
|
1828
|
+
cwd,
|
|
1829
|
+
session_id: "sess-performance-mismatch-blocked-stop",
|
|
1830
|
+
thread_id: "thread-performance-mismatch-blocked-stop",
|
|
1831
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1832
|
+
}, { cwd });
|
|
1833
|
+
|
|
1834
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1835
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1836
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1837
|
+
} finally {
|
|
1838
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
it("does not block Stop for an already complete performance-goal state", async () => {
|
|
1843
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
|
|
1844
|
+
try {
|
|
1845
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1846
|
+
version: 1,
|
|
1847
|
+
workflow: "performance-goal",
|
|
1848
|
+
slug: "latency",
|
|
1849
|
+
objective: "Reduce latency",
|
|
1850
|
+
status: "complete",
|
|
1851
|
+
completedAt: "2026-05-20T00:00:00.000Z",
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
const result = await dispatchCodexNativeHook({
|
|
1855
|
+
hook_event_name: "Stop",
|
|
1856
|
+
cwd,
|
|
1857
|
+
session_id: "sess-performance-complete-stop",
|
|
1858
|
+
thread_id: "thread-performance-complete-stop",
|
|
1859
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1860
|
+
}, { cwd });
|
|
1861
|
+
|
|
1862
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1863
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1864
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1865
|
+
} finally {
|
|
1866
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1501
1870
|
it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
|
|
1502
1871
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
|
|
1503
1872
|
try {
|
|
@@ -1546,7 +1915,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1546
1915
|
}
|
|
1547
1916
|
});
|
|
1548
1917
|
|
|
1549
|
-
it("blocks ultragoal Stop with blocked checkpoint and
|
|
1918
|
+
it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
|
|
1550
1919
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
|
|
1551
1920
|
try {
|
|
1552
1921
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
@@ -1567,7 +1936,8 @@ describe("codex native hook dispatch", () => {
|
|
|
1567
1936
|
assert.equal(result.outputJson?.decision, "block");
|
|
1568
1937
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1569
1938
|
assert.match(output, /--status blocked/);
|
|
1570
|
-
assert.match(output, /
|
|
1939
|
+
assert.match(output, /Codex goal context/);
|
|
1940
|
+
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1571
1941
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1572
1942
|
} finally {
|
|
1573
1943
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -1581,7 +1951,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1581
1951
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
1582
1952
|
version: 1,
|
|
1583
1953
|
codexGoalMode: "aggregate",
|
|
1584
|
-
codexObjective: "Complete
|
|
1954
|
+
codexObjective: "Complete the durable ultragoal plan in .omx/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omx/ultragoal/ledger.jsonl as the audit trail.",
|
|
1585
1955
|
activeGoalId: "G001-micro",
|
|
1586
1956
|
aggregateCompletion: {
|
|
1587
1957
|
status: "complete",
|
|
@@ -1842,55 +2212,233 @@ describe("codex native hook dispatch", () => {
|
|
|
1842
2212
|
{
|
|
1843
2213
|
hook_event_name: "UserPromptSubmit",
|
|
1844
2214
|
cwd,
|
|
1845
|
-
session_id: "sess-plugin-1",
|
|
1846
|
-
thread_id: "thread-plugin-1",
|
|
1847
|
-
turn_id: "turn-plugin-1",
|
|
1848
|
-
prompt: "$oh-my-codex:ralplan implement issue #1307",
|
|
2215
|
+
session_id: "sess-plugin-1",
|
|
2216
|
+
thread_id: "thread-plugin-1",
|
|
2217
|
+
turn_id: "turn-plugin-1",
|
|
2218
|
+
prompt: "$oh-my-codex:ralplan implement issue #1307",
|
|
2219
|
+
},
|
|
2220
|
+
{ cwd },
|
|
2221
|
+
);
|
|
2222
|
+
|
|
2223
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2224
|
+
assert.equal(result.skillState?.skill, "ralplan");
|
|
2225
|
+
const message = String(
|
|
2226
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2227
|
+
);
|
|
2228
|
+
assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/);
|
|
2229
|
+
assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
|
|
2230
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
|
|
2231
|
+
} finally {
|
|
2232
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
|
|
2237
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
2238
|
+
try {
|
|
2239
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2240
|
+
const result = await dispatchCodexNativeHook(
|
|
2241
|
+
{
|
|
2242
|
+
hook_event_name: "UserPromptSubmit",
|
|
2243
|
+
cwd,
|
|
2244
|
+
session_id: "sess-ultragoal-1",
|
|
2245
|
+
thread_id: "thread-ultragoal-1",
|
|
2246
|
+
turn_id: "turn-ultragoal-1",
|
|
2247
|
+
prompt: "$ultragoal split this launch into durable goals",
|
|
2248
|
+
},
|
|
2249
|
+
{ cwd },
|
|
2250
|
+
);
|
|
2251
|
+
|
|
2252
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
2253
|
+
assert.equal(result.skillState?.skill, "ultragoal");
|
|
2254
|
+
assert.equal(result.skillState?.initialized_mode, undefined);
|
|
2255
|
+
const message = String(
|
|
2256
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2257
|
+
);
|
|
2258
|
+
assert.match(message, /"\$ultragoal" -> ultragoal/);
|
|
2259
|
+
assert.match(message, /Ultragoal protocol:/);
|
|
2260
|
+
assert.match(message, /get_goal/);
|
|
2261
|
+
assert.match(message, /create_goal/);
|
|
2262
|
+
assert.match(message, /update_goal/);
|
|
2263
|
+
assert.match(message, /does not call `\/goal clear`/);
|
|
2264
|
+
assert.match(message, /multiple sequential ultragoal runs/);
|
|
2265
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
|
|
2266
|
+
} finally {
|
|
2267
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
|
|
2272
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
|
|
2273
|
+
try {
|
|
2274
|
+
await createUltragoalPlan(cwd, {
|
|
2275
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
|
|
2276
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2277
|
+
});
|
|
2278
|
+
|
|
2279
|
+
const prose = await dispatchCodexNativeHook(
|
|
2280
|
+
{
|
|
2281
|
+
hook_event_name: "UserPromptSubmit",
|
|
2282
|
+
cwd,
|
|
2283
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2284
|
+
prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
|
|
2285
|
+
},
|
|
2286
|
+
{ cwd },
|
|
2287
|
+
);
|
|
2288
|
+
assert.equal(prose.outputJson, null);
|
|
2289
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
2290
|
+
|
|
2291
|
+
const jsonExample = await dispatchCodexNativeHook(
|
|
2292
|
+
{
|
|
2293
|
+
hook_event_name: "UserPromptSubmit",
|
|
2294
|
+
cwd,
|
|
2295
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2296
|
+
prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
|
|
2297
|
+
kind: "add_subgoal",
|
|
2298
|
+
source: "user_prompt_submit",
|
|
2299
|
+
evidence: "Example JSON should not mutate .omx/ultragoal.",
|
|
2300
|
+
rationale: "Only explicit steering fences or labels are executable.",
|
|
2301
|
+
title: "Inert JSON example",
|
|
2302
|
+
objective: "This example must not be added.",
|
|
2303
|
+
})}\n\`\`\``,
|
|
2304
|
+
},
|
|
2305
|
+
{ cwd },
|
|
2306
|
+
);
|
|
2307
|
+
assert.equal(jsonExample.outputJson, null);
|
|
2308
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
2309
|
+
|
|
2310
|
+
const result = await dispatchCodexNativeHook(
|
|
2311
|
+
{
|
|
2312
|
+
hook_event_name: "UserPromptSubmit",
|
|
2313
|
+
cwd,
|
|
2314
|
+
session_id: "sess-ultragoal-steer-1",
|
|
2315
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2316
|
+
kind: "add_subgoal",
|
|
2317
|
+
source: "user_prompt_submit",
|
|
2318
|
+
evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
|
|
2319
|
+
rationale: "Add bounded hook regression work while preserving all completion gates.",
|
|
2320
|
+
title: "Prompt bridge regression",
|
|
2321
|
+
objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
|
|
2322
|
+
})}`,
|
|
2323
|
+
},
|
|
2324
|
+
{ cwd },
|
|
2325
|
+
);
|
|
2326
|
+
|
|
2327
|
+
const message = String(
|
|
2328
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
2329
|
+
);
|
|
2330
|
+
assert.match(message, /bounded \.omx\/ultragoal steering/);
|
|
2331
|
+
assert.match(message, /G002-cli-and-prompt-submit-bridge/);
|
|
2332
|
+
assert.match(message, /accepted/);
|
|
2333
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2334
|
+
assert.equal(plan.goals.length, 2);
|
|
2335
|
+
assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
|
|
2336
|
+
} finally {
|
|
2337
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
|
|
2342
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
|
|
2343
|
+
try {
|
|
2344
|
+
await createUltragoalPlan(cwd, {
|
|
2345
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
|
|
2346
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2347
|
+
});
|
|
2348
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2349
|
+
const canonicalSessionId = "sess-ultragoal-parent";
|
|
2350
|
+
const leaderNativeSessionId = "native-ultragoal-parent";
|
|
2351
|
+
const childNativeSessionId = "native-ultragoal-child";
|
|
2352
|
+
const nowIso = new Date().toISOString();
|
|
2353
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
2354
|
+
session_id: canonicalSessionId,
|
|
2355
|
+
native_session_id: leaderNativeSessionId,
|
|
2356
|
+
});
|
|
2357
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
2358
|
+
schemaVersion: 1,
|
|
2359
|
+
sessions: {
|
|
2360
|
+
[canonicalSessionId]: {
|
|
2361
|
+
session_id: canonicalSessionId,
|
|
2362
|
+
leader_thread_id: leaderNativeSessionId,
|
|
2363
|
+
updated_at: nowIso,
|
|
2364
|
+
threads: {
|
|
2365
|
+
[leaderNativeSessionId]: {
|
|
2366
|
+
thread_id: leaderNativeSessionId,
|
|
2367
|
+
kind: "leader",
|
|
2368
|
+
first_seen_at: nowIso,
|
|
2369
|
+
last_seen_at: nowIso,
|
|
2370
|
+
turn_count: 1,
|
|
2371
|
+
},
|
|
2372
|
+
[childNativeSessionId]: {
|
|
2373
|
+
thread_id: childNativeSessionId,
|
|
2374
|
+
kind: "subagent",
|
|
2375
|
+
first_seen_at: nowIso,
|
|
2376
|
+
last_seen_at: nowIso,
|
|
2377
|
+
turn_count: 1,
|
|
2378
|
+
mode: "architect",
|
|
2379
|
+
},
|
|
2380
|
+
},
|
|
2381
|
+
},
|
|
2382
|
+
},
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
const result = await dispatchCodexNativeHook(
|
|
2386
|
+
{
|
|
2387
|
+
hook_event_name: "UserPromptSubmit",
|
|
2388
|
+
cwd,
|
|
2389
|
+
session_id: childNativeSessionId,
|
|
2390
|
+
thread_id: childNativeSessionId,
|
|
2391
|
+
turn_id: "turn-ultragoal-child-1",
|
|
2392
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2393
|
+
kind: "add_subgoal",
|
|
2394
|
+
source: "user_prompt_submit",
|
|
2395
|
+
evidence: "Subagent prompt text must be literal delegated context.",
|
|
2396
|
+
rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
|
|
2397
|
+
title: "Subagent should not add this",
|
|
2398
|
+
objective: "This must remain literal prompt text.",
|
|
2399
|
+
})}`,
|
|
1849
2400
|
},
|
|
1850
2401
|
{ cwd },
|
|
1851
2402
|
);
|
|
1852
2403
|
|
|
1853
|
-
assert.equal(result.
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
);
|
|
1858
|
-
assert.match(
|
|
1859
|
-
assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
|
|
1860
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
|
|
2404
|
+
assert.equal(result.outputJson, null);
|
|
2405
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2406
|
+
assert.equal(plan.goals.length, 1);
|
|
2407
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2408
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
|
|
2409
|
+
assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
|
|
1861
2410
|
} finally {
|
|
1862
2411
|
await rm(cwd, { recursive: true, force: true });
|
|
1863
2412
|
}
|
|
1864
2413
|
});
|
|
1865
2414
|
|
|
1866
|
-
it("
|
|
1867
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
|
|
2415
|
+
it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
|
|
2416
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
|
|
1868
2417
|
try {
|
|
1869
|
-
await
|
|
1870
|
-
|
|
1871
|
-
{
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2418
|
+
await createUltragoalPlan(cwd, {
|
|
2419
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
|
|
2420
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2421
|
+
});
|
|
2422
|
+
const prompt = `\`\`\`omx-ultragoal-steer
|
|
2423
|
+
${JSON.stringify({
|
|
2424
|
+
kind: "add_subgoal",
|
|
2425
|
+
source: "user_prompt_submit",
|
|
2426
|
+
evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
|
|
2427
|
+
rationale: "Use idempotent bridge semantics for repeated hook delivery.",
|
|
2428
|
+
title: "Deduped bridge regression",
|
|
2429
|
+
objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
|
|
2430
|
+
})}
|
|
2431
|
+
\`\`\``;
|
|
2432
|
+
await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2433
|
+
const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
1885
2434
|
const message = String(
|
|
1886
|
-
(
|
|
2435
|
+
(second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
1887
2436
|
);
|
|
1888
|
-
assert.match(message, /
|
|
1889
|
-
|
|
1890
|
-
assert.
|
|
1891
|
-
|
|
1892
|
-
assert.match(
|
|
1893
|
-
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
|
|
2437
|
+
assert.match(message, /deduped/);
|
|
2438
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2439
|
+
assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
|
|
2440
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2441
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
|
|
1894
2442
|
} finally {
|
|
1895
2443
|
await rm(cwd, { recursive: true, force: true });
|
|
1896
2444
|
}
|
|
@@ -3768,7 +4316,7 @@ exit 0
|
|
|
3768
4316
|
cwd,
|
|
3769
4317
|
tool_name: "Bash",
|
|
3770
4318
|
tool_use_id: "tool-slop-git-priority",
|
|
3771
|
-
tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
|
|
4319
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
|
|
3772
4320
|
},
|
|
3773
4321
|
{ cwd },
|
|
3774
4322
|
);
|
|
@@ -3791,7 +4339,7 @@ exit 0
|
|
|
3791
4339
|
cwd,
|
|
3792
4340
|
tool_name: "Bash",
|
|
3793
4341
|
tool_use_id: "tool-git-commit-invalid",
|
|
3794
|
-
tool_input: { command: 'git commit -m "fix tests"' },
|
|
4342
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
|
|
3795
4343
|
},
|
|
3796
4344
|
{ cwd },
|
|
3797
4345
|
);
|
|
@@ -3820,11 +4368,38 @@ exit 0
|
|
|
3820
4368
|
}
|
|
3821
4369
|
});
|
|
3822
4370
|
|
|
3823
|
-
|
|
4371
|
+
|
|
4372
|
+
it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
|
|
4373
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
|
|
4374
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4375
|
+
try {
|
|
4376
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
4377
|
+
const result = await dispatchCodexNativeHook(
|
|
4378
|
+
{
|
|
4379
|
+
hook_event_name: "PreToolUse",
|
|
4380
|
+
cwd,
|
|
4381
|
+
tool_name: "Bash",
|
|
4382
|
+
tool_use_id: "tool-git-commit-lore-env-enabled",
|
|
4383
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
4384
|
+
},
|
|
4385
|
+
{ cwd },
|
|
4386
|
+
);
|
|
4387
|
+
|
|
4388
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4389
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4390
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4391
|
+
} finally {
|
|
4392
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4393
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4394
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4395
|
+
}
|
|
4396
|
+
});
|
|
4397
|
+
|
|
4398
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
|
|
3824
4399
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
|
|
3825
4400
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3826
4401
|
try {
|
|
3827
|
-
process.env.OMX_LORE_COMMIT_GUARD
|
|
4402
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3828
4403
|
const result = await dispatchCodexNativeHook(
|
|
3829
4404
|
{
|
|
3830
4405
|
hook_event_name: "PreToolUse",
|
|
@@ -3845,6 +4420,80 @@ exit 0
|
|
|
3845
4420
|
}
|
|
3846
4421
|
});
|
|
3847
4422
|
|
|
4423
|
+
it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
|
|
4424
|
+
await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
|
|
4425
|
+
const result = await dispatchCodexNativeHook(
|
|
4426
|
+
{
|
|
4427
|
+
hook_event_name: "PreToolUse",
|
|
4428
|
+
cwd,
|
|
4429
|
+
tool_name: "Bash",
|
|
4430
|
+
tool_use_id: "tool-git-commit-lore-config-enabled",
|
|
4431
|
+
tool_input: { command: 'git commit -m "fix: conventional"' },
|
|
4432
|
+
},
|
|
4433
|
+
{ cwd },
|
|
4434
|
+
);
|
|
4435
|
+
|
|
4436
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4437
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4438
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4439
|
+
});
|
|
4440
|
+
});
|
|
4441
|
+
|
|
4442
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
|
|
4443
|
+
await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
|
|
4444
|
+
const result = await dispatchCodexNativeHook(
|
|
4445
|
+
{
|
|
4446
|
+
hook_event_name: "PreToolUse",
|
|
4447
|
+
cwd,
|
|
4448
|
+
tool_name: "Bash",
|
|
4449
|
+
tool_use_id: "tool-git-commit-lore-config-disabled",
|
|
4450
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
4451
|
+
},
|
|
4452
|
+
{ cwd },
|
|
4453
|
+
);
|
|
4454
|
+
|
|
4455
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4456
|
+
assert.equal(result.outputJson, null);
|
|
4457
|
+
});
|
|
4458
|
+
});
|
|
4459
|
+
|
|
4460
|
+
it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
|
|
4461
|
+
await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
|
|
4462
|
+
const result = await dispatchCodexNativeHook(
|
|
4463
|
+
{
|
|
4464
|
+
hook_event_name: "PreToolUse",
|
|
4465
|
+
cwd,
|
|
4466
|
+
tool_name: "Bash",
|
|
4467
|
+
tool_use_id: "tool-git-commit-lore-config-inline-enabled",
|
|
4468
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
|
|
4469
|
+
},
|
|
4470
|
+
{ cwd },
|
|
4471
|
+
);
|
|
4472
|
+
|
|
4473
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4474
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
4475
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4476
|
+
});
|
|
4477
|
+
});
|
|
4478
|
+
|
|
4479
|
+
it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
|
|
4480
|
+
await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
|
|
4481
|
+
const result = await dispatchCodexNativeHook(
|
|
4482
|
+
{
|
|
4483
|
+
hook_event_name: "PreToolUse",
|
|
4484
|
+
cwd,
|
|
4485
|
+
tool_name: "Bash",
|
|
4486
|
+
tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
|
|
4487
|
+
tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
|
|
4488
|
+
},
|
|
4489
|
+
{ cwd },
|
|
4490
|
+
);
|
|
4491
|
+
|
|
4492
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4493
|
+
assert.equal(result.outputJson, null);
|
|
4494
|
+
});
|
|
4495
|
+
});
|
|
4496
|
+
|
|
3848
4497
|
it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
|
|
3849
4498
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
|
|
3850
4499
|
try {
|
|
@@ -3866,7 +4515,33 @@ exit 0
|
|
|
3866
4515
|
}
|
|
3867
4516
|
});
|
|
3868
4517
|
|
|
3869
|
-
|
|
4518
|
+
|
|
4519
|
+
it("allows inline disabled guard to override an enabled process env", async () => {
|
|
4520
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
|
|
4521
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
4522
|
+
try {
|
|
4523
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
4524
|
+
const result = await dispatchCodexNativeHook(
|
|
4525
|
+
{
|
|
4526
|
+
hook_event_name: "PreToolUse",
|
|
4527
|
+
cwd,
|
|
4528
|
+
tool_name: "Bash",
|
|
4529
|
+
tool_use_id: "tool-git-commit-lore-inline-override-disabled",
|
|
4530
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
4531
|
+
},
|
|
4532
|
+
{ cwd },
|
|
4533
|
+
);
|
|
4534
|
+
|
|
4535
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4536
|
+
assert.equal(result.outputJson, null);
|
|
4537
|
+
} finally {
|
|
4538
|
+
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4539
|
+
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
4540
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4541
|
+
}
|
|
4542
|
+
});
|
|
4543
|
+
|
|
4544
|
+
it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
|
|
3870
4545
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
|
|
3871
4546
|
try {
|
|
3872
4547
|
const result = await dispatchCodexNativeHook(
|
|
@@ -3875,24 +4550,41 @@ exit 0
|
|
|
3875
4550
|
cwd,
|
|
3876
4551
|
tool_name: "Bash",
|
|
3877
4552
|
tool_use_id: "tool-git-commit-lore-newline-assignment",
|
|
3878
|
-
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=
|
|
4553
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
|
|
3879
4554
|
},
|
|
3880
4555
|
{ cwd },
|
|
3881
4556
|
);
|
|
3882
4557
|
|
|
3883
4558
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3884
|
-
assert.equal(
|
|
3885
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4559
|
+
assert.equal(result.outputJson, null);
|
|
3886
4560
|
} finally {
|
|
3887
4561
|
await rm(cwd, { recursive: true, force: true });
|
|
3888
4562
|
}
|
|
3889
4563
|
});
|
|
3890
4564
|
|
|
3891
|
-
it("restores default-
|
|
4565
|
+
it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
|
|
4566
|
+
await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
|
|
4567
|
+
const result = await dispatchCodexNativeHook(
|
|
4568
|
+
{
|
|
4569
|
+
hook_event_name: "PreToolUse",
|
|
4570
|
+
cwd,
|
|
4571
|
+
tool_name: "Bash",
|
|
4572
|
+
tool_use_id: "tool-git-commit-lore-config-env-unset",
|
|
4573
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
4574
|
+
},
|
|
4575
|
+
{ cwd },
|
|
4576
|
+
);
|
|
4577
|
+
|
|
4578
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4579
|
+
assert.equal(result.outputJson, null);
|
|
4580
|
+
});
|
|
4581
|
+
});
|
|
4582
|
+
|
|
4583
|
+
it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
|
|
3892
4584
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
|
|
3893
4585
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3894
4586
|
try {
|
|
3895
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4587
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3896
4588
|
const result = await dispatchCodexNativeHook(
|
|
3897
4589
|
{
|
|
3898
4590
|
hook_event_name: "PreToolUse",
|
|
@@ -3905,8 +4597,7 @@ exit 0
|
|
|
3905
4597
|
);
|
|
3906
4598
|
|
|
3907
4599
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3908
|
-
assert.equal(
|
|
3909
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4600
|
+
assert.equal(result.outputJson, null);
|
|
3910
4601
|
} finally {
|
|
3911
4602
|
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3912
4603
|
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
@@ -3914,11 +4605,11 @@ exit 0
|
|
|
3914
4605
|
}
|
|
3915
4606
|
});
|
|
3916
4607
|
|
|
3917
|
-
it("restores default-
|
|
4608
|
+
it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
|
|
3918
4609
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
|
|
3919
4610
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3920
4611
|
try {
|
|
3921
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
4612
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3922
4613
|
const result = await dispatchCodexNativeHook(
|
|
3923
4614
|
{
|
|
3924
4615
|
hook_event_name: "PreToolUse",
|
|
@@ -3931,8 +4622,7 @@ exit 0
|
|
|
3931
4622
|
);
|
|
3932
4623
|
|
|
3933
4624
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3934
|
-
assert.equal(
|
|
3935
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4625
|
+
assert.equal(result.outputJson, null);
|
|
3936
4626
|
} finally {
|
|
3937
4627
|
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3938
4628
|
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
@@ -3940,7 +4630,7 @@ exit 0
|
|
|
3940
4630
|
}
|
|
3941
4631
|
});
|
|
3942
4632
|
|
|
3943
|
-
it("keeps Lore commit enforcement
|
|
4633
|
+
it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
|
|
3944
4634
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
|
|
3945
4635
|
try {
|
|
3946
4636
|
const result = await dispatchCodexNativeHook(
|
|
@@ -3955,8 +4645,7 @@ exit 0
|
|
|
3955
4645
|
);
|
|
3956
4646
|
|
|
3957
4647
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3958
|
-
assert.equal(
|
|
3959
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4648
|
+
assert.equal(result.outputJson, null);
|
|
3960
4649
|
} finally {
|
|
3961
4650
|
await rm(cwd, { recursive: true, force: true });
|
|
3962
4651
|
}
|
|
@@ -3987,7 +4676,7 @@ exit 0
|
|
|
3987
4676
|
}
|
|
3988
4677
|
});
|
|
3989
4678
|
|
|
3990
|
-
it("keeps Lore commit enforcement
|
|
4679
|
+
it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
|
|
3991
4680
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
|
|
3992
4681
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3993
4682
|
try {
|
|
@@ -4004,8 +4693,7 @@ exit 0
|
|
|
4004
4693
|
);
|
|
4005
4694
|
|
|
4006
4695
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
4007
|
-
assert.equal(
|
|
4008
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
4696
|
+
assert.equal(result.outputJson, null);
|
|
4009
4697
|
} finally {
|
|
4010
4698
|
if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
4011
4699
|
else process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
@@ -4111,7 +4799,7 @@ exit 0
|
|
|
4111
4799
|
cwd,
|
|
4112
4800
|
tool_name: "Bash",
|
|
4113
4801
|
tool_use_id: "tool-git-commit-env-invalid",
|
|
4114
|
-
tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
|
|
4802
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
|
|
4115
4803
|
},
|
|
4116
4804
|
{ cwd },
|
|
4117
4805
|
);
|
|
@@ -4146,7 +4834,7 @@ exit 0
|
|
|
4146
4834
|
cwd,
|
|
4147
4835
|
tool_name: "Bash",
|
|
4148
4836
|
tool_use_id: "tool-git-commit-option-invalid",
|
|
4149
|
-
tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
|
|
4837
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
|
|
4150
4838
|
},
|
|
4151
4839
|
{ cwd },
|
|
4152
4840
|
);
|
|
@@ -4181,7 +4869,7 @@ exit 0
|
|
|
4181
4869
|
cwd,
|
|
4182
4870
|
tool_name: "Bash",
|
|
4183
4871
|
tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
|
|
4184
|
-
tool_input: { command: 'env git.exe commit -m "fix tests"' },
|
|
4872
|
+
tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4185
4873
|
},
|
|
4186
4874
|
{ cwd },
|
|
4187
4875
|
);
|
|
@@ -4216,7 +4904,7 @@ exit 0
|
|
|
4216
4904
|
cwd,
|
|
4217
4905
|
tool_name: "Bash",
|
|
4218
4906
|
tool_use_id: "tool-git-exe-commit-invalid",
|
|
4219
|
-
tool_input: { command: 'git.exe commit -m "fix tests"' },
|
|
4907
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4220
4908
|
},
|
|
4221
4909
|
{ cwd },
|
|
4222
4910
|
);
|
|
@@ -4251,7 +4939,7 @@ exit 0
|
|
|
4251
4939
|
cwd,
|
|
4252
4940
|
tool_name: "Bash",
|
|
4253
4941
|
tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
|
|
4254
|
-
tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
|
|
4942
|
+
tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4255
4943
|
},
|
|
4256
4944
|
{ cwd },
|
|
4257
4945
|
);
|
|
@@ -4286,7 +4974,7 @@ exit 0
|
|
|
4286
4974
|
cwd,
|
|
4287
4975
|
tool_name: "Bash",
|
|
4288
4976
|
tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
|
|
4289
|
-
tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
|
|
4977
|
+
tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
4290
4978
|
},
|
|
4291
4979
|
{ cwd },
|
|
4292
4980
|
);
|
|
@@ -4321,7 +5009,7 @@ exit 0
|
|
|
4321
5009
|
cwd,
|
|
4322
5010
|
tool_name: "Bash",
|
|
4323
5011
|
tool_use_id: "tool-git-exe-commit-windows-path-invalid",
|
|
4324
|
-
tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
5012
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
4325
5013
|
},
|
|
4326
5014
|
{ cwd },
|
|
4327
5015
|
);
|
|
@@ -4356,7 +5044,7 @@ exit 0
|
|
|
4356
5044
|
cwd,
|
|
4357
5045
|
tool_name: "Bash",
|
|
4358
5046
|
tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
|
|
4359
|
-
tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
5047
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
4360
5048
|
},
|
|
4361
5049
|
{ cwd },
|
|
4362
5050
|
);
|
|
@@ -4391,7 +5079,7 @@ exit 0
|
|
|
4391
5079
|
cwd,
|
|
4392
5080
|
tool_name: "Bash",
|
|
4393
5081
|
tool_use_id: "tool-git-commit-path-invalid",
|
|
4394
|
-
tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
|
|
5082
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
|
|
4395
5083
|
},
|
|
4396
5084
|
{ cwd },
|
|
4397
5085
|
);
|
|
@@ -4426,7 +5114,7 @@ exit 0
|
|
|
4426
5114
|
cwd,
|
|
4427
5115
|
tool_name: "Bash",
|
|
4428
5116
|
tool_use_id: "tool-git-commit-file",
|
|
4429
|
-
tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
|
|
5117
|
+
tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
|
|
4430
5118
|
},
|
|
4431
5119
|
{ cwd },
|
|
4432
5120
|
);
|
|
@@ -4460,7 +5148,7 @@ exit 0
|
|
|
4460
5148
|
tool_use_id: "tool-git-commit-missing-omx-coauthor",
|
|
4461
5149
|
tool_input: {
|
|
4462
5150
|
command: [
|
|
4463
|
-
'git commit',
|
|
5151
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4464
5152
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
4465
5153
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
4466
5154
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -4500,7 +5188,7 @@ exit 0
|
|
|
4500
5188
|
tool_use_id: "tool-git-commit-valid",
|
|
4501
5189
|
tool_input: {
|
|
4502
5190
|
command: [
|
|
4503
|
-
'git commit',
|
|
5191
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4504
5192
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
4505
5193
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
4506
5194
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -4530,7 +5218,7 @@ exit 0
|
|
|
4530
5218
|
tool_use_id: "tool-git-commit-compact-coauthor",
|
|
4531
5219
|
tool_input: {
|
|
4532
5220
|
command: [
|
|
4533
|
-
'git commit',
|
|
5221
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4534
5222
|
'-m "Launch lvisai.xyz intro site"',
|
|
4535
5223
|
'-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
4536
5224
|
].join(" "),
|
|
@@ -4557,7 +5245,7 @@ exit 0
|
|
|
4557
5245
|
tool_use_id: "tool-git-commit-compact-trailers",
|
|
4558
5246
|
tool_input: {
|
|
4559
5247
|
command: [
|
|
4560
|
-
'git commit',
|
|
5248
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4561
5249
|
'-m "Launch lvisai.xyz intro site"',
|
|
4562
5250
|
'-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
4563
5251
|
].join(" "),
|
|
@@ -4584,7 +5272,7 @@ exit 0
|
|
|
4584
5272
|
tool_use_id: "tool-git-commit-compact-no-separator",
|
|
4585
5273
|
tool_input: {
|
|
4586
5274
|
command: [
|
|
4587
|
-
'git commit',
|
|
5275
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
4588
5276
|
'--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
4589
5277
|
].join(" "),
|
|
4590
5278
|
},
|
|
@@ -5617,6 +6305,91 @@ exit 0
|
|
|
5617
6305
|
}
|
|
5618
6306
|
});
|
|
5619
6307
|
|
|
6308
|
+
it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
|
|
6309
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
|
|
6310
|
+
try {
|
|
6311
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6312
|
+
const sessionId = "sess-stop-autopilot-terminal-run-state";
|
|
6313
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
6314
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
6315
|
+
active: true,
|
|
6316
|
+
mode: "autopilot",
|
|
6317
|
+
current_phase: "ralplan",
|
|
6318
|
+
});
|
|
6319
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
6320
|
+
version: 1,
|
|
6321
|
+
active: false,
|
|
6322
|
+
mode: "autopilot",
|
|
6323
|
+
outcome: "finish",
|
|
6324
|
+
lifecycle_outcome: "finished",
|
|
6325
|
+
current_phase: "complete",
|
|
6326
|
+
completed_at: "2026-05-20T11:00:00.000Z",
|
|
6327
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
6328
|
+
});
|
|
6329
|
+
|
|
6330
|
+
const result = await dispatchCodexNativeHook(
|
|
6331
|
+
{
|
|
6332
|
+
hook_event_name: "Stop",
|
|
6333
|
+
cwd,
|
|
6334
|
+
session_id: sessionId,
|
|
6335
|
+
thread_id: "thread-stop-autopilot-terminal-run-state",
|
|
6336
|
+
turn_id: "turn-stop-autopilot-terminal-run-state-1",
|
|
6337
|
+
last_assistant_message: "Done. Verification passed.",
|
|
6338
|
+
},
|
|
6339
|
+
{ cwd },
|
|
6340
|
+
);
|
|
6341
|
+
|
|
6342
|
+
assert.equal(result.omxEventName, "stop");
|
|
6343
|
+
assert.equal(result.outputJson, null);
|
|
6344
|
+
} finally {
|
|
6345
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6346
|
+
}
|
|
6347
|
+
});
|
|
6348
|
+
|
|
6349
|
+
it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
|
|
6350
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
|
|
6351
|
+
try {
|
|
6352
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6353
|
+
const sessionId = "sess-stop-autopilot-active-ralplan";
|
|
6354
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
6355
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
6356
|
+
active: true,
|
|
6357
|
+
mode: "autopilot",
|
|
6358
|
+
current_phase: "ralplan",
|
|
6359
|
+
});
|
|
6360
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
6361
|
+
version: 1,
|
|
6362
|
+
active: true,
|
|
6363
|
+
mode: "autopilot",
|
|
6364
|
+
outcome: "continue",
|
|
6365
|
+
current_phase: "ralplan",
|
|
6366
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
6367
|
+
});
|
|
6368
|
+
|
|
6369
|
+
const result = await dispatchCodexNativeHook(
|
|
6370
|
+
{
|
|
6371
|
+
hook_event_name: "Stop",
|
|
6372
|
+
cwd,
|
|
6373
|
+
session_id: sessionId,
|
|
6374
|
+
thread_id: "thread-stop-autopilot-active-ralplan",
|
|
6375
|
+
turn_id: "turn-stop-autopilot-active-ralplan-1",
|
|
6376
|
+
},
|
|
6377
|
+
{ cwd },
|
|
6378
|
+
);
|
|
6379
|
+
|
|
6380
|
+
assert.equal(result.omxEventName, "stop");
|
|
6381
|
+
assert.deepEqual(result.outputJson, {
|
|
6382
|
+
decision: "block",
|
|
6383
|
+
reason:
|
|
6384
|
+
"OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
|
|
6385
|
+
stopReason: "autopilot_ralplan",
|
|
6386
|
+
systemMessage: "OMX autopilot is still active (phase: ralplan).",
|
|
6387
|
+
});
|
|
6388
|
+
} finally {
|
|
6389
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6390
|
+
}
|
|
6391
|
+
});
|
|
6392
|
+
|
|
5620
6393
|
it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
|
|
5621
6394
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
|
|
5622
6395
|
try {
|
|
@@ -8381,16 +9154,29 @@ exit 0
|
|
|
8381
9154
|
assert.equal(result.omxEventName, "stop");
|
|
8382
9155
|
const reason = String(result.outputJson?.reason);
|
|
8383
9156
|
assert.match(reason, /Ralph completion audit is missing required evidence/);
|
|
8384
|
-
assert.match(reason, /
|
|
9157
|
+
assert.match(reason, /set "completion_audit" on the Ralph state object/);
|
|
9158
|
+
assert.doesNotMatch(reason, /state\.completion_audit/);
|
|
8385
9159
|
assert.match(reason, /repo-relative JSON file/);
|
|
8386
9160
|
assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
|
|
8387
9161
|
assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
8388
9162
|
const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
|
|
8389
|
-
assert.equal(reopened.active,
|
|
8390
|
-
assert.equal(reopened.current_phase, "
|
|
9163
|
+
assert.equal(reopened.active, false);
|
|
9164
|
+
assert.equal(reopened.current_phase, "complete");
|
|
8391
9165
|
assert.equal(reopened.completion_audit_gate, "blocked");
|
|
8392
9166
|
assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
|
|
8393
|
-
assert.equal(
|
|
9167
|
+
assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
|
|
9168
|
+
|
|
9169
|
+
const repeat = await dispatchCodexNativeHook(
|
|
9170
|
+
{
|
|
9171
|
+
hook_event_name: "Stop",
|
|
9172
|
+
cwd,
|
|
9173
|
+
session_id: sessionId,
|
|
9174
|
+
last_assistant_message: "Done. Ralph complete.",
|
|
9175
|
+
},
|
|
9176
|
+
{ cwd },
|
|
9177
|
+
);
|
|
9178
|
+
assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
9179
|
+
assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
|
|
8394
9180
|
} finally {
|
|
8395
9181
|
await rm(cwd, { recursive: true, force: true });
|
|
8396
9182
|
}
|