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
|
@@ -17,6 +17,7 @@ import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
|
|
|
17
17
|
import { readAllState } from "../../hud/state.js";
|
|
18
18
|
import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
|
|
19
19
|
import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
|
|
20
|
+
import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
|
|
20
21
|
function nativeHookScriptPath() {
|
|
21
22
|
return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
|
|
22
23
|
}
|
|
@@ -39,6 +40,38 @@ async function writeJson(path, value) {
|
|
|
39
40
|
await mkdir(dirname(path), { recursive: true }).catch(() => { });
|
|
40
41
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
41
42
|
}
|
|
43
|
+
async function withLoreGuardConfig(value, prefix, run) {
|
|
44
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
|
|
45
|
+
const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
|
|
46
|
+
const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
|
|
47
|
+
const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
|
|
48
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
49
|
+
const originalHome = process.env.HOME;
|
|
50
|
+
try {
|
|
51
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
52
|
+
process.env.CODEX_HOME = codexHome;
|
|
53
|
+
process.env.HOME = defaultHome;
|
|
54
|
+
await writeFile(join(codexHome, "config.toml"), `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`, "utf-8");
|
|
55
|
+
return await run(cwd);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (originalGuard === undefined)
|
|
59
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
60
|
+
else
|
|
61
|
+
process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
|
|
62
|
+
if (originalCodexHome === undefined)
|
|
63
|
+
delete process.env.CODEX_HOME;
|
|
64
|
+
else
|
|
65
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
66
|
+
if (originalHome === undefined)
|
|
67
|
+
delete process.env.HOME;
|
|
68
|
+
else
|
|
69
|
+
process.env.HOME = originalHome;
|
|
70
|
+
await rm(cwd, { recursive: true, force: true });
|
|
71
|
+
await rm(codexHome, { recursive: true, force: true });
|
|
72
|
+
await rm(defaultHome, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
42
75
|
function buildWorkerStopFakeTmux(tmuxLogPath, options = {}) {
|
|
43
76
|
return `#!/usr/bin/env bash
|
|
44
77
|
set -eu
|
|
@@ -567,7 +600,16 @@ describe("codex native hook dispatch", () => {
|
|
|
567
600
|
});
|
|
568
601
|
it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
|
|
569
602
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
|
|
570
|
-
|
|
603
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
604
|
+
try {
|
|
605
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
606
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
607
|
+
notifications: {
|
|
608
|
+
enabled: true,
|
|
609
|
+
verbosity: "session",
|
|
610
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
611
|
+
},
|
|
612
|
+
});
|
|
571
613
|
const stateDir = join(cwd, ".omx", "state");
|
|
572
614
|
const canonicalSessionId = "omx-leader-session";
|
|
573
615
|
const leaderNativeSessionId = "codex-leader-thread";
|
|
@@ -583,6 +625,13 @@ describe("codex native hook dispatch", () => {
|
|
|
583
625
|
iteration: 1,
|
|
584
626
|
max_iterations: 5,
|
|
585
627
|
});
|
|
628
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
629
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
630
|
+
"import { appendFileSync } from 'node:fs';",
|
|
631
|
+
"export async function onHookEvent(event) {",
|
|
632
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
|
|
633
|
+
"}",
|
|
634
|
+
].join("\n"));
|
|
586
635
|
const transcriptPath = join(cwd, "subagent-rollout.jsonl");
|
|
587
636
|
await writeFile(transcriptPath, `${JSON.stringify({
|
|
588
637
|
type: "session_meta",
|
|
@@ -616,6 +665,7 @@ describe("codex native hook dispatch", () => {
|
|
|
616
665
|
const leaderRalph = JSON.parse(await readFile(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), "utf-8"));
|
|
617
666
|
assert.equal(leaderRalph.active, true);
|
|
618
667
|
assert.equal(leaderRalph.current_phase, "executing");
|
|
668
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must not independently dispatch session-start hook notifications");
|
|
619
669
|
const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"));
|
|
620
670
|
assert.equal(tracking.sessions?.[canonicalSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
621
671
|
assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
@@ -623,8 +673,213 @@ describe("codex native hook dispatch", () => {
|
|
|
623
673
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
|
|
624
674
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
|
|
625
675
|
assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
|
|
676
|
+
await dispatchCodexNativeHook({
|
|
677
|
+
hook_event_name: "Stop",
|
|
678
|
+
cwd,
|
|
679
|
+
session_id: childNativeSessionId,
|
|
680
|
+
thread_id: childNativeSessionId,
|
|
681
|
+
turn_id: "child-stop-turn",
|
|
682
|
+
}, { cwd });
|
|
683
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent Stop must not independently dispatch stop hook notifications");
|
|
684
|
+
}
|
|
685
|
+
finally {
|
|
686
|
+
if (originalCodexHome === undefined) {
|
|
687
|
+
delete process.env.CODEX_HOME;
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
691
|
+
}
|
|
692
|
+
await rm(cwd, { recursive: true, force: true });
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
|
|
696
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
|
|
697
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
698
|
+
try {
|
|
699
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
700
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
701
|
+
notifications: {
|
|
702
|
+
enabled: true,
|
|
703
|
+
verbosity: "minimal",
|
|
704
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
708
|
+
const canonicalSessionId = "omx-leader-session-minimal";
|
|
709
|
+
const leaderNativeSessionId = "codex-leader-thread-minimal";
|
|
710
|
+
const childNativeSessionId = "codex-child-thread-minimal";
|
|
711
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
712
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
713
|
+
nativeSessionId: leaderNativeSessionId,
|
|
714
|
+
});
|
|
715
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
716
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
717
|
+
"import { appendFileSync } from 'node:fs';",
|
|
718
|
+
"export async function onHookEvent(event) {",
|
|
719
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
720
|
+
"}",
|
|
721
|
+
].join("\n"));
|
|
722
|
+
const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
|
|
723
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
724
|
+
type: "session_meta",
|
|
725
|
+
payload: {
|
|
726
|
+
id: childNativeSessionId,
|
|
727
|
+
source: {
|
|
728
|
+
subagent: {
|
|
729
|
+
thread_spawn: {
|
|
730
|
+
parent_thread_id: leaderNativeSessionId,
|
|
731
|
+
agent_role: "verifier",
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
})}\n`);
|
|
737
|
+
await dispatchCodexNativeHook({
|
|
738
|
+
hook_event_name: "SessionStart",
|
|
739
|
+
cwd,
|
|
740
|
+
session_id: childNativeSessionId,
|
|
741
|
+
transcript_path: transcriptPath,
|
|
742
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
743
|
+
assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must be suppressed at minimal verbosity");
|
|
744
|
+
}
|
|
745
|
+
finally {
|
|
746
|
+
if (originalCodexHome === undefined) {
|
|
747
|
+
delete process.env.CODEX_HOME;
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
751
|
+
}
|
|
752
|
+
await rm(cwd, { recursive: true, force: true });
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
|
|
756
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
|
|
757
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
758
|
+
try {
|
|
759
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
760
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
761
|
+
notifications: {
|
|
762
|
+
enabled: true,
|
|
763
|
+
verbosity: "session",
|
|
764
|
+
includeChildAgents: true,
|
|
765
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
769
|
+
const canonicalSessionId = "omx-leader-session-include";
|
|
770
|
+
const leaderNativeSessionId = "codex-leader-thread-include";
|
|
771
|
+
const childNativeSessionId = "codex-child-thread-include";
|
|
772
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
773
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
774
|
+
nativeSessionId: leaderNativeSessionId,
|
|
775
|
+
});
|
|
776
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
777
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
778
|
+
"import { appendFileSync } from 'node:fs';",
|
|
779
|
+
"export async function onHookEvent(event) {",
|
|
780
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
781
|
+
"}",
|
|
782
|
+
].join("\n"));
|
|
783
|
+
const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
|
|
784
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
785
|
+
type: "session_meta",
|
|
786
|
+
payload: {
|
|
787
|
+
id: childNativeSessionId,
|
|
788
|
+
source: {
|
|
789
|
+
subagent: {
|
|
790
|
+
thread_spawn: {
|
|
791
|
+
parent_thread_id: leaderNativeSessionId,
|
|
792
|
+
agent_role: "verifier",
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
})}\n`);
|
|
798
|
+
await dispatchCodexNativeHook({
|
|
799
|
+
hook_event_name: "SessionStart",
|
|
800
|
+
cwd,
|
|
801
|
+
session_id: childNativeSessionId,
|
|
802
|
+
transcript_path: transcriptPath,
|
|
803
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
804
|
+
await dispatchCodexNativeHook({
|
|
805
|
+
hook_event_name: "Stop",
|
|
806
|
+
cwd,
|
|
807
|
+
session_id: childNativeSessionId,
|
|
808
|
+
thread_id: childNativeSessionId,
|
|
809
|
+
turn_id: "included-child-stop-turn",
|
|
810
|
+
}, { cwd });
|
|
811
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
812
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
813
|
+
assert.match(hookEvents, /"event":"stop"/);
|
|
814
|
+
}
|
|
815
|
+
finally {
|
|
816
|
+
if (originalCodexHome === undefined) {
|
|
817
|
+
delete process.env.CODEX_HOME;
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
821
|
+
}
|
|
822
|
+
await rm(cwd, { recursive: true, force: true });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
|
|
826
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
|
|
827
|
+
const originalCodexHome = process.env.CODEX_HOME;
|
|
828
|
+
try {
|
|
829
|
+
process.env.CODEX_HOME = join(cwd, "codex-home");
|
|
830
|
+
await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
|
|
831
|
+
notifications: {
|
|
832
|
+
enabled: true,
|
|
833
|
+
verbosity: "agent",
|
|
834
|
+
telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
838
|
+
const canonicalSessionId = "omx-leader-session-agent";
|
|
839
|
+
const leaderNativeSessionId = "codex-leader-thread-agent";
|
|
840
|
+
const childNativeSessionId = "codex-child-thread-agent";
|
|
841
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
842
|
+
await writeSessionStart(cwd, canonicalSessionId, {
|
|
843
|
+
nativeSessionId: leaderNativeSessionId,
|
|
844
|
+
});
|
|
845
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
846
|
+
await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
|
|
847
|
+
"import { appendFileSync } from 'node:fs';",
|
|
848
|
+
"export async function onHookEvent(event) {",
|
|
849
|
+
" appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
|
|
850
|
+
"}",
|
|
851
|
+
].join("\n"));
|
|
852
|
+
const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
|
|
853
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
854
|
+
type: "session_meta",
|
|
855
|
+
payload: {
|
|
856
|
+
id: childNativeSessionId,
|
|
857
|
+
source: {
|
|
858
|
+
subagent: {
|
|
859
|
+
thread_spawn: {
|
|
860
|
+
parent_thread_id: leaderNativeSessionId,
|
|
861
|
+
agent_role: "verifier",
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
})}\n`);
|
|
867
|
+
await dispatchCodexNativeHook({
|
|
868
|
+
hook_event_name: "SessionStart",
|
|
869
|
+
cwd,
|
|
870
|
+
session_id: childNativeSessionId,
|
|
871
|
+
transcript_path: transcriptPath,
|
|
872
|
+
}, { cwd, sessionOwnerPid: process.pid });
|
|
873
|
+
const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
|
|
874
|
+
assert.match(hookEvents, /"event":"session-start"/);
|
|
626
875
|
}
|
|
627
876
|
finally {
|
|
877
|
+
if (originalCodexHome === undefined) {
|
|
878
|
+
delete process.env.CODEX_HOME;
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
process.env.CODEX_HOME = originalCodexHome;
|
|
882
|
+
}
|
|
628
883
|
await rm(cwd, { recursive: true, force: true });
|
|
629
884
|
}
|
|
630
885
|
});
|
|
@@ -1218,6 +1473,62 @@ describe("codex native hook dispatch", () => {
|
|
|
1218
1473
|
await rm(cwd, { recursive: true, force: true });
|
|
1219
1474
|
}
|
|
1220
1475
|
});
|
|
1476
|
+
it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
|
|
1477
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
|
|
1478
|
+
try {
|
|
1479
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1480
|
+
version: 1,
|
|
1481
|
+
workflow: "performance-goal",
|
|
1482
|
+
slug: "latency",
|
|
1483
|
+
objective: "Reduce latency",
|
|
1484
|
+
status: "blocked",
|
|
1485
|
+
lastValidation: {
|
|
1486
|
+
status: "blocked",
|
|
1487
|
+
evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
|
|
1488
|
+
recordedAt: "2026-05-20T00:00:00.000Z",
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
const result = await dispatchCodexNativeHook({
|
|
1492
|
+
hook_event_name: "Stop",
|
|
1493
|
+
cwd,
|
|
1494
|
+
session_id: "sess-performance-mismatch-blocked-stop",
|
|
1495
|
+
thread_id: "thread-performance-mismatch-blocked-stop",
|
|
1496
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1497
|
+
}, { cwd });
|
|
1498
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1499
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1500
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1501
|
+
}
|
|
1502
|
+
finally {
|
|
1503
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
it("does not block Stop for an already complete performance-goal state", async () => {
|
|
1507
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
|
|
1508
|
+
try {
|
|
1509
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
1510
|
+
version: 1,
|
|
1511
|
+
workflow: "performance-goal",
|
|
1512
|
+
slug: "latency",
|
|
1513
|
+
objective: "Reduce latency",
|
|
1514
|
+
status: "complete",
|
|
1515
|
+
completedAt: "2026-05-20T00:00:00.000Z",
|
|
1516
|
+
});
|
|
1517
|
+
const result = await dispatchCodexNativeHook({
|
|
1518
|
+
hook_event_name: "Stop",
|
|
1519
|
+
cwd,
|
|
1520
|
+
session_id: "sess-performance-complete-stop",
|
|
1521
|
+
thread_id: "thread-performance-complete-stop",
|
|
1522
|
+
last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
|
|
1523
|
+
}, { cwd });
|
|
1524
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
1525
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
|
|
1526
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
|
|
1527
|
+
}
|
|
1528
|
+
finally {
|
|
1529
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1221
1532
|
it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
|
|
1222
1533
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
|
|
1223
1534
|
try {
|
|
@@ -1262,7 +1573,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1262
1573
|
await rm(cwd, { recursive: true, force: true });
|
|
1263
1574
|
}
|
|
1264
1575
|
});
|
|
1265
|
-
it("blocks ultragoal Stop with blocked checkpoint and
|
|
1576
|
+
it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
|
|
1266
1577
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
|
|
1267
1578
|
try {
|
|
1268
1579
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
@@ -1281,7 +1592,8 @@ describe("codex native hook dispatch", () => {
|
|
|
1281
1592
|
assert.equal(result.outputJson?.decision, "block");
|
|
1282
1593
|
assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
|
|
1283
1594
|
assert.match(output, /--status blocked/);
|
|
1284
|
-
assert.match(output, /
|
|
1595
|
+
assert.match(output, /Codex goal context/);
|
|
1596
|
+
assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
|
|
1285
1597
|
assert.match(output, /Hooks must not mutate Codex goal state/);
|
|
1286
1598
|
}
|
|
1287
1599
|
finally {
|
|
@@ -1294,7 +1606,7 @@ describe("codex native hook dispatch", () => {
|
|
|
1294
1606
|
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
1295
1607
|
version: 1,
|
|
1296
1608
|
codexGoalMode: "aggregate",
|
|
1297
|
-
codexObjective: "Complete
|
|
1609
|
+
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.",
|
|
1298
1610
|
activeGoalId: "G001-micro",
|
|
1299
1611
|
aggregateCompletion: {
|
|
1300
1612
|
status: "complete",
|
|
@@ -1573,12 +1885,168 @@ describe("codex native hook dispatch", () => {
|
|
|
1573
1885
|
assert.match(message, /get_goal/);
|
|
1574
1886
|
assert.match(message, /create_goal/);
|
|
1575
1887
|
assert.match(message, /update_goal/);
|
|
1888
|
+
assert.match(message, /does not call `\/goal clear`/);
|
|
1889
|
+
assert.match(message, /multiple sequential ultragoal runs/);
|
|
1576
1890
|
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
|
|
1577
1891
|
}
|
|
1578
1892
|
finally {
|
|
1579
1893
|
await rm(cwd, { recursive: true, force: true });
|
|
1580
1894
|
}
|
|
1581
1895
|
});
|
|
1896
|
+
it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
|
|
1897
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
|
|
1898
|
+
try {
|
|
1899
|
+
await createUltragoalPlan(cwd, {
|
|
1900
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
|
|
1901
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
1902
|
+
});
|
|
1903
|
+
const prose = await dispatchCodexNativeHook({
|
|
1904
|
+
hook_event_name: "UserPromptSubmit",
|
|
1905
|
+
cwd,
|
|
1906
|
+
session_id: "sess-ultragoal-steer-1",
|
|
1907
|
+
prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
|
|
1908
|
+
}, { cwd });
|
|
1909
|
+
assert.equal(prose.outputJson, null);
|
|
1910
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
1911
|
+
const jsonExample = await dispatchCodexNativeHook({
|
|
1912
|
+
hook_event_name: "UserPromptSubmit",
|
|
1913
|
+
cwd,
|
|
1914
|
+
session_id: "sess-ultragoal-steer-1",
|
|
1915
|
+
prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
|
|
1916
|
+
kind: "add_subgoal",
|
|
1917
|
+
source: "user_prompt_submit",
|
|
1918
|
+
evidence: "Example JSON should not mutate .omx/ultragoal.",
|
|
1919
|
+
rationale: "Only explicit steering fences or labels are executable.",
|
|
1920
|
+
title: "Inert JSON example",
|
|
1921
|
+
objective: "This example must not be added.",
|
|
1922
|
+
})}\n\`\`\``,
|
|
1923
|
+
}, { cwd });
|
|
1924
|
+
assert.equal(jsonExample.outputJson, null);
|
|
1925
|
+
assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
|
|
1926
|
+
const result = await dispatchCodexNativeHook({
|
|
1927
|
+
hook_event_name: "UserPromptSubmit",
|
|
1928
|
+
cwd,
|
|
1929
|
+
session_id: "sess-ultragoal-steer-1",
|
|
1930
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
1931
|
+
kind: "add_subgoal",
|
|
1932
|
+
source: "user_prompt_submit",
|
|
1933
|
+
evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
|
|
1934
|
+
rationale: "Add bounded hook regression work while preserving all completion gates.",
|
|
1935
|
+
title: "Prompt bridge regression",
|
|
1936
|
+
objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
|
|
1937
|
+
})}`,
|
|
1938
|
+
}, { cwd });
|
|
1939
|
+
const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
1940
|
+
assert.match(message, /bounded \.omx\/ultragoal steering/);
|
|
1941
|
+
assert.match(message, /G002-cli-and-prompt-submit-bridge/);
|
|
1942
|
+
assert.match(message, /accepted/);
|
|
1943
|
+
const plan = await readUltragoalPlan(cwd);
|
|
1944
|
+
assert.equal(plan.goals.length, 2);
|
|
1945
|
+
assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
|
|
1946
|
+
}
|
|
1947
|
+
finally {
|
|
1948
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
|
|
1952
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
|
|
1953
|
+
try {
|
|
1954
|
+
await createUltragoalPlan(cwd, {
|
|
1955
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
|
|
1956
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
1957
|
+
});
|
|
1958
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1959
|
+
const canonicalSessionId = "sess-ultragoal-parent";
|
|
1960
|
+
const leaderNativeSessionId = "native-ultragoal-parent";
|
|
1961
|
+
const childNativeSessionId = "native-ultragoal-child";
|
|
1962
|
+
const nowIso = new Date().toISOString();
|
|
1963
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
1964
|
+
session_id: canonicalSessionId,
|
|
1965
|
+
native_session_id: leaderNativeSessionId,
|
|
1966
|
+
});
|
|
1967
|
+
await writeJson(join(stateDir, "subagent-tracking.json"), {
|
|
1968
|
+
schemaVersion: 1,
|
|
1969
|
+
sessions: {
|
|
1970
|
+
[canonicalSessionId]: {
|
|
1971
|
+
session_id: canonicalSessionId,
|
|
1972
|
+
leader_thread_id: leaderNativeSessionId,
|
|
1973
|
+
updated_at: nowIso,
|
|
1974
|
+
threads: {
|
|
1975
|
+
[leaderNativeSessionId]: {
|
|
1976
|
+
thread_id: leaderNativeSessionId,
|
|
1977
|
+
kind: "leader",
|
|
1978
|
+
first_seen_at: nowIso,
|
|
1979
|
+
last_seen_at: nowIso,
|
|
1980
|
+
turn_count: 1,
|
|
1981
|
+
},
|
|
1982
|
+
[childNativeSessionId]: {
|
|
1983
|
+
thread_id: childNativeSessionId,
|
|
1984
|
+
kind: "subagent",
|
|
1985
|
+
first_seen_at: nowIso,
|
|
1986
|
+
last_seen_at: nowIso,
|
|
1987
|
+
turn_count: 1,
|
|
1988
|
+
mode: "architect",
|
|
1989
|
+
},
|
|
1990
|
+
},
|
|
1991
|
+
},
|
|
1992
|
+
},
|
|
1993
|
+
});
|
|
1994
|
+
const result = await dispatchCodexNativeHook({
|
|
1995
|
+
hook_event_name: "UserPromptSubmit",
|
|
1996
|
+
cwd,
|
|
1997
|
+
session_id: childNativeSessionId,
|
|
1998
|
+
thread_id: childNativeSessionId,
|
|
1999
|
+
turn_id: "turn-ultragoal-child-1",
|
|
2000
|
+
prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
|
|
2001
|
+
kind: "add_subgoal",
|
|
2002
|
+
source: "user_prompt_submit",
|
|
2003
|
+
evidence: "Subagent prompt text must be literal delegated context.",
|
|
2004
|
+
rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
|
|
2005
|
+
title: "Subagent should not add this",
|
|
2006
|
+
objective: "This must remain literal prompt text.",
|
|
2007
|
+
})}`,
|
|
2008
|
+
}, { cwd });
|
|
2009
|
+
assert.equal(result.outputJson, null);
|
|
2010
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2011
|
+
assert.equal(plan.goals.length, 1);
|
|
2012
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2013
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
|
|
2014
|
+
assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
|
|
2015
|
+
}
|
|
2016
|
+
finally {
|
|
2017
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
|
|
2021
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
|
|
2022
|
+
try {
|
|
2023
|
+
await createUltragoalPlan(cwd, {
|
|
2024
|
+
brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
|
|
2025
|
+
goals: [{ title: "First", objective: "Complete first milestone with tests." }],
|
|
2026
|
+
});
|
|
2027
|
+
const prompt = `\`\`\`omx-ultragoal-steer
|
|
2028
|
+
${JSON.stringify({
|
|
2029
|
+
kind: "add_subgoal",
|
|
2030
|
+
source: "user_prompt_submit",
|
|
2031
|
+
evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
|
|
2032
|
+
rationale: "Use idempotent bridge semantics for repeated hook delivery.",
|
|
2033
|
+
title: "Deduped bridge regression",
|
|
2034
|
+
objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
|
|
2035
|
+
})}
|
|
2036
|
+
\`\`\``;
|
|
2037
|
+
await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2038
|
+
const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
|
|
2039
|
+
const message = String(second.outputJson?.hookSpecificOutput?.additionalContext || "");
|
|
2040
|
+
assert.match(message, /deduped/);
|
|
2041
|
+
const plan = await readUltragoalPlan(cwd);
|
|
2042
|
+
assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
|
|
2043
|
+
const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
|
|
2044
|
+
assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
|
|
2045
|
+
}
|
|
2046
|
+
finally {
|
|
2047
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
1582
2050
|
it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
|
|
1583
2051
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
|
|
1584
2052
|
try {
|
|
@@ -3109,7 +3577,7 @@ exit 0
|
|
|
3109
3577
|
cwd,
|
|
3110
3578
|
tool_name: "Bash",
|
|
3111
3579
|
tool_use_id: "tool-slop-git-priority",
|
|
3112
|
-
tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
|
|
3580
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
|
|
3113
3581
|
}, { cwd });
|
|
3114
3582
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3115
3583
|
assert.equal(result.outputJson?.decision, "block");
|
|
@@ -3128,7 +3596,7 @@ exit 0
|
|
|
3128
3596
|
cwd,
|
|
3129
3597
|
tool_name: "Bash",
|
|
3130
3598
|
tool_use_id: "tool-git-commit-invalid",
|
|
3131
|
-
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3599
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
|
|
3132
3600
|
}, { cwd });
|
|
3133
3601
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3134
3602
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3153,11 +3621,35 @@ exit 0
|
|
|
3153
3621
|
await rm(cwd, { recursive: true, force: true });
|
|
3154
3622
|
}
|
|
3155
3623
|
});
|
|
3156
|
-
it("
|
|
3624
|
+
it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
|
|
3625
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
|
|
3626
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3627
|
+
try {
|
|
3628
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3629
|
+
const result = await dispatchCodexNativeHook({
|
|
3630
|
+
hook_event_name: "PreToolUse",
|
|
3631
|
+
cwd,
|
|
3632
|
+
tool_name: "Bash",
|
|
3633
|
+
tool_use_id: "tool-git-commit-lore-env-enabled",
|
|
3634
|
+
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3635
|
+
}, { cwd });
|
|
3636
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3637
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3638
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3639
|
+
}
|
|
3640
|
+
finally {
|
|
3641
|
+
if (original === undefined)
|
|
3642
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3643
|
+
else
|
|
3644
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3645
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3646
|
+
}
|
|
3647
|
+
});
|
|
3648
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
|
|
3157
3649
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
|
|
3158
3650
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3159
3651
|
try {
|
|
3160
|
-
process.env.OMX_LORE_COMMIT_GUARD
|
|
3652
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3161
3653
|
const result = await dispatchCodexNativeHook({
|
|
3162
3654
|
hook_event_name: "PreToolUse",
|
|
3163
3655
|
cwd,
|
|
@@ -3176,6 +3668,60 @@ exit 0
|
|
|
3176
3668
|
await rm(cwd, { recursive: true, force: true });
|
|
3177
3669
|
}
|
|
3178
3670
|
});
|
|
3671
|
+
it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
|
|
3672
|
+
await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
|
|
3673
|
+
const result = await dispatchCodexNativeHook({
|
|
3674
|
+
hook_event_name: "PreToolUse",
|
|
3675
|
+
cwd,
|
|
3676
|
+
tool_name: "Bash",
|
|
3677
|
+
tool_use_id: "tool-git-commit-lore-config-enabled",
|
|
3678
|
+
tool_input: { command: 'git commit -m "fix: conventional"' },
|
|
3679
|
+
}, { cwd });
|
|
3680
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3681
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3682
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3683
|
+
});
|
|
3684
|
+
});
|
|
3685
|
+
it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
|
|
3686
|
+
await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
|
|
3687
|
+
const result = await dispatchCodexNativeHook({
|
|
3688
|
+
hook_event_name: "PreToolUse",
|
|
3689
|
+
cwd,
|
|
3690
|
+
tool_name: "Bash",
|
|
3691
|
+
tool_use_id: "tool-git-commit-lore-config-disabled",
|
|
3692
|
+
tool_input: { command: 'git commit -m "fix: use conventional commit"' },
|
|
3693
|
+
}, { cwd });
|
|
3694
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3695
|
+
assert.equal(result.outputJson, null);
|
|
3696
|
+
});
|
|
3697
|
+
});
|
|
3698
|
+
it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
|
|
3699
|
+
await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
|
|
3700
|
+
const result = await dispatchCodexNativeHook({
|
|
3701
|
+
hook_event_name: "PreToolUse",
|
|
3702
|
+
cwd,
|
|
3703
|
+
tool_name: "Bash",
|
|
3704
|
+
tool_use_id: "tool-git-commit-lore-config-inline-enabled",
|
|
3705
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
|
|
3706
|
+
}, { cwd });
|
|
3707
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3708
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
3709
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3710
|
+
});
|
|
3711
|
+
});
|
|
3712
|
+
it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
|
|
3713
|
+
await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
|
|
3714
|
+
const result = await dispatchCodexNativeHook({
|
|
3715
|
+
hook_event_name: "PreToolUse",
|
|
3716
|
+
cwd,
|
|
3717
|
+
tool_name: "Bash",
|
|
3718
|
+
tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
|
|
3719
|
+
tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
|
|
3720
|
+
}, { cwd });
|
|
3721
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3722
|
+
assert.equal(result.outputJson, null);
|
|
3723
|
+
});
|
|
3724
|
+
});
|
|
3179
3725
|
it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
|
|
3180
3726
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
|
|
3181
3727
|
try {
|
|
@@ -3193,7 +3739,30 @@ exit 0
|
|
|
3193
3739
|
await rm(cwd, { recursive: true, force: true });
|
|
3194
3740
|
}
|
|
3195
3741
|
});
|
|
3196
|
-
it("
|
|
3742
|
+
it("allows inline disabled guard to override an enabled process env", async () => {
|
|
3743
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
|
|
3744
|
+
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3745
|
+
try {
|
|
3746
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3747
|
+
const result = await dispatchCodexNativeHook({
|
|
3748
|
+
hook_event_name: "PreToolUse",
|
|
3749
|
+
cwd,
|
|
3750
|
+
tool_name: "Bash",
|
|
3751
|
+
tool_use_id: "tool-git-commit-lore-inline-override-disabled",
|
|
3752
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
|
|
3753
|
+
}, { cwd });
|
|
3754
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3755
|
+
assert.equal(result.outputJson, null);
|
|
3756
|
+
}
|
|
3757
|
+
finally {
|
|
3758
|
+
if (original === undefined)
|
|
3759
|
+
delete process.env.OMX_LORE_COMMIT_GUARD;
|
|
3760
|
+
else
|
|
3761
|
+
process.env.OMX_LORE_COMMIT_GUARD = original;
|
|
3762
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3763
|
+
}
|
|
3764
|
+
});
|
|
3765
|
+
it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
|
|
3197
3766
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
|
|
3198
3767
|
try {
|
|
3199
3768
|
const result = await dispatchCodexNativeHook({
|
|
@@ -3201,21 +3770,33 @@ exit 0
|
|
|
3201
3770
|
cwd,
|
|
3202
3771
|
tool_name: "Bash",
|
|
3203
3772
|
tool_use_id: "tool-git-commit-lore-newline-assignment",
|
|
3204
|
-
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=
|
|
3773
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
|
|
3205
3774
|
}, { cwd });
|
|
3206
3775
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3207
|
-
assert.equal(result.outputJson
|
|
3208
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3776
|
+
assert.equal(result.outputJson, null);
|
|
3209
3777
|
}
|
|
3210
3778
|
finally {
|
|
3211
3779
|
await rm(cwd, { recursive: true, force: true });
|
|
3212
3780
|
}
|
|
3213
3781
|
});
|
|
3214
|
-
it("restores default-
|
|
3782
|
+
it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
|
|
3783
|
+
await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
|
|
3784
|
+
const result = await dispatchCodexNativeHook({
|
|
3785
|
+
hook_event_name: "PreToolUse",
|
|
3786
|
+
cwd,
|
|
3787
|
+
tool_name: "Bash",
|
|
3788
|
+
tool_use_id: "tool-git-commit-lore-config-env-unset",
|
|
3789
|
+
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
3790
|
+
}, { cwd });
|
|
3791
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3792
|
+
assert.equal(result.outputJson, null);
|
|
3793
|
+
});
|
|
3794
|
+
});
|
|
3795
|
+
it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
|
|
3215
3796
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
|
|
3216
3797
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3217
3798
|
try {
|
|
3218
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
3799
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3219
3800
|
const result = await dispatchCodexNativeHook({
|
|
3220
3801
|
hook_event_name: "PreToolUse",
|
|
3221
3802
|
cwd,
|
|
@@ -3224,8 +3805,7 @@ exit 0
|
|
|
3224
3805
|
tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
|
|
3225
3806
|
}, { cwd });
|
|
3226
3807
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3227
|
-
assert.equal(result.outputJson
|
|
3228
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3808
|
+
assert.equal(result.outputJson, null);
|
|
3229
3809
|
}
|
|
3230
3810
|
finally {
|
|
3231
3811
|
if (original === undefined)
|
|
@@ -3235,11 +3815,11 @@ exit 0
|
|
|
3235
3815
|
await rm(cwd, { recursive: true, force: true });
|
|
3236
3816
|
}
|
|
3237
3817
|
});
|
|
3238
|
-
it("restores default-
|
|
3818
|
+
it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
|
|
3239
3819
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
|
|
3240
3820
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3241
3821
|
try {
|
|
3242
|
-
process.env.OMX_LORE_COMMIT_GUARD = "
|
|
3822
|
+
process.env.OMX_LORE_COMMIT_GUARD = "1";
|
|
3243
3823
|
const result = await dispatchCodexNativeHook({
|
|
3244
3824
|
hook_event_name: "PreToolUse",
|
|
3245
3825
|
cwd,
|
|
@@ -3248,8 +3828,7 @@ exit 0
|
|
|
3248
3828
|
tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
|
|
3249
3829
|
}, { cwd });
|
|
3250
3830
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3251
|
-
assert.equal(result.outputJson
|
|
3252
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3831
|
+
assert.equal(result.outputJson, null);
|
|
3253
3832
|
}
|
|
3254
3833
|
finally {
|
|
3255
3834
|
if (original === undefined)
|
|
@@ -3259,7 +3838,7 @@ exit 0
|
|
|
3259
3838
|
await rm(cwd, { recursive: true, force: true });
|
|
3260
3839
|
}
|
|
3261
3840
|
});
|
|
3262
|
-
it("keeps Lore commit enforcement
|
|
3841
|
+
it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
|
|
3263
3842
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
|
|
3264
3843
|
try {
|
|
3265
3844
|
const result = await dispatchCodexNativeHook({
|
|
@@ -3270,8 +3849,7 @@ exit 0
|
|
|
3270
3849
|
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
|
|
3271
3850
|
}, { cwd });
|
|
3272
3851
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3273
|
-
assert.equal(result.outputJson
|
|
3274
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3852
|
+
assert.equal(result.outputJson, null);
|
|
3275
3853
|
}
|
|
3276
3854
|
finally {
|
|
3277
3855
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3300,7 +3878,7 @@ exit 0
|
|
|
3300
3878
|
await rm(cwd, { recursive: true, force: true });
|
|
3301
3879
|
}
|
|
3302
3880
|
});
|
|
3303
|
-
it("keeps Lore commit enforcement
|
|
3881
|
+
it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
|
|
3304
3882
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
|
|
3305
3883
|
const original = process.env.OMX_LORE_COMMIT_GUARD;
|
|
3306
3884
|
try {
|
|
@@ -3313,8 +3891,7 @@ exit 0
|
|
|
3313
3891
|
tool_input: { command: 'git commit -m "fix tests"' },
|
|
3314
3892
|
}, { cwd });
|
|
3315
3893
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3316
|
-
assert.equal(result.outputJson
|
|
3317
|
-
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
3894
|
+
assert.equal(result.outputJson, null);
|
|
3318
3895
|
}
|
|
3319
3896
|
finally {
|
|
3320
3897
|
if (original === undefined)
|
|
@@ -3407,7 +3984,7 @@ exit 0
|
|
|
3407
3984
|
cwd,
|
|
3408
3985
|
tool_name: "Bash",
|
|
3409
3986
|
tool_use_id: "tool-git-commit-env-invalid",
|
|
3410
|
-
tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
|
|
3987
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
|
|
3411
3988
|
}, { cwd });
|
|
3412
3989
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3413
3990
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3437,7 +4014,7 @@ exit 0
|
|
|
3437
4014
|
cwd,
|
|
3438
4015
|
tool_name: "Bash",
|
|
3439
4016
|
tool_use_id: "tool-git-commit-option-invalid",
|
|
3440
|
-
tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
|
|
4017
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
|
|
3441
4018
|
}, { cwd });
|
|
3442
4019
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3443
4020
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3467,7 +4044,7 @@ exit 0
|
|
|
3467
4044
|
cwd,
|
|
3468
4045
|
tool_name: "Bash",
|
|
3469
4046
|
tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
|
|
3470
|
-
tool_input: { command: 'env git.exe commit -m "fix tests"' },
|
|
4047
|
+
tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3471
4048
|
}, { cwd });
|
|
3472
4049
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3473
4050
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3497,7 +4074,7 @@ exit 0
|
|
|
3497
4074
|
cwd,
|
|
3498
4075
|
tool_name: "Bash",
|
|
3499
4076
|
tool_use_id: "tool-git-exe-commit-invalid",
|
|
3500
|
-
tool_input: { command: 'git.exe commit -m "fix tests"' },
|
|
4077
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3501
4078
|
}, { cwd });
|
|
3502
4079
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3503
4080
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3527,7 +4104,7 @@ exit 0
|
|
|
3527
4104
|
cwd,
|
|
3528
4105
|
tool_name: "Bash",
|
|
3529
4106
|
tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
|
|
3530
|
-
tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
|
|
4107
|
+
tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3531
4108
|
}, { cwd });
|
|
3532
4109
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3533
4110
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3557,7 +4134,7 @@ exit 0
|
|
|
3557
4134
|
cwd,
|
|
3558
4135
|
tool_name: "Bash",
|
|
3559
4136
|
tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
|
|
3560
|
-
tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
|
|
4137
|
+
tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
|
|
3561
4138
|
}, { cwd });
|
|
3562
4139
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3563
4140
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3587,7 +4164,7 @@ exit 0
|
|
|
3587
4164
|
cwd,
|
|
3588
4165
|
tool_name: "Bash",
|
|
3589
4166
|
tool_use_id: "tool-git-exe-commit-windows-path-invalid",
|
|
3590
|
-
tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
4167
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
|
|
3591
4168
|
}, { cwd });
|
|
3592
4169
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3593
4170
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3617,7 +4194,7 @@ exit 0
|
|
|
3617
4194
|
cwd,
|
|
3618
4195
|
tool_name: "Bash",
|
|
3619
4196
|
tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
|
|
3620
|
-
tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
4197
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
|
|
3621
4198
|
}, { cwd });
|
|
3622
4199
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3623
4200
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3647,7 +4224,7 @@ exit 0
|
|
|
3647
4224
|
cwd,
|
|
3648
4225
|
tool_name: "Bash",
|
|
3649
4226
|
tool_use_id: "tool-git-commit-path-invalid",
|
|
3650
|
-
tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
|
|
4227
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
|
|
3651
4228
|
}, { cwd });
|
|
3652
4229
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3653
4230
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3677,7 +4254,7 @@ exit 0
|
|
|
3677
4254
|
cwd,
|
|
3678
4255
|
tool_name: "Bash",
|
|
3679
4256
|
tool_use_id: "tool-git-commit-file",
|
|
3680
|
-
tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
|
|
4257
|
+
tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
|
|
3681
4258
|
}, { cwd });
|
|
3682
4259
|
assert.equal(result.omxEventName, "pre-tool-use");
|
|
3683
4260
|
assert.deepEqual(result.outputJson, {
|
|
@@ -3706,7 +4283,7 @@ exit 0
|
|
|
3706
4283
|
tool_use_id: "tool-git-commit-missing-omx-coauthor",
|
|
3707
4284
|
tool_input: {
|
|
3708
4285
|
command: [
|
|
3709
|
-
'git commit',
|
|
4286
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3710
4287
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
3711
4288
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
3712
4289
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -3741,7 +4318,7 @@ exit 0
|
|
|
3741
4318
|
tool_use_id: "tool-git-commit-valid",
|
|
3742
4319
|
tool_input: {
|
|
3743
4320
|
command: [
|
|
3744
|
-
'git commit',
|
|
4321
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3745
4322
|
'-m "Prevent invalid history from bypassing Lore enforcement"',
|
|
3746
4323
|
'-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
|
|
3747
4324
|
'-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
|
|
@@ -3767,7 +4344,7 @@ exit 0
|
|
|
3767
4344
|
tool_use_id: "tool-git-commit-compact-coauthor",
|
|
3768
4345
|
tool_input: {
|
|
3769
4346
|
command: [
|
|
3770
|
-
'git commit',
|
|
4347
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3771
4348
|
'-m "Launch lvisai.xyz intro site"',
|
|
3772
4349
|
'-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
3773
4350
|
].join(" "),
|
|
@@ -3790,7 +4367,7 @@ exit 0
|
|
|
3790
4367
|
tool_use_id: "tool-git-commit-compact-trailers",
|
|
3791
4368
|
tool_input: {
|
|
3792
4369
|
command: [
|
|
3793
|
-
'git commit',
|
|
4370
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3794
4371
|
'-m "Launch lvisai.xyz intro site"',
|
|
3795
4372
|
'-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>"',
|
|
3796
4373
|
].join(" "),
|
|
@@ -3813,7 +4390,7 @@ exit 0
|
|
|
3813
4390
|
tool_use_id: "tool-git-commit-compact-no-separator",
|
|
3814
4391
|
tool_input: {
|
|
3815
4392
|
command: [
|
|
3816
|
-
'git commit',
|
|
4393
|
+
'OMX_LORE_COMMIT_GUARD=1 git commit',
|
|
3817
4394
|
'--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
|
|
3818
4395
|
].join(" "),
|
|
3819
4396
|
},
|
|
@@ -4622,6 +5199,80 @@ exit 0
|
|
|
4622
5199
|
await rm(cwd, { recursive: true, force: true });
|
|
4623
5200
|
}
|
|
4624
5201
|
});
|
|
5202
|
+
it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
|
|
5203
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
|
|
5204
|
+
try {
|
|
5205
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5206
|
+
const sessionId = "sess-stop-autopilot-terminal-run-state";
|
|
5207
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
5208
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
5209
|
+
active: true,
|
|
5210
|
+
mode: "autopilot",
|
|
5211
|
+
current_phase: "ralplan",
|
|
5212
|
+
});
|
|
5213
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
5214
|
+
version: 1,
|
|
5215
|
+
active: false,
|
|
5216
|
+
mode: "autopilot",
|
|
5217
|
+
outcome: "finish",
|
|
5218
|
+
lifecycle_outcome: "finished",
|
|
5219
|
+
current_phase: "complete",
|
|
5220
|
+
completed_at: "2026-05-20T11:00:00.000Z",
|
|
5221
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
5222
|
+
});
|
|
5223
|
+
const result = await dispatchCodexNativeHook({
|
|
5224
|
+
hook_event_name: "Stop",
|
|
5225
|
+
cwd,
|
|
5226
|
+
session_id: sessionId,
|
|
5227
|
+
thread_id: "thread-stop-autopilot-terminal-run-state",
|
|
5228
|
+
turn_id: "turn-stop-autopilot-terminal-run-state-1",
|
|
5229
|
+
last_assistant_message: "Done. Verification passed.",
|
|
5230
|
+
}, { cwd });
|
|
5231
|
+
assert.equal(result.omxEventName, "stop");
|
|
5232
|
+
assert.equal(result.outputJson, null);
|
|
5233
|
+
}
|
|
5234
|
+
finally {
|
|
5235
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5236
|
+
}
|
|
5237
|
+
});
|
|
5238
|
+
it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
|
|
5239
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
|
|
5240
|
+
try {
|
|
5241
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5242
|
+
const sessionId = "sess-stop-autopilot-active-ralplan";
|
|
5243
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
5244
|
+
await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
|
|
5245
|
+
active: true,
|
|
5246
|
+
mode: "autopilot",
|
|
5247
|
+
current_phase: "ralplan",
|
|
5248
|
+
});
|
|
5249
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
5250
|
+
version: 1,
|
|
5251
|
+
active: true,
|
|
5252
|
+
mode: "autopilot",
|
|
5253
|
+
outcome: "continue",
|
|
5254
|
+
current_phase: "ralplan",
|
|
5255
|
+
updated_at: "2026-05-20T11:00:00.000Z",
|
|
5256
|
+
});
|
|
5257
|
+
const result = await dispatchCodexNativeHook({
|
|
5258
|
+
hook_event_name: "Stop",
|
|
5259
|
+
cwd,
|
|
5260
|
+
session_id: sessionId,
|
|
5261
|
+
thread_id: "thread-stop-autopilot-active-ralplan",
|
|
5262
|
+
turn_id: "turn-stop-autopilot-active-ralplan-1",
|
|
5263
|
+
}, { cwd });
|
|
5264
|
+
assert.equal(result.omxEventName, "stop");
|
|
5265
|
+
assert.deepEqual(result.outputJson, {
|
|
5266
|
+
decision: "block",
|
|
5267
|
+
reason: "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
|
|
5268
|
+
stopReason: "autopilot_ralplan",
|
|
5269
|
+
systemMessage: "OMX autopilot is still active (phase: ralplan).",
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
finally {
|
|
5273
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5274
|
+
}
|
|
5275
|
+
});
|
|
4625
5276
|
it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
|
|
4626
5277
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
|
|
4627
5278
|
try {
|
|
@@ -6961,16 +7612,25 @@ exit 0
|
|
|
6961
7612
|
assert.equal(result.omxEventName, "stop");
|
|
6962
7613
|
const reason = String(result.outputJson?.reason);
|
|
6963
7614
|
assert.match(reason, /Ralph completion audit is missing required evidence/);
|
|
6964
|
-
assert.match(reason, /
|
|
7615
|
+
assert.match(reason, /set "completion_audit" on the Ralph state object/);
|
|
7616
|
+
assert.doesNotMatch(reason, /state\.completion_audit/);
|
|
6965
7617
|
assert.match(reason, /repo-relative JSON file/);
|
|
6966
7618
|
assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
|
|
6967
7619
|
assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
6968
7620
|
const reopened = JSON.parse(await readFile(statePath, "utf-8"));
|
|
6969
|
-
assert.equal(reopened.active,
|
|
6970
|
-
assert.equal(reopened.current_phase, "
|
|
7621
|
+
assert.equal(reopened.active, false);
|
|
7622
|
+
assert.equal(reopened.current_phase, "complete");
|
|
6971
7623
|
assert.equal(reopened.completion_audit_gate, "blocked");
|
|
6972
7624
|
assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
|
|
6973
|
-
assert.equal(
|
|
7625
|
+
assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
|
|
7626
|
+
const repeat = await dispatchCodexNativeHook({
|
|
7627
|
+
hook_event_name: "Stop",
|
|
7628
|
+
cwd,
|
|
7629
|
+
session_id: sessionId,
|
|
7630
|
+
last_assistant_message: "Done. Ralph complete.",
|
|
7631
|
+
}, { cwd });
|
|
7632
|
+
assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
|
|
7633
|
+
assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
|
|
6974
7634
|
}
|
|
6975
7635
|
finally {
|
|
6976
7636
|
await rm(cwd, { recursive: true, force: true });
|