oh-my-codex 0.19.1 → 0.20.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.
Files changed (229) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -1
  4. package/crates/omx-api/src/lib.rs +3 -3
  5. package/crates/omx-api/tests/cli.rs +4 -4
  6. package/crates/omx-explore/src/main.rs +4 -4
  7. package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
  8. package/dist/agents/__tests__/definitions.test.js +3 -3
  9. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  10. package/dist/agents/__tests__/native-config.test.js +71 -42
  11. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  12. package/dist/agents/definitions.d.ts +1 -1
  13. package/dist/agents/definitions.d.ts.map +1 -1
  14. package/dist/agents/definitions.js +3 -3
  15. package/dist/agents/definitions.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -2
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js +7 -5
  19. package/dist/agents/native-config.js.map +1 -1
  20. package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
  21. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
  22. package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -1
  25. package/dist/autopilot/planner-routing.js +4 -2
  26. package/dist/autopilot/planner-routing.js.map +1 -1
  27. package/dist/autopilot/ralplan-gate.d.ts +1 -0
  28. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  29. package/dist/autopilot/ralplan-gate.js +38 -2
  30. package/dist/autopilot/ralplan-gate.js.map +1 -1
  31. package/dist/autoresearch/runtime.d.ts +2 -0
  32. package/dist/autoresearch/runtime.d.ts.map +1 -1
  33. package/dist/autoresearch/runtime.js +8 -0
  34. package/dist/autoresearch/runtime.js.map +1 -1
  35. package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
  36. package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
  37. package/dist/capabilities/__tests__/lockfile.test.js +115 -0
  38. package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
  39. package/dist/capabilities/lockfile.d.ts +102 -0
  40. package/dist/capabilities/lockfile.d.ts.map +1 -0
  41. package/dist/capabilities/lockfile.js +334 -0
  42. package/dist/capabilities/lockfile.js.map +1 -0
  43. package/dist/cli/__tests__/agents.test.js +5 -5
  44. package/dist/cli/__tests__/agents.test.js.map +1 -1
  45. package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
  46. package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
  47. package/dist/cli/__tests__/capabilities.test.js +135 -0
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -0
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +264 -6
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
  52. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js +10 -1
  54. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  55. package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
  56. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  57. package/dist/cli/__tests__/doctor-warning-copy.test.js +87 -1
  58. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  59. package/dist/cli/__tests__/index.test.js +53 -18
  60. package/dist/cli/__tests__/index.test.js.map +1 -1
  61. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
  62. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  63. package/dist/cli/__tests__/resume.test.js +95 -6
  64. package/dist/cli/__tests__/resume.test.js.map +1 -1
  65. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +54 -2
  66. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  67. package/dist/cli/__tests__/setup-install-mode.test.js +49 -21
  68. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  69. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
  70. package/dist/cli/__tests__/setup-refresh.test.js +166 -12
  71. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  72. package/dist/cli/__tests__/setup-scope.test.js +27 -3
  73. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  74. package/dist/cli/__tests__/uninstall.test.js +301 -7
  75. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  76. package/dist/cli/agents.js +1 -1
  77. package/dist/cli/agents.js.map +1 -1
  78. package/dist/cli/capabilities.d.ts +3 -0
  79. package/dist/cli/capabilities.d.ts.map +1 -0
  80. package/dist/cli/capabilities.js +118 -0
  81. package/dist/cli/capabilities.js.map +1 -0
  82. package/dist/cli/doctor.d.ts +3 -1
  83. package/dist/cli/doctor.d.ts.map +1 -1
  84. package/dist/cli/doctor.js +50 -34
  85. package/dist/cli/doctor.js.map +1 -1
  86. package/dist/cli/index.d.ts +7 -4
  87. package/dist/cli/index.d.ts.map +1 -1
  88. package/dist/cli/index.js +77 -19
  89. package/dist/cli/index.js.map +1 -1
  90. package/dist/cli/plugin-marketplace.d.ts +1 -0
  91. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  92. package/dist/cli/plugin-marketplace.js +31 -0
  93. package/dist/cli/plugin-marketplace.js.map +1 -1
  94. package/dist/cli/setup.d.ts.map +1 -1
  95. package/dist/cli/setup.js +9 -6
  96. package/dist/cli/setup.js.map +1 -1
  97. package/dist/cli/uninstall.d.ts.map +1 -1
  98. package/dist/cli/uninstall.js +3 -4
  99. package/dist/cli/uninstall.js.map +1 -1
  100. package/dist/config/__tests__/generator-idempotent.test.js +210 -114
  101. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  102. package/dist/config/__tests__/generator-notify.test.js +26 -52
  103. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  104. package/dist/config/__tests__/models.test.js +51 -26
  105. package/dist/config/__tests__/models.test.js.map +1 -1
  106. package/dist/config/generator.d.ts +20 -11
  107. package/dist/config/generator.d.ts.map +1 -1
  108. package/dist/config/generator.js +169 -152
  109. package/dist/config/generator.js.map +1 -1
  110. package/dist/config/models.d.ts +16 -6
  111. package/dist/config/models.d.ts.map +1 -1
  112. package/dist/config/models.js +19 -8
  113. package/dist/config/models.js.map +1 -1
  114. package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
  115. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  116. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  117. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
  118. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  119. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  120. package/dist/leader/__tests__/contract.test.js +150 -1
  121. package/dist/leader/__tests__/contract.test.js.map +1 -1
  122. package/dist/leader/contract.d.ts +26 -0
  123. package/dist/leader/contract.d.ts.map +1 -1
  124. package/dist/leader/contract.js +188 -0
  125. package/dist/leader/contract.js.map +1 -1
  126. package/dist/modes/base.d.ts +1 -1
  127. package/dist/modes/base.d.ts.map +1 -1
  128. package/dist/pipeline/__tests__/stages.test.js +27 -1
  129. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  130. package/dist/pipeline/stages/ralplan.d.ts +2 -1
  131. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  132. package/dist/pipeline/stages/ralplan.js +1 -0
  133. package/dist/pipeline/stages/ralplan.js.map +1 -1
  134. package/dist/pipeline/stages/ultragoal.d.ts +5 -1
  135. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  136. package/dist/pipeline/stages/ultragoal.js +14 -4
  137. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  138. package/dist/ralplan/__tests__/runtime.test.js +132 -0
  139. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  140. package/dist/ralplan/runtime.d.ts +17 -0
  141. package/dist/ralplan/runtime.d.ts.map +1 -1
  142. package/dist/ralplan/runtime.js +97 -1
  143. package/dist/ralplan/runtime.js.map +1 -1
  144. package/dist/scripts/__tests__/codex-native-hook.test.js +1528 -32
  145. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  146. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  147. package/dist/scripts/codex-native-hook.js +377 -31
  148. package/dist/scripts/codex-native-hook.js.map +1 -1
  149. package/dist/state/__tests__/operations.test.js +450 -0
  150. package/dist/state/__tests__/operations.test.js.map +1 -1
  151. package/dist/state/__tests__/workflow-transition.test.js +14 -0
  152. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  153. package/dist/state/operations.d.ts.map +1 -1
  154. package/dist/state/operations.js +72 -11
  155. package/dist/state/operations.js.map +1 -1
  156. package/dist/state/terminal-normalization.d.ts +8 -0
  157. package/dist/state/terminal-normalization.d.ts.map +1 -0
  158. package/dist/state/terminal-normalization.js +127 -0
  159. package/dist/state/terminal-normalization.js.map +1 -0
  160. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  161. package/dist/state/workflow-transition-reconcile.js +3 -1
  162. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  163. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  164. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  165. package/dist/team/__tests__/model-contract.test.js +25 -25
  166. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  167. package/dist/team/__tests__/runtime.test.js +28 -28
  168. package/dist/team/__tests__/runtime.test.js.map +1 -1
  169. package/dist/team/__tests__/scaling.test.js +7 -7
  170. package/dist/team/__tests__/scaling.test.js.map +1 -1
  171. package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
  172. package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
  173. package/dist/team/__tests__/tmux-session.test.js +12 -12
  174. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  175. package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
  176. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  177. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  178. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  179. package/dist/team/model-contract.d.ts +1 -1
  180. package/dist/team/model-contract.d.ts.map +1 -1
  181. package/dist/team/runtime.d.ts.map +1 -1
  182. package/dist/team/runtime.js +11 -0
  183. package/dist/team/runtime.js.map +1 -1
  184. package/dist/team/worker-bootstrap.d.ts +2 -0
  185. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  186. package/dist/team/worker-bootstrap.js +6 -1
  187. package/dist/team/worker-bootstrap.js.map +1 -1
  188. package/dist/ultragoal/__tests__/artifacts.test.js +68 -1
  189. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  190. package/dist/ultragoal/artifacts.d.ts +5 -1
  191. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  192. package/dist/ultragoal/artifacts.js +41 -9
  193. package/dist/ultragoal/artifacts.js.map +1 -1
  194. package/dist/utils/__tests__/agents-md.test.js +16 -0
  195. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  196. package/dist/utils/__tests__/agents-model-table.test.js +7 -7
  197. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  198. package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
  199. package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
  200. package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
  201. package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
  202. package/dist/utils/agents-md.d.ts.map +1 -1
  203. package/dist/utils/agents-md.js +4 -3
  204. package/dist/utils/agents-md.js.map +1 -1
  205. package/dist/utils/worktree-tool-context.d.ts +25 -0
  206. package/dist/utils/worktree-tool-context.d.ts.map +1 -0
  207. package/dist/utils/worktree-tool-context.js +116 -0
  208. package/dist/utils/worktree-tool-context.js.map +1 -0
  209. package/package.json +1 -1
  210. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  211. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
  212. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
  213. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  214. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
  215. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
  216. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  217. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  218. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
  219. package/prompts/prometheus-strict-metis.md +1 -1
  220. package/skills/ai-slop-cleaner/SKILL.md +1 -1
  221. package/skills/code-review/SKILL.md +1 -1
  222. package/skills/ecomode/references/agent-tiers.md +1 -1
  223. package/skills/omx-setup/SKILL.md +5 -5
  224. package/skills/prometheus-strict/SKILL.md +3 -3
  225. package/skills/ralplan/SKILL.md +1 -1
  226. package/skills/team/SKILL.md +1 -1
  227. package/skills/ultrawork/references/agent-tiers.md +2 -2
  228. package/src/scripts/__tests__/codex-native-hook.test.ts +1818 -113
  229. package/src/scripts/codex-native-hook.ts +414 -27
@@ -0,0 +1,135 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { describe, it } from "node:test";
8
+ const testDir = join(fileURLToPath(import.meta.url), "..");
9
+ const repoRoot = join(testDir, "..", "..", "..");
10
+ const omxBin = join(repoRoot, "dist", "cli", "omx.js");
11
+ async function withTempDir(run) {
12
+ const dir = await mkdtemp(join(tmpdir(), "omx-capabilities-cli-"));
13
+ try {
14
+ return await run(dir);
15
+ }
16
+ finally {
17
+ await rm(dir, { recursive: true, force: true });
18
+ }
19
+ }
20
+ function runOmx(cwd, args) {
21
+ return spawnSync(process.execPath, [omxBin, ...args], { cwd, encoding: "utf8" });
22
+ }
23
+ async function writeObservations(dir, observations) {
24
+ const path = join(dir, "observations.json");
25
+ await writeFile(path, JSON.stringify({ version: 1, kind: "omx_capability_observations", observations }, null, 2), "utf8");
26
+ return path;
27
+ }
28
+ const SUCCESS_OBSERVATIONS = [
29
+ {
30
+ fixture_id: "project-memory-write-required-arg",
31
+ tool_calls: [{ name: "project_memory_write", arguments: { memory: { note: "capability preflight fixture" } } }],
32
+ },
33
+ {
34
+ fixture_id: "project-memory-read-known-tool",
35
+ tool_calls: [{ name: "project_memory_read", arguments: {} }],
36
+ },
37
+ {
38
+ fixture_id: "tool-restraint-no-call",
39
+ tool_calls: [],
40
+ },
41
+ ];
42
+ function hasFailure(stdout, code) {
43
+ const parsed = JSON.parse(stdout);
44
+ return parsed.failures?.some((failure) => failure.code === code) ?? false;
45
+ }
46
+ describe("omx capabilities cli", () => {
47
+ it("routes local help", async () => {
48
+ await withTempDir(async (dir) => {
49
+ const result = runOmx(dir, ["capabilities", "--help"]);
50
+ assert.equal(result.status, 0);
51
+ assert.match(result.stdout, /omx capabilities lock/);
52
+ assert.doesNotMatch(result.stdout, /Launch Codex CLI/);
53
+ });
54
+ });
55
+ it("locks and checks with JSON output", async () => {
56
+ await withTempDir(async (dir) => {
57
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
58
+ assert.equal(lock.status, 0, lock.stderr);
59
+ const lockJson = JSON.parse(lock.stdout);
60
+ assert.equal(lockJson.ok, true);
61
+ assert.match(lockJson.lockfile, /omx-capabilities\.lock\.json$/);
62
+ const check = runOmx(dir, ["capabilities", "check", "--json"]);
63
+ assert.equal(check.status, 0, check.stderr);
64
+ const checkJson = JSON.parse(check.stdout);
65
+ assert.equal(checkJson.ok, true);
66
+ assert.equal(checkJson.observations_checked, false);
67
+ });
68
+ });
69
+ it("checks successful fixture observations", async () => {
70
+ await withTempDir(async (dir) => {
71
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
72
+ assert.equal(lock.status, 0, lock.stderr);
73
+ const observations = await writeObservations(dir, SUCCESS_OBSERVATIONS);
74
+ const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--require-observations", "--json"]);
75
+ assert.equal(check.status, 0, check.stderr);
76
+ const checkJson = JSON.parse(check.stdout);
77
+ assert.equal(checkJson.ok, true);
78
+ assert.equal(checkJson.observations_checked, true);
79
+ });
80
+ });
81
+ it("exits non-zero for required missing observations", async () => {
82
+ await withTempDir(async (dir) => {
83
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
84
+ assert.equal(lock.status, 0, lock.stderr);
85
+ const check = runOmx(dir, ["capabilities", "check", "--require-observations", "--json"]);
86
+ assert.notEqual(check.status, 0);
87
+ const checkJson = JSON.parse(check.stdout);
88
+ assert.equal(checkJson.ok, false);
89
+ assert.ok(checkJson.failures.some((failure) => failure.code === "observations_missing"));
90
+ });
91
+ });
92
+ it("reports missing required tool arguments", async () => {
93
+ await withTempDir(async (dir) => {
94
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
95
+ assert.equal(lock.status, 0, lock.stderr);
96
+ const observations = await writeObservations(dir, [
97
+ { fixture_id: "project-memory-write-required-arg", tool_calls: [{ name: "project_memory_write", arguments: {} }] },
98
+ SUCCESS_OBSERVATIONS[1],
99
+ SUCCESS_OBSERVATIONS[2],
100
+ ]);
101
+ const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
102
+ assert.notEqual(check.status, 0);
103
+ assert.equal(hasFailure(check.stdout, "missing_required_arg"), true);
104
+ });
105
+ });
106
+ it("reports hallucinated tools", async () => {
107
+ await withTempDir(async (dir) => {
108
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
109
+ assert.equal(lock.status, 0, lock.stderr);
110
+ const observations = await writeObservations(dir, [
111
+ SUCCESS_OBSERVATIONS[0],
112
+ { fixture_id: "project-memory-read-known-tool", tool_calls: [{ name: "imaginary_tool", arguments: {} }] },
113
+ SUCCESS_OBSERVATIONS[2],
114
+ ]);
115
+ const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
116
+ assert.notEqual(check.status, 0);
117
+ assert.equal(hasFailure(check.stdout, "hallucinated_tool"), true);
118
+ });
119
+ });
120
+ it("reports tool-restraint violations", async () => {
121
+ await withTempDir(async (dir) => {
122
+ const lock = runOmx(dir, ["capabilities", "lock", "--json"]);
123
+ assert.equal(lock.status, 0, lock.stderr);
124
+ const observations = await writeObservations(dir, [
125
+ SUCCESS_OBSERVATIONS[0],
126
+ SUCCESS_OBSERVATIONS[1],
127
+ { fixture_id: "tool-restraint-no-call", tool_calls: [{ name: "project_memory_read", arguments: {} }] },
128
+ ]);
129
+ const check = runOmx(dir, ["capabilities", "check", "--observations", observations, "--json"]);
130
+ assert.notEqual(check.status, 0);
131
+ assert.equal(hasFailure(check.stdout, "unexpected_tool_call"), true);
132
+ });
133
+ });
134
+ });
135
+ //# sourceMappingURL=capabilities.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.test.js","sourceRoot":"","sources":["../../../src/cli/__tests__/capabilities.test.ts"],"names":[],"mappings":"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"}
@@ -148,6 +148,8 @@ async function assertPluginHookLaunchesPostCompactFromCache() {
148
148
  OMX_ROOT: join(cacheRoot, '.omx-root'),
149
149
  OMX_SESSION_ID: 'omx-plugin-hook-postcompact-smoke',
150
150
  OMX_SOURCE_CWD: cacheRoot,
151
+ OMX_ENTRY_PATH: omxBin,
152
+ OMX_CODEX_LAUNCH_ID: 'omx-plugin-hook-postcompact-smoke-launch',
151
153
  OMX_STARTUP_CWD: cacheRoot,
152
154
  },
153
155
  });
@@ -197,6 +199,8 @@ async function assertPluginHookDelegatesPostCompactToPinnedCommand() {
197
199
  OMX_ROOT: join(cacheRoot, '.omx-root'),
198
200
  OMX_SESSION_ID: 'omx-plugin-hook-postcompact-delegate',
199
201
  OMX_SOURCE_CWD: cacheRoot,
202
+ OMX_ENTRY_PATH: omxBin,
203
+ OMX_CODEX_LAUNCH_ID: 'omx-plugin-hook-postcompact-delegate-launch',
200
204
  OMX_STARTUP_CWD: cacheRoot,
201
205
  },
202
206
  });
@@ -254,10 +258,25 @@ async function withPluginCacheCopy(run) {
254
258
  }
255
259
  function pluginHookEnv(overrides = {}) {
256
260
  const env = { ...process.env };
257
- for (const key of ['OMX_TEAM_STATE_ROOT', 'OMX_ROOT', 'OMX_STATE_ROOT', 'OMX_SESSION_ID', 'CODEX_SESSION_ID']) {
261
+ for (const key of [
262
+ 'OMX_TEAM_STATE_ROOT',
263
+ 'OMX_ROOT',
264
+ 'OMX_STATE_ROOT',
265
+ 'OMX_SESSION_ID',
266
+ 'CODEX_SESSION_ID',
267
+ 'OMX_ENTRY_PATH',
268
+ 'OMX_CODEX_LAUNCH_ID',
269
+ 'OMX_STARTUP_CWD',
270
+ ]) {
258
271
  delete env[key];
259
272
  }
260
- return { ...env, ...overrides };
273
+ return {
274
+ ...env,
275
+ OMX_ENTRY_PATH: omxBin,
276
+ OMX_CODEX_LAUNCH_ID: 'omx-plugin-layout-launch',
277
+ OMX_STARTUP_CWD: root,
278
+ ...overrides,
279
+ };
261
280
  }
262
281
  function runPluginNativeHook(cachePluginRoot, input, env = {}) {
263
282
  return spawnSync(process.execPath, [join(cachePluginRoot, 'hooks', 'codex-native-hook.mjs')], {
@@ -336,6 +355,97 @@ describe('official Codex plugin layout', () => {
336
355
  assert.match(launcher, /return \{ \.\.\.options, shell: true, windowsHide: true \};/);
337
356
  assert.doesNotMatch(launcher, /shell:\s*process\.platform === 'win32'/);
338
357
  });
358
+ it('no-ops plugin hooks when Codex was not launched through omx', async () => {
359
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
360
+ const calledPath = join(cacheRoot, 'called.txt');
361
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'record-called.cmd' : 'record-called.sh');
362
+ if (process.platform === 'win32') {
363
+ await writeFile(commandPath, `@echo off\r\necho called > "${calledPath}"\r\necho {}\r\n`, 'utf-8');
364
+ }
365
+ else {
366
+ await writeFile(commandPath, `#!/bin/sh\necho called > "${calledPath}"\nprintf '{}\\n'\n`, 'utf-8');
367
+ await chmod(commandPath, 0o755);
368
+ }
369
+ const userPrompt = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'UserPromptSubmit', prompt: '$ralplan smoke' }), {
370
+ OMX_ENTRY_PATH: '',
371
+ OMX_CODEX_LAUNCH_ID: '',
372
+ OMX_NATIVE_HOOK_COMMAND: commandPath,
373
+ });
374
+ assert.equal(userPrompt.status, 0, userPrompt.stderr || userPrompt.stdout);
375
+ assert.equal(userPrompt.stdout, '');
376
+ await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
377
+ const stop = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'plain-codex-stop' }), {
378
+ OMX_ENTRY_PATH: '',
379
+ OMX_CODEX_LAUNCH_ID: '',
380
+ OMX_NATIVE_HOOK_COMMAND: commandPath,
381
+ });
382
+ assert.equal(stop.status, 0, stop.stderr || stop.stdout);
383
+ assert.deepEqual(parseSingleJsonStdout(stop.stdout), {});
384
+ await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
385
+ const oversizedUserPrompt = runPluginNativeHook(cachePluginRoot, JSON.stringify({
386
+ hook_event_name: 'UserPromptSubmit',
387
+ prompt: 'x'.repeat(2 * 1024 * 1024),
388
+ }), {
389
+ OMX_ENTRY_PATH: '',
390
+ OMX_CODEX_LAUNCH_ID: '',
391
+ OMX_NATIVE_HOOK_COMMAND: commandPath,
392
+ });
393
+ assert.equal(oversizedUserPrompt.error, undefined, oversizedUserPrompt.error?.message ?? '');
394
+ assert.equal(oversizedUserPrompt.status, 0, oversizedUserPrompt.stderr || oversizedUserPrompt.stdout);
395
+ assert.equal(oversizedUserPrompt.stdout, '');
396
+ await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
397
+ const oversizedStop = runPluginNativeHook(cachePluginRoot, JSON.stringify({
398
+ hook_event_name: 'Stop',
399
+ session_id: 'plain-codex-oversized-stop',
400
+ padding: 'x'.repeat(2 * 1024 * 1024),
401
+ }), {
402
+ OMX_ENTRY_PATH: '',
403
+ OMX_CODEX_LAUNCH_ID: '',
404
+ OMX_NATIVE_HOOK_COMMAND: commandPath,
405
+ });
406
+ assert.equal(oversizedStop.error, undefined, oversizedStop.error?.message ?? '');
407
+ assert.equal(oversizedStop.status, 0, oversizedStop.stderr || oversizedStop.stdout);
408
+ assert.deepEqual(parseSingleJsonStdout(oversizedStop.stdout), {});
409
+ await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
410
+ });
411
+ });
412
+ it('no-ops plugin hooks for nested plain Codex sessions that inherit omx launch env', async () => {
413
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
414
+ const calledPath = join(cacheRoot, 'called.txt');
415
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'record-inherited-called.cmd' : 'record-inherited-called.sh');
416
+ if (process.platform === 'win32') {
417
+ await writeFile(commandPath, `@echo off\r\necho %* > "${calledPath}"\r\necho {}\r\n`, 'utf-8');
418
+ }
419
+ else {
420
+ await writeFile(commandPath, `#!/bin/sh\necho "$@" > "${calledPath}"\nprintf '{}\\n'\n`, 'utf-8');
421
+ await chmod(commandPath, 0o755);
422
+ }
423
+ const inheritedEnv = {
424
+ OMX_ROOT: join(cacheRoot, '.omx-root'),
425
+ OMX_ENTRY_PATH: omxBin,
426
+ OMX_CODEX_LAUNCH_ID: 'inherited-launch-token',
427
+ OMX_NATIVE_HOOK_COMMAND: commandPath,
428
+ };
429
+ const owner = runPluginNativeHook(cachePluginRoot, JSON.stringify({
430
+ hook_event_name: 'UserPromptSubmit',
431
+ session_id: 'owner-codex-session',
432
+ session_pid: 111,
433
+ prompt: '$ralplan smoke',
434
+ }), inheritedEnv);
435
+ assert.equal(owner.status, 0, owner.stderr || owner.stdout);
436
+ assert.equal((await readFile(calledPath, 'utf-8')).trim(), 'codex-native-hook');
437
+ await rm(calledPath, { force: true });
438
+ const nestedPlainCodex = runPluginNativeHook(cachePluginRoot, JSON.stringify({
439
+ hook_event_name: 'UserPromptSubmit',
440
+ session_id: 'nested-plain-codex-session',
441
+ session_pid: 222,
442
+ prompt: '$ralplan nested',
443
+ }), inheritedEnv);
444
+ assert.equal(nestedPlainCodex.status, 0, nestedPlainCodex.stderr || nestedPlainCodex.stdout);
445
+ assert.equal(nestedPlainCodex.stdout, '');
446
+ await assert.rejects(readFile(calledPath, 'utf-8'), { code: 'ENOENT' });
447
+ });
448
+ });
339
449
  it('emits Stop JSON when the plugin hook pinned launcher is invalid', async () => {
340
450
  await withPluginCacheCopy(async (cachePluginRoot) => {
341
451
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
@@ -428,6 +538,152 @@ exit 7
428
538
  assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher/);
429
539
  });
430
540
  });
541
+ it('forwards the complete valid Stop child decision unchanged', async () => {
542
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
543
+ const childOutput = {
544
+ decision: 'block',
545
+ reason: 'Autopilot workflow is still active.',
546
+ stopReason: 'autopilot_ultragoal',
547
+ systemMessage: 'Autopilot diagnostic: complete the active workflow before stopping.',
548
+ };
549
+ const childJson = JSON.stringify(childOutput);
550
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'complete-valid-json.cmd' : 'complete-valid-json.sh');
551
+ if (process.platform === 'win32') {
552
+ await writeFile(commandPath, `@echo off\r\necho ${childJson}\r\nexit /b 0\r\n`, 'utf-8');
553
+ }
554
+ else {
555
+ await writeFile(commandPath, `#!/bin/sh
556
+ printf '${childJson}\n'
557
+ `, 'utf-8');
558
+ await chmod(commandPath, 0o755);
559
+ }
560
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-complete-valid-json-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
561
+ assert.equal(result.status, 0, result.stderr || result.stdout);
562
+ assert.equal(result.stderr, '');
563
+ const output = parseSingleJsonStdout(result.stdout);
564
+ assert.deepEqual(Object.keys(output).sort(), ['decision', 'reason', 'stopReason', 'systemMessage']);
565
+ assert.deepEqual(output, childOutput);
566
+ });
567
+ });
568
+ it('preserves valid pretty-printed Stop child JSON', async () => {
569
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
570
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'pretty-valid-json.cmd' : 'pretty-valid-json.sh');
571
+ if (process.platform === 'win32') {
572
+ await writeFile(commandPath, '@echo off\r\necho {\r\necho "decision": "block",\r\necho "stopReason": "pretty_child_json"\r\necho }\r\nexit /b 0\r\n', 'utf-8');
573
+ }
574
+ else {
575
+ await writeFile(commandPath, `#!/bin/sh
576
+ printf '{\n'
577
+ printf ' "decision": "block",\n'
578
+ printf ' "stopReason": "pretty_child_json"\n'
579
+ printf '}\n'
580
+ `, 'utf-8');
581
+ await chmod(commandPath, 0o755);
582
+ }
583
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-pretty-valid-json-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
584
+ assert.equal(result.status, 0, result.stderr || result.stdout);
585
+ const output = parseSingleJsonStdout(result.stdout);
586
+ assert.equal(output.decision, 'block');
587
+ assert.equal(output.stopReason, 'pretty_child_json');
588
+ assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher_invalid_stdout/);
589
+ });
590
+ });
591
+ it('preserves valid Stop child JSON when stdout includes prior launcher noise', async () => {
592
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
593
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'noisy-valid-json.cmd' : 'noisy-valid-json.sh');
594
+ if (process.platform === 'win32') {
595
+ await writeFile(commandPath, '@echo off\r\necho runtime notice before json\r\necho {"decision":"block","stopReason":"autopilot_ultragoal"}\r\nexit /b 0\r\n', 'utf-8');
596
+ }
597
+ else {
598
+ await writeFile(commandPath, `#!/bin/sh
599
+ printf 'runtime notice before json\n'
600
+ printf '{"decision":"block","stopReason":"autopilot_ultragoal"}\n'
601
+ `, 'utf-8');
602
+ await chmod(commandPath, 0o755);
603
+ }
604
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
605
+ hook_event_name: 'Stop',
606
+ session_id: 'omx-1783508412223-c32f1l',
607
+ cwd: cacheRoot,
608
+ }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
609
+ assert.equal(result.status, 0, result.stderr || result.stdout);
610
+ assert.doesNotMatch(result.stdout, /runtime notice before json/);
611
+ const output = parseSingleJsonStdout(result.stdout);
612
+ assert.equal(output.decision, 'block');
613
+ assert.equal(output.stopReason, 'autopilot_ultragoal');
614
+ assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher_invalid_stdout/);
615
+ });
616
+ });
617
+ it('rejects Stop child JSON when later stdout noise follows it', async () => {
618
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
619
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'valid-json-trailing-noise.cmd' : 'valid-json-trailing-noise.sh');
620
+ if (process.platform === 'win32') {
621
+ await writeFile(commandPath, '@echo off\r\necho {"decision":"block","stopReason":"child_valid_json"}\r\necho trailing runtime noise\r\nexit /b 0\r\n', 'utf-8');
622
+ }
623
+ else {
624
+ await writeFile(commandPath, `#!/bin/sh
625
+ printf '{"decision":"block","stopReason":"child_valid_json"}\n'
626
+ printf 'trailing runtime noise\n'
627
+ `, 'utf-8');
628
+ await chmod(commandPath, 0o755);
629
+ }
630
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-trailing-noise-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
631
+ assert.equal(result.status, 0, result.stderr || result.stdout);
632
+ assert.doesNotMatch(result.stdout, /trailing runtime noise/);
633
+ const output = parseSingleJsonStdout(result.stdout);
634
+ assert.equal(output.decision, 'block');
635
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
636
+ });
637
+ });
638
+ it('rejects Stop child JSON when an earlier JSON decision is followed by noise and final JSON', async () => {
639
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
640
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'json-noise-json.cmd' : 'json-noise-json.sh');
641
+ if (process.platform === 'win32') {
642
+ await writeFile(commandPath, '@echo off\r\necho {"decision":"block","stopReason":"first_json"}\r\necho trailing runtime noise\r\necho {"decision":"block","stopReason":"second_json"}\r\nexit /b 0\r\n', 'utf-8');
643
+ }
644
+ else {
645
+ await writeFile(commandPath, `#!/bin/sh
646
+ printf '{"decision":"block","stopReason":"first_json"}\n'
647
+ printf 'trailing runtime noise\n'
648
+ printf '{"decision":"block","stopReason":"second_json"}\n'
649
+ `, 'utf-8');
650
+ await chmod(commandPath, 0o755);
651
+ }
652
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-json-noise-json-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
653
+ assert.equal(result.status, 0, result.stderr || result.stdout);
654
+ assert.doesNotMatch(result.stdout, /first_json/);
655
+ assert.doesNotMatch(result.stdout, /second_json/);
656
+ assert.doesNotMatch(result.stdout, /trailing runtime noise/);
657
+ const output = parseSingleJsonStdout(result.stdout);
658
+ assert.equal(output.decision, 'block');
659
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
660
+ });
661
+ });
662
+ it('rejects pretty Stop child JSON followed by final JSON log noise', async () => {
663
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
664
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'pretty-json-final-log.cmd' : 'pretty-json-final-log.sh');
665
+ if (process.platform === 'win32') {
666
+ await writeFile(commandPath, '@echo off\r\necho {\r\necho "decision": "block",\r\necho "stopReason": "real_block"\r\necho }\r\necho {"level":"info"}\r\nexit /b 0\r\n', 'utf-8');
667
+ }
668
+ else {
669
+ await writeFile(commandPath, `#!/bin/sh
670
+ printf '{\n'
671
+ printf ' "decision": "block",\n'
672
+ printf ' "stopReason": "real_block"\n'
673
+ printf '}\n'
674
+ printf '{"level":"info"}\n'
675
+ `, 'utf-8');
676
+ await chmod(commandPath, 0o755);
677
+ }
678
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-pretty-json-final-log-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
679
+ assert.equal(result.status, 0, result.stderr || result.stdout);
680
+ assert.doesNotMatch(result.stdout, /real_block/);
681
+ assert.doesNotMatch(result.stdout, /level/);
682
+ const output = parseSingleJsonStdout(result.stdout);
683
+ assert.equal(output.decision, 'block');
684
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_invalid_stdout');
685
+ });
686
+ });
431
687
  it('replaces oversized Stop child stdout with fallback Stop JSON', async () => {
432
688
  await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
433
689
  const commandPath = join(cacheRoot, process.platform === 'win32' ? 'oversized-stop-stdout.cmd' : 'oversized-stop-stdout.sh');
@@ -451,7 +707,7 @@ head -c 1100000 /dev/zero | tr '\0' x
451
707
  it('emits Stop JSON for malformed Stop-looking stdin before invalid launcher failure', async () => {
452
708
  await withPluginCacheCopy(async (cachePluginRoot) => {
453
709
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
454
- const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"Stop",');
710
+ const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"Stop","session_id":"sess-plugin-malformed-stop",');
455
711
  assert.equal(result.status, 0, result.stderr || result.stdout);
456
712
  const output = parseSingleJsonStdout(result.stdout);
457
713
  assert.equal(output.decision, 'block');
@@ -461,7 +717,7 @@ head -c 1100000 /dev/zero | tr '\0' x
461
717
  it('emits Stop JSON for the core-supported name alias before invalid launcher failure', async () => {
462
718
  await withPluginCacheCopy(async (cachePluginRoot) => {
463
719
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
464
- const result = runPluginNativeHook(cachePluginRoot, '{"name":"Stop",');
720
+ const result = runPluginNativeHook(cachePluginRoot, '{"name":"Stop","session_id":"sess-plugin-malformed-name-stop",');
465
721
  assert.equal(result.status, 0, result.stderr || result.stdout);
466
722
  const output = parseSingleJsonStdout(result.stdout);
467
723
  assert.equal(output.decision, 'block');
@@ -494,6 +750,7 @@ head -c 1100000 /dev/zero | tr '\0' x
494
750
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
495
751
  const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
496
752
  hook_event_name: 'UserPromptSubmit',
753
+ session_id: 'sess-plugin-invalid-launcher-user-prompt',
497
754
  prompt: 'hello',
498
755
  }));
499
756
  assert.equal(result.status, 1);
@@ -531,6 +788,7 @@ head -c 1100000 /dev/zero | tr '\0' x
531
788
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
532
789
  const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
533
790
  hook_event_name: 'PreToolUse',
791
+ session_id: 'sess-plugin-nested-stop-text',
534
792
  tool_input: { name: 'Stop' },
535
793
  }));
536
794
  assert.equal(result.status, 1);
@@ -541,7 +799,7 @@ head -c 1100000 /dev/zero | tr '\0' x
541
799
  it('does not classify malformed non-Stop plugin JSON with nested Stop text as Stop', async () => {
542
800
  await withPluginCacheCopy(async (cachePluginRoot) => {
543
801
  await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
544
- const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"PreToolUse","tool_input":{"name":"Stop"},');
802
+ const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"PreToolUse","session_id":"sess-plugin-malformed-non-stop","tool_input":{"name":"Stop"},');
545
803
  assert.equal(result.status, 1);
546
804
  assert.equal(result.stdout, '');
547
805
  assert.match(result.stderr, /invalid plugin hook launcher/);
@@ -681,7 +939,7 @@ head -c 1100000 /dev/zero | tr '\0' x
681
939
  });
682
940
  it('fails oversized non-Stop plugin stdin without Stop JSON', async () => {
683
941
  await withPluginCacheCopy(async (cachePluginRoot) => {
684
- const result = runPluginNativeHook(cachePluginRoot, 'x'.repeat(1024 * 1024 + 1));
942
+ const result = runPluginNativeHook(cachePluginRoot, `{"hook_event_name":"UserPromptSubmit","session_id":"sess-plugin-oversized-non-stop","padding":"${'x'.repeat(1024 * 1024 + 1)}`);
685
943
  assert.equal(result.status, 1);
686
944
  assert.equal(result.stdout, '');
687
945
  assert.match(result.stderr, /plugin hook stdin exceeded/);