oh-my-codex 0.17.3 → 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.
Files changed (381) hide show
  1. package/Cargo.lock +13 -5
  2. package/Cargo.toml +2 -1
  3. package/README.md +44 -19
  4. package/crates/omx-api/Cargo.toml +19 -0
  5. package/crates/omx-api/src/lib.rs +2997 -0
  6. package/crates/omx-api/src/main.rs +10 -0
  7. package/crates/omx-api/tests/cli.rs +558 -0
  8. package/crates/omx-explore/src/main.rs +4 -0
  9. package/crates/omx-sparkshell/src/codex_bridge.rs +437 -123
  10. package/crates/omx-sparkshell/src/exec.rs +127 -1
  11. package/crates/omx-sparkshell/src/main.rs +829 -30
  12. package/crates/omx-sparkshell/src/prompt.rs +25 -3
  13. package/crates/omx-sparkshell/src/redaction.rs +241 -0
  14. package/crates/omx-sparkshell/tests/execution.rs +702 -237
  15. package/dist/cli/__tests__/api.test.d.ts +2 -0
  16. package/dist/cli/__tests__/api.test.d.ts.map +1 -0
  17. package/dist/cli/__tests__/api.test.js +175 -0
  18. package/dist/cli/__tests__/api.test.js.map +1 -0
  19. package/dist/cli/__tests__/ask.test.js +72 -5
  20. package/dist/cli/__tests__/ask.test.js.map +1 -1
  21. package/dist/cli/__tests__/autoresearch-goal.test.js +14 -1
  22. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
  23. package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
  24. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  25. package/dist/cli/__tests__/doctor-warning-copy.test.js +76 -3
  26. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  27. package/dist/cli/__tests__/explore.test.js +23 -0
  28. package/dist/cli/__tests__/explore.test.js.map +1 -1
  29. package/dist/cli/__tests__/index.test.js +171 -5
  30. package/dist/cli/__tests__/index.test.js.map +1 -1
  31. package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
  32. package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
  33. package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
  34. package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
  35. package/dist/cli/__tests__/launch-fallback.test.js +191 -0
  36. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  37. package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
  38. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  39. package/dist/cli/__tests__/question.test.js +27 -41
  40. package/dist/cli/__tests__/question.test.js.map +1 -1
  41. package/dist/cli/__tests__/setup-install-mode.test.js +232 -35
  42. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  43. package/dist/cli/__tests__/sparkshell-cli.test.js +25 -1
  44. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  45. package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
  46. package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
  47. package/dist/cli/__tests__/ultragoal.test.js +227 -4
  48. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  49. package/dist/cli/__tests__/update.test.js +72 -1
  50. package/dist/cli/__tests__/update.test.js.map +1 -1
  51. package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
  52. package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
  53. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
  54. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  55. package/dist/cli/api.d.ts +26 -0
  56. package/dist/cli/api.d.ts.map +1 -0
  57. package/dist/cli/api.js +153 -0
  58. package/dist/cli/api.js.map +1 -0
  59. package/dist/cli/codex-feature-probe.d.ts +5 -0
  60. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  61. package/dist/cli/codex-feature-probe.js +13 -7
  62. package/dist/cli/codex-feature-probe.js.map +1 -1
  63. package/dist/cli/doctor.d.ts +7 -0
  64. package/dist/cli/doctor.d.ts.map +1 -1
  65. package/dist/cli/doctor.js +119 -10
  66. package/dist/cli/doctor.js.map +1 -1
  67. package/dist/cli/explore.d.ts +2 -0
  68. package/dist/cli/explore.d.ts.map +1 -1
  69. package/dist/cli/explore.js +43 -1
  70. package/dist/cli/explore.js.map +1 -1
  71. package/dist/cli/index.d.ts +12 -4
  72. package/dist/cli/index.d.ts.map +1 -1
  73. package/dist/cli/index.js +460 -87
  74. package/dist/cli/index.js.map +1 -1
  75. package/dist/cli/native-assets.d.ts +2 -1
  76. package/dist/cli/native-assets.d.ts.map +1 -1
  77. package/dist/cli/native-assets.js +1 -0
  78. package/dist/cli/native-assets.js.map +1 -1
  79. package/dist/cli/plugin-marketplace.d.ts +2 -0
  80. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  81. package/dist/cli/plugin-marketplace.js +15 -1
  82. package/dist/cli/plugin-marketplace.js.map +1 -1
  83. package/dist/cli/setup.d.ts.map +1 -1
  84. package/dist/cli/setup.js +71 -11
  85. package/dist/cli/setup.js.map +1 -1
  86. package/dist/cli/sparkshell.d.ts +7 -1
  87. package/dist/cli/sparkshell.d.ts.map +1 -1
  88. package/dist/cli/sparkshell.js +31 -4
  89. package/dist/cli/sparkshell.js.map +1 -1
  90. package/dist/cli/ultragoal.d.ts +1 -1
  91. package/dist/cli/ultragoal.d.ts.map +1 -1
  92. package/dist/cli/ultragoal.js +184 -10
  93. package/dist/cli/ultragoal.js.map +1 -1
  94. package/dist/cli/update.d.ts +2 -0
  95. package/dist/cli/update.d.ts.map +1 -1
  96. package/dist/cli/update.js +14 -3
  97. package/dist/cli/update.js.map +1 -1
  98. package/dist/compat/__tests__/doctor-contract.test.js +3 -0
  99. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  100. package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
  101. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
  102. package/dist/config/__tests__/codex-hooks.test.js +19 -8
  103. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  104. package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
  105. package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
  106. package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
  107. package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
  108. package/dist/config/codex-feature-flags.d.ts +4 -0
  109. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  110. package/dist/config/codex-feature-flags.js +4 -0
  111. package/dist/config/codex-feature-flags.js.map +1 -1
  112. package/dist/config/codex-hooks.js +6 -6
  113. package/dist/config/codex-hooks.js.map +1 -1
  114. package/dist/config/commit-lore-guard.d.ts +1 -0
  115. package/dist/config/commit-lore-guard.d.ts.map +1 -1
  116. package/dist/config/commit-lore-guard.js +29 -3
  117. package/dist/config/commit-lore-guard.js.map +1 -1
  118. package/dist/config/generator.d.ts +3 -1
  119. package/dist/config/generator.d.ts.map +1 -1
  120. package/dist/config/generator.js +114 -10
  121. package/dist/config/generator.js.map +1 -1
  122. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -0
  123. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  124. package/dist/goal-workflows/codex-goal-snapshot.js +5 -1
  125. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  126. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +10 -6
  127. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  128. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
  129. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
  130. package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
  131. package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
  132. package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
  133. package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
  134. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  135. package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
  136. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  137. package/dist/hooks/__tests__/keyword-detector.test.js +15 -3
  138. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  139. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
  140. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  141. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
  142. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  143. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
  144. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  145. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
  146. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  147. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  148. package/dist/hooks/extensibility/dispatcher.js +29 -14
  149. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  150. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  151. package/dist/hooks/keyword-detector.js +8 -3
  152. package/dist/hooks/keyword-detector.js.map +1 -1
  153. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  154. package/dist/hooks/keyword-registry.js +1 -0
  155. package/dist/hooks/keyword-registry.js.map +1 -1
  156. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  157. package/dist/hooks/prompt-guidance-contract.js +3 -2
  158. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  159. package/dist/hud/__tests__/hud-tmux-injection.test.js +14 -8
  160. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  161. package/dist/hud/__tests__/reconcile.test.js +4 -4
  162. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  163. package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
  164. package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  165. package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
  166. package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
  167. package/dist/hud/__tests__/tmux.test.js +23 -18
  168. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  169. package/dist/hud/index.d.ts +1 -1
  170. package/dist/hud/index.d.ts.map +1 -1
  171. package/dist/hud/index.js +10 -4
  172. package/dist/hud/index.js.map +1 -1
  173. package/dist/hud/tmux.d.ts.map +1 -1
  174. package/dist/hud/tmux.js +9 -8
  175. package/dist/hud/tmux.js.map +1 -1
  176. package/dist/mcp/__tests__/bootstrap.test.js +75 -1
  177. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  178. package/dist/mcp/bootstrap.d.ts +3 -1
  179. package/dist/mcp/bootstrap.d.ts.map +1 -1
  180. package/dist/mcp/bootstrap.js +71 -2
  181. package/dist/mcp/bootstrap.js.map +1 -1
  182. package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
  183. package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
  184. package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
  185. package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
  186. package/dist/notifications/__tests__/verbosity.test.js +20 -0
  187. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  188. package/dist/notifications/config.d.ts.map +1 -1
  189. package/dist/notifications/config.js +6 -3
  190. package/dist/notifications/config.js.map +1 -1
  191. package/dist/notifications/http-client.d.ts.map +1 -1
  192. package/dist/notifications/http-client.js +78 -27
  193. package/dist/notifications/http-client.js.map +1 -1
  194. package/dist/notifications/types.d.ts +2 -0
  195. package/dist/notifications/types.d.ts.map +1 -1
  196. package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
  197. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  198. package/dist/openclaw/dispatcher.d.ts +7 -4
  199. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  200. package/dist/openclaw/dispatcher.js +32 -69
  201. package/dist/openclaw/dispatcher.js.map +1 -1
  202. package/dist/pipeline/__tests__/orchestrator.test.js +65 -3
  203. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  204. package/dist/pipeline/__tests__/stages.test.js +50 -5
  205. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  206. package/dist/pipeline/index.d.ts +8 -2
  207. package/dist/pipeline/index.d.ts.map +1 -1
  208. package/dist/pipeline/index.js +5 -2
  209. package/dist/pipeline/index.js.map +1 -1
  210. package/dist/pipeline/orchestrator.d.ts +5 -4
  211. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  212. package/dist/pipeline/orchestrator.js +56 -15
  213. package/dist/pipeline/orchestrator.js.map +1 -1
  214. package/dist/pipeline/stages/code-review.d.ts +2 -2
  215. package/dist/pipeline/stages/code-review.d.ts.map +1 -1
  216. package/dist/pipeline/stages/code-review.js +5 -3
  217. package/dist/pipeline/stages/code-review.js.map +1 -1
  218. package/dist/pipeline/stages/deep-interview.d.ts +15 -0
  219. package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
  220. package/dist/pipeline/stages/deep-interview.js +32 -0
  221. package/dist/pipeline/stages/deep-interview.js.map +1 -0
  222. package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
  223. package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
  224. package/dist/pipeline/stages/ralph-verify.js +2 -2
  225. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  226. package/dist/pipeline/stages/ultragoal.d.ts +19 -0
  227. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
  228. package/dist/pipeline/stages/ultragoal.js +38 -0
  229. package/dist/pipeline/stages/ultragoal.js.map +1 -0
  230. package/dist/pipeline/stages/ultraqa.d.ts +30 -0
  231. package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
  232. package/dist/pipeline/stages/ultraqa.js +46 -0
  233. package/dist/pipeline/stages/ultraqa.js.map +1 -0
  234. package/dist/pipeline/types.d.ts +8 -6
  235. package/dist/pipeline/types.d.ts.map +1 -1
  236. package/dist/pipeline/types.js +2 -2
  237. package/dist/scripts/__tests__/codex-native-hook.test.js +1488 -117
  238. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  239. package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
  240. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  241. package/dist/scripts/__tests__/smoke-packed-install.test.js +27 -2
  242. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  243. package/dist/scripts/__tests__/verify-native-agents.test.js +16 -1
  244. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  245. package/dist/scripts/build-api.d.ts +2 -0
  246. package/dist/scripts/build-api.d.ts.map +1 -0
  247. package/dist/scripts/build-api.js +44 -0
  248. package/dist/scripts/build-api.js.map +1 -0
  249. package/dist/scripts/cleanup-explore-harness.js +1 -0
  250. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  251. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  252. package/dist/scripts/codex-native-hook.js +364 -16
  253. package/dist/scripts/codex-native-hook.js.map +1 -1
  254. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  255. package/dist/scripts/codex-native-pre-post.js +98 -25
  256. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  257. package/dist/scripts/notify-dispatcher.js +88 -0
  258. package/dist/scripts/notify-dispatcher.js.map +1 -1
  259. package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
  260. package/dist/scripts/notify-hook/process-runner.js +39 -17
  261. package/dist/scripts/notify-hook/process-runner.js.map +1 -1
  262. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  263. package/dist/scripts/notify-hook/team-dispatch.js +36 -14
  264. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  265. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  266. package/dist/scripts/notify-hook/team-leader-nudge.js +26 -11
  267. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  268. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +2 -1
  269. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  270. package/dist/scripts/notify-hook/team-tmux-guard.js +45 -1
  271. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  272. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  273. package/dist/scripts/notify-hook/team-worker-stop.js +27 -14
  274. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  275. package/dist/scripts/run-provider-advisor.js +9 -3
  276. package/dist/scripts/run-provider-advisor.js.map +1 -1
  277. package/dist/scripts/smoke-packed-install.d.ts +4 -1
  278. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  279. package/dist/scripts/smoke-packed-install.js +101 -1
  280. package/dist/scripts/smoke-packed-install.js.map +1 -1
  281. package/dist/scripts/sync-plugin-mirror.js +2 -2
  282. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  283. package/dist/scripts/verify-native-agents.js +2 -2
  284. package/dist/scripts/verify-native-agents.js.map +1 -1
  285. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
  286. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  287. package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
  288. package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
  289. package/dist/sidecar/index.d.ts +1 -1
  290. package/dist/sidecar/index.d.ts.map +1 -1
  291. package/dist/sidecar/index.js +29 -12
  292. package/dist/sidecar/index.js.map +1 -1
  293. package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
  294. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
  295. package/dist/state/operations.d.ts.map +1 -1
  296. package/dist/state/operations.js +11 -0
  297. package/dist/state/operations.js.map +1 -1
  298. package/dist/team/__tests__/runtime.test.js +2 -2
  299. package/dist/team/__tests__/runtime.test.js.map +1 -1
  300. package/dist/team/__tests__/tmux-session.test.js +207 -22
  301. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  302. package/dist/team/tmux-session.d.ts +1 -0
  303. package/dist/team/tmux-session.d.ts.map +1 -1
  304. package/dist/team/tmux-session.js +73 -28
  305. package/dist/team/tmux-session.js.map +1 -1
  306. package/dist/ultragoal/__tests__/artifacts.test.js +714 -10
  307. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  308. package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
  309. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  310. package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
  311. package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
  312. package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
  313. package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
  314. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
  315. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
  316. package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
  317. package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
  318. package/dist/ultragoal/artifacts.d.ts +97 -2
  319. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  320. package/dist/ultragoal/artifacts.js +811 -256
  321. package/dist/ultragoal/artifacts.js.map +1 -1
  322. package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
  323. package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
  324. package/dist/utils/__tests__/sleep-resource.test.js +39 -0
  325. package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
  326. package/dist/utils/sleep.d.ts.map +1 -1
  327. package/dist/utils/sleep.js +17 -6
  328. package/dist/utils/sleep.js.map +1 -1
  329. package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
  330. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  331. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
  332. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  333. package/package.json +5 -3
  334. package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
  335. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
  336. package/plugins/oh-my-codex/hooks/hooks.json +77 -0
  337. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +77 -47
  338. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
  339. package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
  340. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -8
  341. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
  342. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +22 -11
  343. package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
  344. package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
  345. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -5
  346. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  347. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
  348. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
  349. package/prompts/planner.md +1 -1
  350. package/prompts/researcher.md +15 -10
  351. package/skills/autopilot/SKILL.md +77 -47
  352. package/skills/best-practice-research/SKILL.md +83 -0
  353. package/skills/cancel/SKILL.md +2 -2
  354. package/skills/deep-interview/SKILL.md +9 -8
  355. package/skills/omx-setup/SKILL.md +1 -1
  356. package/skills/pipeline/SKILL.md +22 -11
  357. package/skills/plan/SKILL.md +8 -8
  358. package/skills/ralph/SKILL.md +7 -0
  359. package/skills/ralplan/SKILL.md +5 -5
  360. package/skills/team/SKILL.md +1 -1
  361. package/skills/ultragoal/SKILL.md +38 -4
  362. package/skills/ultrawork/SKILL.md +1 -1
  363. package/src/scripts/__tests__/codex-native-hook.test.ts +1758 -166
  364. package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
  365. package/src/scripts/__tests__/smoke-packed-install.test.ts +39 -2
  366. package/src/scripts/__tests__/verify-native-agents.test.ts +21 -1
  367. package/src/scripts/build-api.ts +48 -0
  368. package/src/scripts/cleanup-explore-harness.ts +1 -0
  369. package/src/scripts/codex-native-hook.ts +416 -18
  370. package/src/scripts/codex-native-pre-post.ts +119 -25
  371. package/src/scripts/notify-dispatcher.ts +97 -0
  372. package/src/scripts/notify-hook/process-runner.ts +40 -16
  373. package/src/scripts/notify-hook/team-dispatch.ts +36 -13
  374. package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
  375. package/src/scripts/notify-hook/team-tmux-guard.ts +49 -0
  376. package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
  377. package/src/scripts/run-provider-advisor.ts +11 -3
  378. package/src/scripts/smoke-packed-install.ts +107 -0
  379. package/src/scripts/sync-plugin-mirror.ts +3 -3
  380. package/src/scripts/verify-native-agents.ts +2 -2
  381. package/templates/catalog-manifest.json +7 -0
@@ -28,6 +28,7 @@ import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
28
28
  import { readAllState } from "../../hud/state.js";
29
29
  import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
30
30
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
31
+ import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
31
32
 
32
33
  function nativeHookScriptPath(): string {
33
34
  return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
@@ -62,7 +63,44 @@ async function writeJson(path: string, value: unknown): Promise<void> {
62
63
  await writeFile(path, JSON.stringify(value, null, 2));
63
64
  }
64
65
 
65
- function buildWorkerStopFakeTmux(tmuxLogPath: string, options: { failSend?: boolean } = {}): string {
66
+ async function withLoreGuardConfig<T>(
67
+ value: string,
68
+ prefix: string,
69
+ run: (cwd: string) => Promise<T>,
70
+ ): Promise<T> {
71
+ const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
72
+ const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
73
+ const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
74
+ const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
75
+ const originalCodexHome = process.env.CODEX_HOME;
76
+ const originalHome = process.env.HOME;
77
+ try {
78
+ delete process.env.OMX_LORE_COMMIT_GUARD;
79
+ process.env.CODEX_HOME = codexHome;
80
+ process.env.HOME = defaultHome;
81
+ await writeFile(
82
+ join(codexHome, "config.toml"),
83
+ `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`,
84
+ "utf-8",
85
+ );
86
+ return await run(cwd);
87
+ } finally {
88
+ if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
89
+ else process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
90
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
91
+ else process.env.CODEX_HOME = originalCodexHome;
92
+ if (originalHome === undefined) delete process.env.HOME;
93
+ else process.env.HOME = originalHome;
94
+ await rm(cwd, { recursive: true, force: true });
95
+ await rm(codexHome, { recursive: true, force: true });
96
+ await rm(defaultHome, { recursive: true, force: true });
97
+ }
98
+ }
99
+
100
+ function buildWorkerStopFakeTmux(
101
+ tmuxLogPath: string,
102
+ options: { failSend?: boolean; busyLeader?: boolean } = {},
103
+ ): string {
66
104
  return `#!/usr/bin/env bash
67
105
  set -eu
68
106
  echo "$@" >> "${tmuxLogPath}"
@@ -90,7 +128,7 @@ if [[ "$cmd" == "display-message" ]]; then
90
128
  exit 0
91
129
  fi
92
130
  if [[ "$cmd" == "capture-pane" ]]; then
93
- echo "› ready"
131
+ ${options.busyLeader ? 'echo "• Working… (esc to interrupt)"' : 'echo "› ready"'}
94
132
  exit 0
95
133
  fi
96
134
  if [[ "$cmd" == "send-keys" ]]; then
@@ -726,7 +764,16 @@ describe("codex native hook dispatch", () => {
726
764
 
727
765
  it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
728
766
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
767
+ const originalCodexHome = process.env.CODEX_HOME;
729
768
  try {
769
+ process.env.CODEX_HOME = join(cwd, "codex-home");
770
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
771
+ notifications: {
772
+ enabled: true,
773
+ verbosity: "session",
774
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
775
+ },
776
+ });
730
777
  const stateDir = join(cwd, ".omx", "state");
731
778
  const canonicalSessionId = "omx-leader-session";
732
779
  const leaderNativeSessionId = "codex-leader-thread";
@@ -742,6 +789,16 @@ describe("codex native hook dispatch", () => {
742
789
  iteration: 1,
743
790
  max_iterations: 5,
744
791
  });
792
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
793
+ await writeFile(
794
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
795
+ [
796
+ "import { appendFileSync } from 'node:fs';",
797
+ "export async function onHookEvent(event) {",
798
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
799
+ "}",
800
+ ].join("\n"),
801
+ );
745
802
  const transcriptPath = join(cwd, "subagent-rollout.jsonl");
746
803
  await writeFile(
747
804
  transcriptPath,
@@ -791,6 +848,11 @@ describe("codex native hook dispatch", () => {
791
848
  ) as { active?: boolean; current_phase?: string };
792
849
  assert.equal(leaderRalph.active, true);
793
850
  assert.equal(leaderRalph.current_phase, "executing");
851
+ assert.equal(
852
+ existsSync(join(cwd, "hook-events.jsonl")),
853
+ false,
854
+ "subagent SessionStart must not independently dispatch session-start hook notifications",
855
+ );
794
856
 
795
857
  const tracking = JSON.parse(
796
858
  await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"),
@@ -806,7 +868,257 @@ describe("codex native hook dispatch", () => {
806
868
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
807
869
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
808
870
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
871
+
872
+ await dispatchCodexNativeHook(
873
+ {
874
+ hook_event_name: "Stop",
875
+ cwd,
876
+ session_id: childNativeSessionId,
877
+ thread_id: childNativeSessionId,
878
+ turn_id: "child-stop-turn",
879
+ },
880
+ { cwd },
881
+ );
882
+ assert.equal(
883
+ existsSync(join(cwd, "hook-events.jsonl")),
884
+ false,
885
+ "subagent Stop must not independently dispatch stop hook notifications",
886
+ );
887
+ } finally {
888
+ if (originalCodexHome === undefined) {
889
+ delete process.env.CODEX_HOME;
890
+ } else {
891
+ process.env.CODEX_HOME = originalCodexHome;
892
+ }
893
+ await rm(cwd, { recursive: true, force: true });
894
+ }
895
+ });
896
+
897
+ it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
898
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
899
+ const originalCodexHome = process.env.CODEX_HOME;
900
+ try {
901
+ process.env.CODEX_HOME = join(cwd, "codex-home");
902
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
903
+ notifications: {
904
+ enabled: true,
905
+ verbosity: "minimal",
906
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
907
+ },
908
+ });
909
+ const stateDir = join(cwd, ".omx", "state");
910
+ const canonicalSessionId = "omx-leader-session-minimal";
911
+ const leaderNativeSessionId = "codex-leader-thread-minimal";
912
+ const childNativeSessionId = "codex-child-thread-minimal";
913
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
914
+ await writeSessionStart(cwd, canonicalSessionId, {
915
+ nativeSessionId: leaderNativeSessionId,
916
+ });
917
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
918
+ await writeFile(
919
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
920
+ [
921
+ "import { appendFileSync } from 'node:fs';",
922
+ "export async function onHookEvent(event) {",
923
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
924
+ "}",
925
+ ].join("\n"),
926
+ );
927
+ const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
928
+ await writeFile(
929
+ transcriptPath,
930
+ `${JSON.stringify({
931
+ type: "session_meta",
932
+ payload: {
933
+ id: childNativeSessionId,
934
+ source: {
935
+ subagent: {
936
+ thread_spawn: {
937
+ parent_thread_id: leaderNativeSessionId,
938
+ agent_role: "verifier",
939
+ },
940
+ },
941
+ },
942
+ },
943
+ })}\n`,
944
+ );
945
+
946
+ await dispatchCodexNativeHook(
947
+ {
948
+ hook_event_name: "SessionStart",
949
+ cwd,
950
+ session_id: childNativeSessionId,
951
+ transcript_path: transcriptPath,
952
+ },
953
+ { cwd, sessionOwnerPid: process.pid },
954
+ );
955
+
956
+ assert.equal(
957
+ existsSync(join(cwd, "hook-events.jsonl")),
958
+ false,
959
+ "subagent SessionStart must be suppressed at minimal verbosity",
960
+ );
961
+ } finally {
962
+ if (originalCodexHome === undefined) {
963
+ delete process.env.CODEX_HOME;
964
+ } else {
965
+ process.env.CODEX_HOME = originalCodexHome;
966
+ }
967
+ await rm(cwd, { recursive: true, force: true });
968
+ }
969
+ });
970
+
971
+ it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
972
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
973
+ const originalCodexHome = process.env.CODEX_HOME;
974
+ try {
975
+ process.env.CODEX_HOME = join(cwd, "codex-home");
976
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
977
+ notifications: {
978
+ enabled: true,
979
+ verbosity: "session",
980
+ includeChildAgents: true,
981
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
982
+ },
983
+ });
984
+ const stateDir = join(cwd, ".omx", "state");
985
+ const canonicalSessionId = "omx-leader-session-include";
986
+ const leaderNativeSessionId = "codex-leader-thread-include";
987
+ const childNativeSessionId = "codex-child-thread-include";
988
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
989
+ await writeSessionStart(cwd, canonicalSessionId, {
990
+ nativeSessionId: leaderNativeSessionId,
991
+ });
992
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
993
+ await writeFile(
994
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
995
+ [
996
+ "import { appendFileSync } from 'node:fs';",
997
+ "export async function onHookEvent(event) {",
998
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
999
+ "}",
1000
+ ].join("\n"),
1001
+ );
1002
+ const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
1003
+ await writeFile(
1004
+ transcriptPath,
1005
+ `${JSON.stringify({
1006
+ type: "session_meta",
1007
+ payload: {
1008
+ id: childNativeSessionId,
1009
+ source: {
1010
+ subagent: {
1011
+ thread_spawn: {
1012
+ parent_thread_id: leaderNativeSessionId,
1013
+ agent_role: "verifier",
1014
+ },
1015
+ },
1016
+ },
1017
+ },
1018
+ })}\n`,
1019
+ );
1020
+
1021
+ await dispatchCodexNativeHook(
1022
+ {
1023
+ hook_event_name: "SessionStart",
1024
+ cwd,
1025
+ session_id: childNativeSessionId,
1026
+ transcript_path: transcriptPath,
1027
+ },
1028
+ { cwd, sessionOwnerPid: process.pid },
1029
+ );
1030
+
1031
+ await dispatchCodexNativeHook(
1032
+ {
1033
+ hook_event_name: "Stop",
1034
+ cwd,
1035
+ session_id: childNativeSessionId,
1036
+ thread_id: childNativeSessionId,
1037
+ turn_id: "included-child-stop-turn",
1038
+ },
1039
+ { cwd },
1040
+ );
1041
+
1042
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1043
+ assert.match(hookEvents, /"event":"session-start"/);
1044
+ assert.match(hookEvents, /"event":"stop"/);
1045
+ } finally {
1046
+ if (originalCodexHome === undefined) {
1047
+ delete process.env.CODEX_HOME;
1048
+ } else {
1049
+ process.env.CODEX_HOME = originalCodexHome;
1050
+ }
1051
+ await rm(cwd, { recursive: true, force: true });
1052
+ }
1053
+ });
1054
+
1055
+ it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
1056
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
1057
+ const originalCodexHome = process.env.CODEX_HOME;
1058
+ try {
1059
+ process.env.CODEX_HOME = join(cwd, "codex-home");
1060
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
1061
+ notifications: {
1062
+ enabled: true,
1063
+ verbosity: "agent",
1064
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
1065
+ },
1066
+ });
1067
+ const stateDir = join(cwd, ".omx", "state");
1068
+ const canonicalSessionId = "omx-leader-session-agent";
1069
+ const leaderNativeSessionId = "codex-leader-thread-agent";
1070
+ const childNativeSessionId = "codex-child-thread-agent";
1071
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
1072
+ await writeSessionStart(cwd, canonicalSessionId, {
1073
+ nativeSessionId: leaderNativeSessionId,
1074
+ });
1075
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
1076
+ await writeFile(
1077
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
1078
+ [
1079
+ "import { appendFileSync } from 'node:fs';",
1080
+ "export async function onHookEvent(event) {",
1081
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
1082
+ "}",
1083
+ ].join("\n"),
1084
+ );
1085
+ const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
1086
+ await writeFile(
1087
+ transcriptPath,
1088
+ `${JSON.stringify({
1089
+ type: "session_meta",
1090
+ payload: {
1091
+ id: childNativeSessionId,
1092
+ source: {
1093
+ subagent: {
1094
+ thread_spawn: {
1095
+ parent_thread_id: leaderNativeSessionId,
1096
+ agent_role: "verifier",
1097
+ },
1098
+ },
1099
+ },
1100
+ },
1101
+ })}\n`,
1102
+ );
1103
+
1104
+ await dispatchCodexNativeHook(
1105
+ {
1106
+ hook_event_name: "SessionStart",
1107
+ cwd,
1108
+ session_id: childNativeSessionId,
1109
+ transcript_path: transcriptPath,
1110
+ },
1111
+ { cwd, sessionOwnerPid: process.pid },
1112
+ );
1113
+
1114
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1115
+ assert.match(hookEvents, /"event":"session-start"/);
809
1116
  } finally {
1117
+ if (originalCodexHome === undefined) {
1118
+ delete process.env.CODEX_HOME;
1119
+ } else {
1120
+ process.env.CODEX_HOME = originalCodexHome;
1121
+ }
810
1122
  await rm(cwd, { recursive: true, force: true });
811
1123
  }
812
1124
  });
@@ -1495,6 +1807,66 @@ describe("codex native hook dispatch", () => {
1495
1807
  }
1496
1808
  });
1497
1809
 
1810
+ it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
1811
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
1812
+ try {
1813
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1814
+ version: 1,
1815
+ workflow: "performance-goal",
1816
+ slug: "latency",
1817
+ objective: "Reduce latency",
1818
+ status: "blocked",
1819
+ lastValidation: {
1820
+ status: "blocked",
1821
+ evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
1822
+ recordedAt: "2026-05-20T00:00:00.000Z",
1823
+ },
1824
+ });
1825
+
1826
+ const result = await dispatchCodexNativeHook({
1827
+ hook_event_name: "Stop",
1828
+ cwd,
1829
+ session_id: "sess-performance-mismatch-blocked-stop",
1830
+ thread_id: "thread-performance-mismatch-blocked-stop",
1831
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1832
+ }, { cwd });
1833
+
1834
+ assert.notEqual(result.outputJson?.decision, "block");
1835
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1836
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1837
+ } finally {
1838
+ await rm(cwd, { recursive: true, force: true });
1839
+ }
1840
+ });
1841
+
1842
+ it("does not block Stop for an already complete performance-goal state", async () => {
1843
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
1844
+ try {
1845
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1846
+ version: 1,
1847
+ workflow: "performance-goal",
1848
+ slug: "latency",
1849
+ objective: "Reduce latency",
1850
+ status: "complete",
1851
+ completedAt: "2026-05-20T00:00:00.000Z",
1852
+ });
1853
+
1854
+ const result = await dispatchCodexNativeHook({
1855
+ hook_event_name: "Stop",
1856
+ cwd,
1857
+ session_id: "sess-performance-complete-stop",
1858
+ thread_id: "thread-performance-complete-stop",
1859
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1860
+ }, { cwd });
1861
+
1862
+ assert.notEqual(result.outputJson?.decision, "block");
1863
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1864
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1865
+ } finally {
1866
+ await rm(cwd, { recursive: true, force: true });
1867
+ }
1868
+ });
1869
+
1498
1870
  it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
1499
1871
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
1500
1872
  try {
@@ -1543,7 +1915,7 @@ describe("codex native hook dispatch", () => {
1543
1915
  }
1544
1916
  });
1545
1917
 
1546
- it("blocks ultragoal Stop with blocked checkpoint and fresh-thread remediation for completed legacy snapshots", async () => {
1918
+ it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
1547
1919
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
1548
1920
  try {
1549
1921
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
@@ -1564,7 +1936,8 @@ describe("codex native hook dispatch", () => {
1564
1936
  assert.equal(result.outputJson?.decision, "block");
1565
1937
  assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1566
1938
  assert.match(output, /--status blocked/);
1567
- assert.match(output, /fresh Codex thread/);
1939
+ assert.match(output, /Codex goal context/);
1940
+ assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
1568
1941
  assert.match(output, /Hooks must not mutate Codex goal state/);
1569
1942
  } finally {
1570
1943
  await rm(cwd, { recursive: true, force: true });
@@ -1578,7 +1951,7 @@ describe("codex native hook dispatch", () => {
1578
1951
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1579
1952
  version: 1,
1580
1953
  codexGoalMode: "aggregate",
1581
- codexObjective: "Complete all ultragoal stories in .omx/ultragoal/goals.json: many micro goals",
1954
+ codexObjective: "Complete the durable ultragoal plan in .omx/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omx/ultragoal/ledger.jsonl as the audit trail.",
1582
1955
  activeGoalId: "G001-micro",
1583
1956
  aggregateCompletion: {
1584
1957
  status: "complete",
@@ -1701,30 +2074,95 @@ describe("codex native hook dispatch", () => {
1701
2074
  }
1702
2075
  });
1703
2076
 
1704
- it("treats workflow keywords in native subagent prompt text as literal delegation text", async () => {
1705
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-keyword-literal-"));
2077
+ it("does not repeat Stop block when the last autoresearch-goal completion attempt reported objective mismatch", async () => {
2078
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-mismatch-reported-stop-"));
1706
2079
  try {
1707
- const stateDir = join(cwd, ".omx", "state");
1708
- const canonicalSessionId = "sess-parent";
1709
- const leaderNativeSessionId = "native-parent-thread";
1710
- const childNativeSessionId = "native-child-thread";
1711
- const nowIso = new Date().toISOString();
1712
-
1713
- await writeJson(join(stateDir, "session.json"), {
1714
- session_id: canonicalSessionId,
1715
- native_session_id: leaderNativeSessionId,
2080
+ await writeJson(join(cwd, ".omx", "goals", "autoresearch", "mismatched-mission", "mission.json"), {
2081
+ version: 1,
2082
+ workflow: "autoresearch-goal",
2083
+ slug: "mismatched-mission",
2084
+ topic: "Passing research bound to another Codex goal",
2085
+ status: "passed",
1716
2086
  });
1717
- await writeJson(join(stateDir, "subagent-tracking.json"), {
1718
- schemaVersion: 1,
1719
- sessions: {
1720
- [canonicalSessionId]: {
1721
- session_id: canonicalSessionId,
1722
- leader_thread_id: leaderNativeSessionId,
1723
- updated_at: nowIso,
1724
- threads: {
1725
- [leaderNativeSessionId]: {
1726
- thread_id: leaderNativeSessionId,
1727
- kind: "leader",
2087
+ await writeJson(join(cwd, ".omx", "goals", "autoresearch", "mismatched-mission", "completion.json"), {
2088
+ verdict: "pass",
2089
+ passed: true,
2090
+ });
2091
+
2092
+ const result = await dispatchCodexNativeHook({
2093
+ hook_event_name: "Stop",
2094
+ cwd,
2095
+ session_id: "sess-autoresearch-mismatch-reported-stop",
2096
+ thread_id: "thread-autoresearch-mismatch-reported-stop",
2097
+ last_assistant_message: [
2098
+ "I called get_goal and ran omx autoresearch-goal complete --slug mismatched-mission --codex-goal-json /tmp/snapshot.json.",
2099
+ "The autoresearch-goal completion failed with Codex goal objective mismatch, so I will not repeat the same complete command blindly in this thread.",
2100
+ ].join("\n"),
2101
+ }, { cwd });
2102
+
2103
+ assert.notEqual(result.outputJson?.decision, "block");
2104
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /autoresearch-goal complete --slug mismatched-mission/);
2105
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
2106
+ } finally {
2107
+ await rm(cwd, { recursive: true, force: true });
2108
+ }
2109
+ });
2110
+
2111
+ it("still blocks later autoresearch-goal completion claims after an objective mismatch if no mismatch is reported in the final answer", async () => {
2112
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autoresearch-mismatch-later-retry-stop-"));
2113
+ try {
2114
+ await writeJson(join(cwd, ".omx", "goals", "autoresearch", "retryable-mission", "mission.json"), {
2115
+ version: 1,
2116
+ workflow: "autoresearch-goal",
2117
+ slug: "retryable-mission",
2118
+ topic: "Passing research that can still retry with the correct snapshot",
2119
+ status: "passed",
2120
+ });
2121
+ await writeJson(join(cwd, ".omx", "goals", "autoresearch", "retryable-mission", "completion.json"), {
2122
+ verdict: "pass",
2123
+ passed: true,
2124
+ });
2125
+
2126
+ const result = await dispatchCodexNativeHook({
2127
+ hook_event_name: "Stop",
2128
+ cwd,
2129
+ session_id: "sess-autoresearch-mismatch-later-retry-stop",
2130
+ thread_id: "thread-autoresearch-mismatch-later-retry-stop",
2131
+ last_assistant_message: "Autoresearch goal complete; next call update_goal({status: \"complete\"}).",
2132
+ }, { cwd });
2133
+
2134
+ assert.equal(result.outputJson?.decision, "block");
2135
+ assert.match(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
2136
+ assert.match(JSON.stringify(result.outputJson), /omx autoresearch-goal complete --slug retryable-mission/);
2137
+ } finally {
2138
+ await rm(cwd, { recursive: true, force: true });
2139
+ }
2140
+ });
2141
+
2142
+ it("treats workflow keywords in native subagent prompt text as literal delegation text", async () => {
2143
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-keyword-literal-"));
2144
+ try {
2145
+ const stateDir = join(cwd, ".omx", "state");
2146
+ const canonicalSessionId = "sess-parent";
2147
+ const leaderNativeSessionId = "native-parent-thread";
2148
+ const childNativeSessionId = "native-child-thread";
2149
+ const nowIso = new Date().toISOString();
2150
+
2151
+ await writeJson(join(stateDir, "session.json"), {
2152
+ session_id: canonicalSessionId,
2153
+ native_session_id: leaderNativeSessionId,
2154
+ });
2155
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
2156
+ schemaVersion: 1,
2157
+ sessions: {
2158
+ [canonicalSessionId]: {
2159
+ session_id: canonicalSessionId,
2160
+ leader_thread_id: leaderNativeSessionId,
2161
+ updated_at: nowIso,
2162
+ threads: {
2163
+ [leaderNativeSessionId]: {
2164
+ thread_id: leaderNativeSessionId,
2165
+ kind: "leader",
1728
2166
  first_seen_at: nowIso,
1729
2167
  last_seen_at: nowIso,
1730
2168
  turn_count: 1,
@@ -1822,12 +2260,190 @@ describe("codex native hook dispatch", () => {
1822
2260
  assert.match(message, /get_goal/);
1823
2261
  assert.match(message, /create_goal/);
1824
2262
  assert.match(message, /update_goal/);
2263
+ assert.match(message, /does not call `\/goal clear`/);
2264
+ assert.match(message, /multiple sequential ultragoal runs/);
1825
2265
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
1826
2266
  } finally {
1827
2267
  await rm(cwd, { recursive: true, force: true });
1828
2268
  }
1829
2269
  });
1830
2270
 
2271
+ it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
2272
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
2273
+ try {
2274
+ await createUltragoalPlan(cwd, {
2275
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
2276
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2277
+ });
2278
+
2279
+ const prose = await dispatchCodexNativeHook(
2280
+ {
2281
+ hook_event_name: "UserPromptSubmit",
2282
+ cwd,
2283
+ session_id: "sess-ultragoal-steer-1",
2284
+ prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
2285
+ },
2286
+ { cwd },
2287
+ );
2288
+ assert.equal(prose.outputJson, null);
2289
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2290
+
2291
+ const jsonExample = await dispatchCodexNativeHook(
2292
+ {
2293
+ hook_event_name: "UserPromptSubmit",
2294
+ cwd,
2295
+ session_id: "sess-ultragoal-steer-1",
2296
+ prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
2297
+ kind: "add_subgoal",
2298
+ source: "user_prompt_submit",
2299
+ evidence: "Example JSON should not mutate .omx/ultragoal.",
2300
+ rationale: "Only explicit steering fences or labels are executable.",
2301
+ title: "Inert JSON example",
2302
+ objective: "This example must not be added.",
2303
+ })}\n\`\`\``,
2304
+ },
2305
+ { cwd },
2306
+ );
2307
+ assert.equal(jsonExample.outputJson, null);
2308
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2309
+
2310
+ const result = await dispatchCodexNativeHook(
2311
+ {
2312
+ hook_event_name: "UserPromptSubmit",
2313
+ cwd,
2314
+ session_id: "sess-ultragoal-steer-1",
2315
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2316
+ kind: "add_subgoal",
2317
+ source: "user_prompt_submit",
2318
+ evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
2319
+ rationale: "Add bounded hook regression work while preserving all completion gates.",
2320
+ title: "Prompt bridge regression",
2321
+ objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
2322
+ })}`,
2323
+ },
2324
+ { cwd },
2325
+ );
2326
+
2327
+ const message = String(
2328
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2329
+ );
2330
+ assert.match(message, /bounded \.omx\/ultragoal steering/);
2331
+ assert.match(message, /G002-cli-and-prompt-submit-bridge/);
2332
+ assert.match(message, /accepted/);
2333
+ const plan = await readUltragoalPlan(cwd);
2334
+ assert.equal(plan.goals.length, 2);
2335
+ assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
2336
+ } finally {
2337
+ await rm(cwd, { recursive: true, force: true });
2338
+ }
2339
+ });
2340
+
2341
+ it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
2342
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
2343
+ try {
2344
+ await createUltragoalPlan(cwd, {
2345
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
2346
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2347
+ });
2348
+ const stateDir = join(cwd, ".omx", "state");
2349
+ const canonicalSessionId = "sess-ultragoal-parent";
2350
+ const leaderNativeSessionId = "native-ultragoal-parent";
2351
+ const childNativeSessionId = "native-ultragoal-child";
2352
+ const nowIso = new Date().toISOString();
2353
+ await writeJson(join(stateDir, "session.json"), {
2354
+ session_id: canonicalSessionId,
2355
+ native_session_id: leaderNativeSessionId,
2356
+ });
2357
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
2358
+ schemaVersion: 1,
2359
+ sessions: {
2360
+ [canonicalSessionId]: {
2361
+ session_id: canonicalSessionId,
2362
+ leader_thread_id: leaderNativeSessionId,
2363
+ updated_at: nowIso,
2364
+ threads: {
2365
+ [leaderNativeSessionId]: {
2366
+ thread_id: leaderNativeSessionId,
2367
+ kind: "leader",
2368
+ first_seen_at: nowIso,
2369
+ last_seen_at: nowIso,
2370
+ turn_count: 1,
2371
+ },
2372
+ [childNativeSessionId]: {
2373
+ thread_id: childNativeSessionId,
2374
+ kind: "subagent",
2375
+ first_seen_at: nowIso,
2376
+ last_seen_at: nowIso,
2377
+ turn_count: 1,
2378
+ mode: "architect",
2379
+ },
2380
+ },
2381
+ },
2382
+ },
2383
+ });
2384
+
2385
+ const result = await dispatchCodexNativeHook(
2386
+ {
2387
+ hook_event_name: "UserPromptSubmit",
2388
+ cwd,
2389
+ session_id: childNativeSessionId,
2390
+ thread_id: childNativeSessionId,
2391
+ turn_id: "turn-ultragoal-child-1",
2392
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2393
+ kind: "add_subgoal",
2394
+ source: "user_prompt_submit",
2395
+ evidence: "Subagent prompt text must be literal delegated context.",
2396
+ rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
2397
+ title: "Subagent should not add this",
2398
+ objective: "This must remain literal prompt text.",
2399
+ })}`,
2400
+ },
2401
+ { cwd },
2402
+ );
2403
+
2404
+ assert.equal(result.outputJson, null);
2405
+ const plan = await readUltragoalPlan(cwd);
2406
+ assert.equal(plan.goals.length, 1);
2407
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2408
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
2409
+ assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
2410
+ } finally {
2411
+ await rm(cwd, { recursive: true, force: true });
2412
+ }
2413
+ });
2414
+
2415
+ it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
2416
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
2417
+ try {
2418
+ await createUltragoalPlan(cwd, {
2419
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
2420
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2421
+ });
2422
+ const prompt = `\`\`\`omx-ultragoal-steer
2423
+ ${JSON.stringify({
2424
+ kind: "add_subgoal",
2425
+ source: "user_prompt_submit",
2426
+ evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
2427
+ rationale: "Use idempotent bridge semantics for repeated hook delivery.",
2428
+ title: "Deduped bridge regression",
2429
+ objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
2430
+ })}
2431
+ \`\`\``;
2432
+ await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2433
+ const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2434
+ const message = String(
2435
+ (second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2436
+ );
2437
+ assert.match(message, /deduped/);
2438
+ const plan = await readUltragoalPlan(cwd);
2439
+ assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
2440
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2441
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
2442
+ } finally {
2443
+ await rm(cwd, { recursive: true, force: true });
2444
+ }
2445
+ });
2446
+
1831
2447
  it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
1832
2448
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
1833
2449
  try {
@@ -2863,6 +3479,52 @@ exit 0
2863
3479
  }
2864
3480
  });
2865
3481
 
3482
+ it("does not block Bash commands that only mention omx question in quoted arguments", async () => {
3483
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-quoted-mention-"));
3484
+ try {
3485
+ const result = await dispatchCodexNativeHook(
3486
+ {
3487
+ hook_event_name: "PreToolUse",
3488
+ cwd,
3489
+ tool_name: "Bash",
3490
+ tool_use_id: "tool-question-quoted-mention",
3491
+ tool_input: {
3492
+ command: `omx ultragoal create-goals --brief "Deep interview says omx question failed in tmux"`,
3493
+ },
3494
+ },
3495
+ { cwd },
3496
+ );
3497
+
3498
+ assert.equal(result.omxEventName, "pre-tool-use");
3499
+ assert.equal(result.outputJson, null);
3500
+ } finally {
3501
+ await rm(cwd, { recursive: true, force: true });
3502
+ }
3503
+ });
3504
+
3505
+ it("does not block Bash heredocs that only document omx question text", async () => {
3506
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-heredoc-mention-"));
3507
+ try {
3508
+ const result = await dispatchCodexNativeHook(
3509
+ {
3510
+ hook_event_name: "PreToolUse",
3511
+ cwd,
3512
+ tool_name: "Bash",
3513
+ tool_use_id: "tool-question-heredoc-mention",
3514
+ tool_input: {
3515
+ command: `cat > issue-notes.md <<'EOF'\nomx question failed in the attached tmux pane\nEOF`,
3516
+ },
3517
+ },
3518
+ { cwd },
3519
+ );
3520
+
3521
+ assert.equal(result.omxEventName, "pre-tool-use");
3522
+ assert.equal(result.outputJson, null);
3523
+ } finally {
3524
+ await rm(cwd, { recursive: true, force: true });
3525
+ }
3526
+ });
3527
+
2866
3528
  it("allows Bash omx question when the command preserves the leader-pane return hint", async () => {
2867
3529
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-question-allow-"));
2868
3530
  try {
@@ -3654,7 +4316,7 @@ exit 0
3654
4316
  cwd,
3655
4317
  tool_name: "Bash",
3656
4318
  tool_use_id: "tool-slop-git-priority",
3657
- tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
4319
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
3658
4320
  },
3659
4321
  { cwd },
3660
4322
  );
@@ -3677,7 +4339,7 @@ exit 0
3677
4339
  cwd,
3678
4340
  tool_name: "Bash",
3679
4341
  tool_use_id: "tool-git-commit-invalid",
3680
- tool_input: { command: 'git commit -m "fix tests"' },
4342
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
3681
4343
  },
3682
4344
  { cwd },
3683
4345
  );
@@ -3706,24 +4368,26 @@ exit 0
3706
4368
  }
3707
4369
  });
3708
4370
 
3709
- it("allows non-Lore git commit messages when the Lore commit guard is explicitly disabled", async () => {
3710
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
4371
+
4372
+ it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
4373
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
3711
4374
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3712
4375
  try {
3713
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4376
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3714
4377
  const result = await dispatchCodexNativeHook(
3715
4378
  {
3716
4379
  hook_event_name: "PreToolUse",
3717
4380
  cwd,
3718
4381
  tool_name: "Bash",
3719
- tool_use_id: "tool-git-commit-lore-disabled",
3720
- tool_input: { command: 'git commit -m "fix: use conventional commit"' },
4382
+ tool_use_id: "tool-git-commit-lore-env-enabled",
4383
+ tool_input: { command: 'git commit -m "fix tests"' },
3721
4384
  },
3722
4385
  { cwd },
3723
4386
  );
3724
4387
 
3725
4388
  assert.equal(result.omxEventName, "pre-tool-use");
3726
- assert.equal(result.outputJson, null);
4389
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4390
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3727
4391
  } finally {
3728
4392
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3729
4393
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -3731,16 +4395,18 @@ exit 0
3731
4395
  }
3732
4396
  });
3733
4397
 
3734
- it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
3735
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
4398
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
4399
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
4400
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
3736
4401
  try {
4402
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3737
4403
  const result = await dispatchCodexNativeHook(
3738
4404
  {
3739
4405
  hook_event_name: "PreToolUse",
3740
4406
  cwd,
3741
4407
  tool_name: "Bash",
3742
- tool_use_id: "tool-git-commit-lore-inline-disabled",
3743
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4408
+ tool_use_id: "tool-git-commit-lore-disabled",
4409
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
3744
4410
  },
3745
4411
  { cwd },
3746
4412
  );
@@ -3748,20 +4414,21 @@ exit 0
3748
4414
  assert.equal(result.omxEventName, "pre-tool-use");
3749
4415
  assert.equal(result.outputJson, null);
3750
4416
  } finally {
4417
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4418
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
3751
4419
  await rm(cwd, { recursive: true, force: true });
3752
4420
  }
3753
4421
  });
3754
4422
 
3755
- it("does not treat newline-separated Lore guard assignment as inline git commit env", async () => {
3756
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
3757
- try {
4423
+ it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
4424
+ await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
3758
4425
  const result = await dispatchCodexNativeHook(
3759
4426
  {
3760
4427
  hook_event_name: "PreToolUse",
3761
4428
  cwd,
3762
4429
  tool_name: "Bash",
3763
- tool_use_id: "tool-git-commit-lore-newline-assignment",
3764
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0\ngit commit -m "fix: conventional"' },
4430
+ tool_use_id: "tool-git-commit-lore-config-enabled",
4431
+ tool_input: { command: 'git commit -m "fix: conventional"' },
3765
4432
  },
3766
4433
  { cwd },
3767
4434
  );
@@ -3769,49 +4436,36 @@ exit 0
3769
4436
  assert.equal(result.omxEventName, "pre-tool-use");
3770
4437
  assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3771
4438
  assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3772
- } finally {
3773
- await rm(cwd, { recursive: true, force: true });
3774
- }
4439
+ });
3775
4440
  });
3776
4441
 
3777
- it("restores default-on Lore guard when env -u unsets a disabled process env", async () => {
3778
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
3779
- const original = process.env.OMX_LORE_COMMIT_GUARD;
3780
- try {
3781
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4442
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
4443
+ await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
3782
4444
  const result = await dispatchCodexNativeHook(
3783
4445
  {
3784
4446
  hook_event_name: "PreToolUse",
3785
4447
  cwd,
3786
4448
  tool_name: "Bash",
3787
- tool_use_id: "tool-git-commit-lore-env-unset",
3788
- tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4449
+ tool_use_id: "tool-git-commit-lore-config-disabled",
4450
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
3789
4451
  },
3790
4452
  { cwd },
3791
4453
  );
3792
4454
 
3793
4455
  assert.equal(result.omxEventName, "pre-tool-use");
3794
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3795
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3796
- } finally {
3797
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3798
- else process.env.OMX_LORE_COMMIT_GUARD = original;
3799
- await rm(cwd, { recursive: true, force: true });
3800
- }
4456
+ assert.equal(result.outputJson, null);
4457
+ });
3801
4458
  });
3802
4459
 
3803
- it("restores default-on Lore guard when env -i clears a disabled process env", async () => {
3804
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
3805
- const original = process.env.OMX_LORE_COMMIT_GUARD;
3806
- try {
3807
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4460
+ it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
4461
+ await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
3808
4462
  const result = await dispatchCodexNativeHook(
3809
4463
  {
3810
4464
  hook_event_name: "PreToolUse",
3811
4465
  cwd,
3812
4466
  tool_name: "Bash",
3813
- tool_use_id: "tool-git-commit-lore-env-ignore",
3814
- tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
4467
+ tool_use_id: "tool-git-commit-lore-config-inline-enabled",
4468
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
3815
4469
  },
3816
4470
  { cwd },
3817
4471
  );
@@ -3819,36 +4473,185 @@ exit 0
3819
4473
  assert.equal(result.omxEventName, "pre-tool-use");
3820
4474
  assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3821
4475
  assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3822
- } finally {
3823
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3824
- else process.env.OMX_LORE_COMMIT_GUARD = original;
3825
- await rm(cwd, { recursive: true, force: true });
3826
- }
4476
+ });
3827
4477
  });
3828
4478
 
3829
- it("keeps Lore commit enforcement enabled for unknown inline guard values", async () => {
3830
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
3831
- try {
4479
+ it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
4480
+ await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
3832
4481
  const result = await dispatchCodexNativeHook(
3833
4482
  {
3834
4483
  hook_event_name: "PreToolUse",
3835
4484
  cwd,
3836
4485
  tool_name: "Bash",
3837
- tool_use_id: "tool-git-commit-lore-inline-unknown",
3838
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
4486
+ tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
4487
+ tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
3839
4488
  },
3840
4489
  { cwd },
3841
4490
  );
3842
4491
 
3843
4492
  assert.equal(result.omxEventName, "pre-tool-use");
3844
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3845
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3846
- } finally {
3847
- await rm(cwd, { recursive: true, force: true });
3848
- }
4493
+ assert.equal(result.outputJson, null);
4494
+ });
3849
4495
  });
3850
4496
 
3851
- it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
4497
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
4498
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
4499
+ try {
4500
+ const result = await dispatchCodexNativeHook(
4501
+ {
4502
+ hook_event_name: "PreToolUse",
4503
+ cwd,
4504
+ tool_name: "Bash",
4505
+ tool_use_id: "tool-git-commit-lore-inline-disabled",
4506
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4507
+ },
4508
+ { cwd },
4509
+ );
4510
+
4511
+ assert.equal(result.omxEventName, "pre-tool-use");
4512
+ assert.equal(result.outputJson, null);
4513
+ } finally {
4514
+ await rm(cwd, { recursive: true, force: true });
4515
+ }
4516
+ });
4517
+
4518
+
4519
+ it("allows inline disabled guard to override an enabled process env", async () => {
4520
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
4521
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4522
+ try {
4523
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4524
+ const result = await dispatchCodexNativeHook(
4525
+ {
4526
+ hook_event_name: "PreToolUse",
4527
+ cwd,
4528
+ tool_name: "Bash",
4529
+ tool_use_id: "tool-git-commit-lore-inline-override-disabled",
4530
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4531
+ },
4532
+ { cwd },
4533
+ );
4534
+
4535
+ assert.equal(result.omxEventName, "pre-tool-use");
4536
+ assert.equal(result.outputJson, null);
4537
+ } finally {
4538
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4539
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4540
+ await rm(cwd, { recursive: true, force: true });
4541
+ }
4542
+ });
4543
+
4544
+ it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
4545
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
4546
+ try {
4547
+ const result = await dispatchCodexNativeHook(
4548
+ {
4549
+ hook_event_name: "PreToolUse",
4550
+ cwd,
4551
+ tool_name: "Bash",
4552
+ tool_use_id: "tool-git-commit-lore-newline-assignment",
4553
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
4554
+ },
4555
+ { cwd },
4556
+ );
4557
+
4558
+ assert.equal(result.omxEventName, "pre-tool-use");
4559
+ assert.equal(result.outputJson, null);
4560
+ } finally {
4561
+ await rm(cwd, { recursive: true, force: true });
4562
+ }
4563
+ });
4564
+
4565
+ it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
4566
+ await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
4567
+ const result = await dispatchCodexNativeHook(
4568
+ {
4569
+ hook_event_name: "PreToolUse",
4570
+ cwd,
4571
+ tool_name: "Bash",
4572
+ tool_use_id: "tool-git-commit-lore-config-env-unset",
4573
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4574
+ },
4575
+ { cwd },
4576
+ );
4577
+
4578
+ assert.equal(result.omxEventName, "pre-tool-use");
4579
+ assert.equal(result.outputJson, null);
4580
+ });
4581
+ });
4582
+
4583
+ it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
4584
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
4585
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4586
+ try {
4587
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4588
+ const result = await dispatchCodexNativeHook(
4589
+ {
4590
+ hook_event_name: "PreToolUse",
4591
+ cwd,
4592
+ tool_name: "Bash",
4593
+ tool_use_id: "tool-git-commit-lore-env-unset",
4594
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4595
+ },
4596
+ { cwd },
4597
+ );
4598
+
4599
+ assert.equal(result.omxEventName, "pre-tool-use");
4600
+ assert.equal(result.outputJson, null);
4601
+ } finally {
4602
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4603
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4604
+ await rm(cwd, { recursive: true, force: true });
4605
+ }
4606
+ });
4607
+
4608
+ it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
4609
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
4610
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4611
+ try {
4612
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4613
+ const result = await dispatchCodexNativeHook(
4614
+ {
4615
+ hook_event_name: "PreToolUse",
4616
+ cwd,
4617
+ tool_name: "Bash",
4618
+ tool_use_id: "tool-git-commit-lore-env-ignore",
4619
+ tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
4620
+ },
4621
+ { cwd },
4622
+ );
4623
+
4624
+ assert.equal(result.omxEventName, "pre-tool-use");
4625
+ assert.equal(result.outputJson, null);
4626
+ } finally {
4627
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4628
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4629
+ await rm(cwd, { recursive: true, force: true });
4630
+ }
4631
+ });
4632
+
4633
+ it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
4634
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
4635
+ try {
4636
+ const result = await dispatchCodexNativeHook(
4637
+ {
4638
+ hook_event_name: "PreToolUse",
4639
+ cwd,
4640
+ tool_name: "Bash",
4641
+ tool_use_id: "tool-git-commit-lore-inline-unknown",
4642
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
4643
+ },
4644
+ { cwd },
4645
+ );
4646
+
4647
+ assert.equal(result.omxEventName, "pre-tool-use");
4648
+ assert.equal(result.outputJson, null);
4649
+ } finally {
4650
+ await rm(cwd, { recursive: true, force: true });
4651
+ }
4652
+ });
4653
+
4654
+ it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
3852
4655
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-"));
3853
4656
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3854
4657
  try {
@@ -3873,7 +4676,7 @@ exit 0
3873
4676
  }
3874
4677
  });
3875
4678
 
3876
- it("keeps Lore commit enforcement enabled for unknown guard values", async () => {
4679
+ it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
3877
4680
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
3878
4681
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3879
4682
  try {
@@ -3890,8 +4693,7 @@ exit 0
3890
4693
  );
3891
4694
 
3892
4695
  assert.equal(result.omxEventName, "pre-tool-use");
3893
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3894
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4696
+ assert.equal(result.outputJson, null);
3895
4697
  } finally {
3896
4698
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3897
4699
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -3997,7 +4799,7 @@ exit 0
3997
4799
  cwd,
3998
4800
  tool_name: "Bash",
3999
4801
  tool_use_id: "tool-git-commit-env-invalid",
4000
- tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
4802
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
4001
4803
  },
4002
4804
  { cwd },
4003
4805
  );
@@ -4032,7 +4834,7 @@ exit 0
4032
4834
  cwd,
4033
4835
  tool_name: "Bash",
4034
4836
  tool_use_id: "tool-git-commit-option-invalid",
4035
- tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
4837
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
4036
4838
  },
4037
4839
  { cwd },
4038
4840
  );
@@ -4067,7 +4869,7 @@ exit 0
4067
4869
  cwd,
4068
4870
  tool_name: "Bash",
4069
4871
  tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
4070
- tool_input: { command: 'env git.exe commit -m "fix tests"' },
4872
+ tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4071
4873
  },
4072
4874
  { cwd },
4073
4875
  );
@@ -4102,7 +4904,7 @@ exit 0
4102
4904
  cwd,
4103
4905
  tool_name: "Bash",
4104
4906
  tool_use_id: "tool-git-exe-commit-invalid",
4105
- tool_input: { command: 'git.exe commit -m "fix tests"' },
4907
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4106
4908
  },
4107
4909
  { cwd },
4108
4910
  );
@@ -4137,7 +4939,7 @@ exit 0
4137
4939
  cwd,
4138
4940
  tool_name: "Bash",
4139
4941
  tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
4140
- tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
4942
+ tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4141
4943
  },
4142
4944
  { cwd },
4143
4945
  );
@@ -4172,7 +4974,7 @@ exit 0
4172
4974
  cwd,
4173
4975
  tool_name: "Bash",
4174
4976
  tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
4175
- tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
4977
+ tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4176
4978
  },
4177
4979
  { cwd },
4178
4980
  );
@@ -4207,7 +5009,7 @@ exit 0
4207
5009
  cwd,
4208
5010
  tool_name: "Bash",
4209
5011
  tool_use_id: "tool-git-exe-commit-windows-path-invalid",
4210
- tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
5012
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
4211
5013
  },
4212
5014
  { cwd },
4213
5015
  );
@@ -4242,7 +5044,7 @@ exit 0
4242
5044
  cwd,
4243
5045
  tool_name: "Bash",
4244
5046
  tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
4245
- tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
5047
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
4246
5048
  },
4247
5049
  { cwd },
4248
5050
  );
@@ -4277,7 +5079,7 @@ exit 0
4277
5079
  cwd,
4278
5080
  tool_name: "Bash",
4279
5081
  tool_use_id: "tool-git-commit-path-invalid",
4280
- tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
5082
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
4281
5083
  },
4282
5084
  { cwd },
4283
5085
  );
@@ -4312,7 +5114,7 @@ exit 0
4312
5114
  cwd,
4313
5115
  tool_name: "Bash",
4314
5116
  tool_use_id: "tool-git-commit-file",
4315
- tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
5117
+ tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
4316
5118
  },
4317
5119
  { cwd },
4318
5120
  );
@@ -4346,7 +5148,7 @@ exit 0
4346
5148
  tool_use_id: "tool-git-commit-missing-omx-coauthor",
4347
5149
  tool_input: {
4348
5150
  command: [
4349
- 'git commit',
5151
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4350
5152
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4351
5153
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
4352
5154
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4386,7 +5188,7 @@ exit 0
4386
5188
  tool_use_id: "tool-git-commit-valid",
4387
5189
  tool_input: {
4388
5190
  command: [
4389
- 'git commit',
5191
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4390
5192
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4391
5193
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
4392
5194
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4416,7 +5218,7 @@ exit 0
4416
5218
  tool_use_id: "tool-git-commit-compact-coauthor",
4417
5219
  tool_input: {
4418
5220
  command: [
4419
- 'git commit',
5221
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4420
5222
  '-m "Launch lvisai.xyz intro site"',
4421
5223
  '-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
4422
5224
  ].join(" "),
@@ -4443,7 +5245,7 @@ exit 0
4443
5245
  tool_use_id: "tool-git-commit-compact-trailers",
4444
5246
  tool_input: {
4445
5247
  command: [
4446
- 'git commit',
5248
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4447
5249
  '-m "Launch lvisai.xyz intro site"',
4448
5250
  '-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4449
5251
  ].join(" "),
@@ -4470,7 +5272,7 @@ exit 0
4470
5272
  tool_use_id: "tool-git-commit-compact-no-separator",
4471
5273
  tool_input: {
4472
5274
  command: [
4473
- 'git commit',
5275
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4474
5276
  '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4475
5277
  ].join(" "),
4476
5278
  },
@@ -4866,6 +5668,99 @@ exit 0
4866
5668
  }
4867
5669
  });
4868
5670
 
5671
+ it("does not treat non-MCP source output containing detector constants as MCP transport death", async () => {
5672
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-read-mcp-source-"));
5673
+ try {
5674
+ const result = await dispatchCodexNativeHook(
5675
+ {
5676
+ hook_event_name: "PostToolUse",
5677
+ cwd,
5678
+ tool_name: "Read",
5679
+ tool_use_id: "tool-read-mcp-source",
5680
+ tool_input: { file_path: "src/scripts/codex-native-pre-post.ts" },
5681
+ tool_response:
5682
+ "const MCP_TRANSPORT_FAILURE_PATTERNS = [/transport closed/i, /server disconnected/i];\nconst context = /\\bmcp\\b/i;",
5683
+ },
5684
+ { cwd },
5685
+ );
5686
+
5687
+ assert.equal(result.omxEventName, "post-tool-use");
5688
+ assert.equal(result.outputJson, null);
5689
+ } finally {
5690
+ await rm(cwd, { recursive: true, force: true });
5691
+ }
5692
+ });
5693
+
5694
+ it("does not treat non-MCP docs stdout mentioning closed MCP transport as transport death", async () => {
5695
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-docs-mcp-log-"));
5696
+ try {
5697
+ const result = await dispatchCodexNativeHook(
5698
+ {
5699
+ hook_event_name: "PostToolUse",
5700
+ cwd,
5701
+ tool_name: "ShellOutput",
5702
+ tool_use_id: "tool-docs-mcp-log",
5703
+ tool_response: JSON.stringify({
5704
+ stdout: "Troubleshooting note: MCP transport closed after the server disconnected in an old log.",
5705
+ stderr: "",
5706
+ }),
5707
+ },
5708
+ { cwd },
5709
+ );
5710
+
5711
+ assert.equal(result.omxEventName, "post-tool-use");
5712
+ assert.equal(result.outputJson, null);
5713
+ } finally {
5714
+ await rm(cwd, { recursive: true, force: true });
5715
+ }
5716
+ });
5717
+
5718
+ it("does not MCP-block non-MCP command output with unrelated stderr and MCP transport stdout", async () => {
5719
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-nonmcp-mixed-output-"));
5720
+ try {
5721
+ const result = await dispatchCodexNativeHook(
5722
+ {
5723
+ hook_event_name: "PostToolUse",
5724
+ cwd,
5725
+ tool_name: "ShellOutput",
5726
+ tool_use_id: "tool-nonmcp-mixed-output",
5727
+ tool_response: JSON.stringify({
5728
+ stdout: "captured log line: MCP transport closed before response",
5729
+ stderr: "grep: fixture.txt: No such file or directory",
5730
+ }),
5731
+ },
5732
+ { cwd },
5733
+ );
5734
+
5735
+ assert.equal(result.omxEventName, "post-tool-use");
5736
+ assert.equal(result.outputJson, null);
5737
+ } finally {
5738
+ await rm(cwd, { recursive: true, force: true });
5739
+ }
5740
+ });
5741
+
5742
+ it("still blocks MCP-like raw transport failures", async () => {
5743
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-raw-transport-"));
5744
+ try {
5745
+ const result = await dispatchCodexNativeHook(
5746
+ {
5747
+ hook_event_name: "PostToolUse",
5748
+ cwd,
5749
+ tool_name: "mcp__omx_state__state_write",
5750
+ tool_use_id: "tool-mcp-raw-transport",
5751
+ tool_response: "transport closed after server disconnected",
5752
+ },
5753
+ { cwd },
5754
+ );
5755
+
5756
+ assert.equal(result.omxEventName, "post-tool-use");
5757
+ assert.equal(result.outputJson?.decision, "block");
5758
+ assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/);
5759
+ } finally {
5760
+ await rm(cwd, { recursive: true, force: true });
5761
+ }
5762
+ });
5763
+
4869
5764
  it("returns PostToolUse MCP transport fallback guidance for clear MCP transport death", async () => {
4870
5765
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-mcp-transport-"));
4871
5766
  try {
@@ -5313,6 +6208,61 @@ exit 0
5313
6208
  }
5314
6209
  });
5315
6210
 
6211
+ it("requires Autopilot code review after a compact-boundary Stop exemption", async () => {
6212
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-review-compact-"));
6213
+ try {
6214
+ const stateDir = join(cwd, ".omx", "state");
6215
+ const sessionId = "sess-stop-autopilot-review-compact";
6216
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6217
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6218
+ active: true,
6219
+ mode: "autopilot",
6220
+ current_phase: "code-review",
6221
+ state: {
6222
+ phase_cycle: ["ralplan", "ralph", "code-review"],
6223
+ handoff_artifacts: {
6224
+ ralplan: ".omx/plans/prd-issue-2366.md",
6225
+ ralph: { verification: ["npm test"] },
6226
+ code_review: null,
6227
+ },
6228
+ review_verdict: null,
6229
+ },
6230
+ });
6231
+
6232
+ const compactBoundary = await dispatchCodexNativeHook(
6233
+ {
6234
+ hook_event_name: "Stop",
6235
+ cwd,
6236
+ session_id: sessionId,
6237
+ stop_reason: "context compact",
6238
+ },
6239
+ { cwd },
6240
+ );
6241
+ const resumedStop = await dispatchCodexNativeHook(
6242
+ {
6243
+ hook_event_name: "Stop",
6244
+ cwd,
6245
+ session_id: sessionId,
6246
+ },
6247
+ { cwd },
6248
+ );
6249
+
6250
+ assert.equal(compactBoundary.omxEventName, "stop");
6251
+ assert.equal(compactBoundary.outputJson, null);
6252
+ assert.equal(resumedStop.omxEventName, "stop");
6253
+ assert.deepEqual(resumedStop.outputJson, {
6254
+ decision: "block",
6255
+ reason:
6256
+ "OMX autopilot is still active (phase: code-review); continue the task and gather fresh verification evidence before stopping.",
6257
+ stopReason: "autopilot_code-review",
6258
+ systemMessage:
6259
+ "OMX autopilot is still active (phase: code-review). Run the required $code-review step before completing or clearing Autopilot state.",
6260
+ });
6261
+ } finally {
6262
+ await rm(cwd, { recursive: true, force: true });
6263
+ }
6264
+ });
6265
+
5316
6266
  it("suppresses duplicate Autopilot planning Stop replays so stale planning state cannot loop indefinitely", async () => {
5317
6267
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-planning-replay-"));
5318
6268
  try {
@@ -5355,6 +6305,91 @@ exit 0
5355
6305
  }
5356
6306
  });
5357
6307
 
6308
+ it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
6309
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
6310
+ try {
6311
+ const stateDir = join(cwd, ".omx", "state");
6312
+ const sessionId = "sess-stop-autopilot-terminal-run-state";
6313
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6314
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6315
+ active: true,
6316
+ mode: "autopilot",
6317
+ current_phase: "ralplan",
6318
+ });
6319
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6320
+ version: 1,
6321
+ active: false,
6322
+ mode: "autopilot",
6323
+ outcome: "finish",
6324
+ lifecycle_outcome: "finished",
6325
+ current_phase: "complete",
6326
+ completed_at: "2026-05-20T11:00:00.000Z",
6327
+ updated_at: "2026-05-20T11:00:00.000Z",
6328
+ });
6329
+
6330
+ const result = await dispatchCodexNativeHook(
6331
+ {
6332
+ hook_event_name: "Stop",
6333
+ cwd,
6334
+ session_id: sessionId,
6335
+ thread_id: "thread-stop-autopilot-terminal-run-state",
6336
+ turn_id: "turn-stop-autopilot-terminal-run-state-1",
6337
+ last_assistant_message: "Done. Verification passed.",
6338
+ },
6339
+ { cwd },
6340
+ );
6341
+
6342
+ assert.equal(result.omxEventName, "stop");
6343
+ assert.equal(result.outputJson, null);
6344
+ } finally {
6345
+ await rm(cwd, { recursive: true, force: true });
6346
+ }
6347
+ });
6348
+
6349
+ it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
6350
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
6351
+ try {
6352
+ const stateDir = join(cwd, ".omx", "state");
6353
+ const sessionId = "sess-stop-autopilot-active-ralplan";
6354
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6355
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6356
+ active: true,
6357
+ mode: "autopilot",
6358
+ current_phase: "ralplan",
6359
+ });
6360
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6361
+ version: 1,
6362
+ active: true,
6363
+ mode: "autopilot",
6364
+ outcome: "continue",
6365
+ current_phase: "ralplan",
6366
+ updated_at: "2026-05-20T11:00:00.000Z",
6367
+ });
6368
+
6369
+ const result = await dispatchCodexNativeHook(
6370
+ {
6371
+ hook_event_name: "Stop",
6372
+ cwd,
6373
+ session_id: sessionId,
6374
+ thread_id: "thread-stop-autopilot-active-ralplan",
6375
+ turn_id: "turn-stop-autopilot-active-ralplan-1",
6376
+ },
6377
+ { cwd },
6378
+ );
6379
+
6380
+ assert.equal(result.omxEventName, "stop");
6381
+ assert.deepEqual(result.outputJson, {
6382
+ decision: "block",
6383
+ reason:
6384
+ "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
6385
+ stopReason: "autopilot_ralplan",
6386
+ systemMessage: "OMX autopilot is still active (phase: ralplan).",
6387
+ });
6388
+ } finally {
6389
+ await rm(cwd, { recursive: true, force: true });
6390
+ }
6391
+ });
6392
+
5358
6393
  it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
5359
6394
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
5360
6395
  try {
@@ -5913,6 +6948,98 @@ exit 0
5913
6948
  }
5914
6949
  });
5915
6950
 
6951
+ it("queues worker Stop leader nudge with Tab and submit when leader pane is busy", async () => {
6952
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-busy-leader-"));
6953
+ const prevTeamWorker = process.env.OMX_TEAM_WORKER;
6954
+ const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
6955
+ const prevPath = process.env.PATH;
6956
+ try {
6957
+ await initTeamState(
6958
+ "worker-stop-team-busy-leader",
6959
+ "worker stop busy leader",
6960
+ "executor",
6961
+ 1,
6962
+ cwd,
6963
+ undefined,
6964
+ { ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-busy-leader" },
6965
+ );
6966
+ const fakeBinDir = join(cwd, "fake-bin");
6967
+ const tmuxLogPath = join(cwd, "tmux.log");
6968
+ await mkdir(fakeBinDir, { recursive: true });
6969
+ await writeFile(join(fakeBinDir, "tmux"), buildWorkerStopFakeTmux(tmuxLogPath, { busyLeader: true }));
6970
+ await chmod(join(fakeBinDir, "tmux"), 0o755);
6971
+ const stateDir = join(cwd, ".omx", "state");
6972
+ const teamDir = join(stateDir, "team", "worker-stop-team-busy-leader");
6973
+ const workerDir = join(teamDir, "workers", "worker-1");
6974
+ await writeJson(join(teamDir, "config.json"), {
6975
+ name: "worker-stop-team-busy-leader",
6976
+ tmux_session: "omx-team-worker-stop",
6977
+ leader_pane_id: "%42",
6978
+ workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
6979
+ });
6980
+ await writeJson(join(teamDir, "manifest.v2.json"), {
6981
+ name: "worker-stop-team-busy-leader",
6982
+ tmux_session: "omx-team-worker-stop",
6983
+ leader_pane_id: "%42",
6984
+ workers: [{ name: "worker-1", index: 1, pane_id: "%10" }],
6985
+ });
6986
+ await writeJson(join(workerDir, "identity.json"), {
6987
+ name: "worker-1",
6988
+ index: 1,
6989
+ role: "executor",
6990
+ assigned_tasks: ["1"],
6991
+ worktree_path: cwd,
6992
+ team_state_root: stateDir,
6993
+ });
6994
+ await writeJson(join(workerDir, "status.json"), {
6995
+ state: "done",
6996
+ current_task_id: "1",
6997
+ updated_at: new Date().toISOString(),
6998
+ });
6999
+ await writeJson(join(teamDir, "tasks", "task-1.json"), {
7000
+ id: "1",
7001
+ subject: "hook task",
7002
+ description: "finish hook task",
7003
+ status: "completed",
7004
+ owner: "worker-1",
7005
+ created_at: new Date().toISOString(),
7006
+ });
7007
+
7008
+ process.env.OMX_TEAM_WORKER = "worker-stop-team-busy-leader/worker-1";
7009
+ process.env.OMX_TEAM_STATE_ROOT = stateDir;
7010
+ process.env.PATH = `${fakeBinDir}:${prevPath || ""}`;
7011
+
7012
+ const result = await dispatchCodexNativeHook(
7013
+ {
7014
+ hook_event_name: "Stop",
7015
+ cwd,
7016
+ session_id: "sess-stop-team-worker-busy-leader",
7017
+ },
7018
+ { cwd },
7019
+ );
7020
+
7021
+ assert.equal(result.outputJson, null);
7022
+ const tmuxLog = await readFile(tmuxLogPath, "utf-8");
7023
+ assert.match(tmuxLog, /send-keys -t %42 -l \[OMX\] worker-1 native Stop allowed/);
7024
+ assert.match(tmuxLog, /send-keys -t %42 Tab/);
7025
+ assert.match(tmuxLog, /send-keys -t %42 C-m/);
7026
+ assert.ok(
7027
+ tmuxLog.indexOf("send-keys -t %42 Tab") < tmuxLog.indexOf("send-keys -t %42 C-m"),
7028
+ "busy worker-stop nudge should press Tab before C-m",
7029
+ );
7030
+ const nudgeState = JSON.parse(await readFile(join(workerDir, "worker-stop-nudge.json"), "utf-8"));
7031
+ assert.equal(nudgeState.delivery, "queued");
7032
+ } finally {
7033
+ if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker;
7034
+ else delete process.env.OMX_TEAM_WORKER;
7035
+ if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
7036
+ else delete process.env.OMX_TEAM_STATE_ROOT;
7037
+ if (typeof prevPath === "string") process.env.PATH = prevPath;
7038
+ else delete process.env.PATH;
7039
+ await rm(cwd, { recursive: true, force: true });
7040
+ }
7041
+ });
7042
+
5916
7043
  it("allows worker Stop when the Stop nudge helper cannot deliver", async () => {
5917
7044
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-helper-fail-"));
5918
7045
  const prevTeamWorker = process.env.OMX_TEAM_WORKER;
@@ -6843,13 +7970,25 @@ exit 0
6843
7970
  }
6844
7971
  });
6845
7972
 
6846
- it("does not block on stale ralplan skill-active when canonical run-state is terminal", async () => {
6847
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-terminal-ralplan-run-"));
7973
+ it("does not block when canonical root ralplan state is inactive but session ralplan state is stale active", async () => {
7974
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralplan-root-inactive-"));
6848
7975
  try {
6849
7976
  const stateDir = join(cwd, ".omx", "state");
6850
- const sessionId = "sess-stop-terminal-ralplan";
7977
+ const sessionId = "sess-stop-stale-session-ralplan";
6851
7978
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6852
7979
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
7980
+ await writeJson(join(stateDir, "skill-active-state.json"), {
7981
+ active: false,
7982
+ skill: "ralplan",
7983
+ phase: "reviewing",
7984
+ active_skills: [],
7985
+ });
7986
+ await writeJson(join(stateDir, "ralplan-state.json"), {
7987
+ active: false,
7988
+ mode: "ralplan",
7989
+ current_phase: "complete",
7990
+ session_id: sessionId,
7991
+ });
6853
7992
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
6854
7993
  active: true,
6855
7994
  skill: "ralplan",
@@ -6868,16 +8007,6 @@ exit 0
6868
8007
  current_phase: "planning",
6869
8008
  session_id: sessionId,
6870
8009
  });
6871
- await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6872
- version: 1,
6873
- mode: "ralplan",
6874
- active: false,
6875
- outcome: "finish",
6876
- lifecycle_outcome: "finished",
6877
- current_phase: "complete",
6878
- completed_at: "2026-05-01T00:00:00.000Z",
6879
- updated_at: "2026-05-01T00:00:00.000Z",
6880
- });
6881
8010
 
6882
8011
  const result = await dispatchCodexNativeHook(
6883
8012
  {
@@ -6895,13 +8024,26 @@ exit 0
6895
8024
  }
6896
8025
  });
6897
8026
 
6898
- it("does not block on stale ralplan skill-active when pinned mode state belongs to another session", async () => {
6899
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-ralplan-"));
8027
+ it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => {
8028
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-"));
6900
8029
  try {
6901
8030
  const stateDir = join(cwd, ".omx", "state");
6902
- const sessionId = "sess-stop-current-ralplan";
8031
+ const sessionId = "sess-stop-current-active-ralplan";
6903
8032
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6904
8033
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
8034
+ await writeJson(join(stateDir, "skill-active-state.json"), {
8035
+ active: false,
8036
+ skill: "ralplan",
8037
+ phase: "complete",
8038
+ session_id: "sess-stop-old-ralplan",
8039
+ active_skills: [],
8040
+ });
8041
+ await writeJson(join(stateDir, "ralplan-state.json"), {
8042
+ active: false,
8043
+ mode: "ralplan",
8044
+ current_phase: "complete",
8045
+ session_id: "sess-stop-old-ralplan",
8046
+ });
6905
8047
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
6906
8048
  active: true,
6907
8049
  skill: "ralplan",
@@ -6918,7 +8060,7 @@ exit 0
6918
8060
  active: true,
6919
8061
  mode: "ralplan",
6920
8062
  current_phase: "planning",
6921
- session_id: "sess-other-ralplan",
8063
+ session_id: sessionId,
6922
8064
  });
6923
8065
 
6924
8066
  const result = await dispatchCodexNativeHook(
@@ -6931,85 +8073,349 @@ exit 0
6931
8073
  );
6932
8074
 
6933
8075
  assert.equal(result.omxEventName, "stop");
6934
- assert.equal(result.outputJson, null);
8076
+ assert.equal(result.outputJson?.decision, "block");
8077
+ assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
6935
8078
  } finally {
6936
8079
  await rm(cwd, { recursive: true, force: true });
6937
8080
  }
6938
8081
  });
6939
8082
 
6940
- it("returns an explicit ralplan waiting status while subagents are still active", async () => {
6941
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
8083
+ it("keeps blocking current session ralplan when root inactive ralplan state is unscoped", async () => {
8084
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-unscoped-"));
6942
8085
  try {
6943
8086
  const stateDir = join(cwd, ".omx", "state");
6944
- await mkdir(join(stateDir, "sessions", "sess-stop-skill-subagent"), { recursive: true });
6945
- await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-subagent" });
6946
- await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "skill-active-state.json"), {
8087
+ const sessionId = "sess-stop-unscoped-root-current-active";
8088
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
8089
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
8090
+ await writeJson(join(stateDir, "skill-active-state.json"), {
8091
+ active: false,
8092
+ skill: "ralplan",
8093
+ phase: "complete",
8094
+ active_skills: [],
8095
+ });
8096
+ await writeJson(join(stateDir, "ralplan-state.json"), {
8097
+ active: false,
8098
+ mode: "ralplan",
8099
+ current_phase: "complete",
8100
+ });
8101
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
6947
8102
  active: true,
6948
8103
  skill: "ralplan",
6949
8104
  phase: "planning",
8105
+ session_id: sessionId,
8106
+ active_skills: [{
8107
+ skill: "ralplan",
8108
+ phase: "planning",
8109
+ active: true,
8110
+ session_id: sessionId,
8111
+ }],
6950
8112
  });
6951
- await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "ralplan-state.json"), {
8113
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
6952
8114
  active: true,
8115
+ mode: "ralplan",
6953
8116
  current_phase: "planning",
6954
- });
6955
- await writeJson(join(stateDir, "subagent-tracking.json"), {
6956
- schemaVersion: 1,
6957
- sessions: {
6958
- "sess-stop-skill-subagent": {
6959
- session_id: "sess-stop-skill-subagent",
6960
- leader_thread_id: "leader-1",
6961
- updated_at: new Date().toISOString(),
6962
- threads: {
6963
- "leader-1": {
6964
- thread_id: "leader-1",
6965
- kind: "leader",
6966
- first_seen_at: new Date().toISOString(),
6967
- last_seen_at: new Date().toISOString(),
6968
- turn_count: 1,
6969
- },
6970
- "sub-1": {
6971
- thread_id: "sub-1",
6972
- kind: "subagent",
6973
- first_seen_at: new Date().toISOString(),
6974
- last_seen_at: new Date().toISOString(),
6975
- turn_count: 1,
6976
- },
6977
- },
6978
- },
6979
- },
8117
+ session_id: sessionId,
6980
8118
  });
6981
8119
 
6982
8120
  const result = await dispatchCodexNativeHook(
6983
8121
  {
6984
8122
  hook_event_name: "Stop",
6985
8123
  cwd,
6986
- session_id: "sess-stop-skill-subagent",
8124
+ session_id: sessionId,
6987
8125
  },
6988
8126
  { cwd },
6989
8127
  );
6990
8128
 
6991
8129
  assert.equal(result.omxEventName, "stop");
6992
8130
  assert.equal(result.outputJson?.decision, "block");
6993
- assert.match(String(result.outputJson?.reason ?? ""), /Status: waiting/);
6994
- assert.match(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/);
6995
- assert.match(String(result.outputJson?.reason ?? ""), /then continue from the current ralplan artifact/i);
6996
- assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_waiting_subagent");
8131
+ assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
6997
8132
  } finally {
6998
8133
  await rm(cwd, { recursive: true, force: true });
6999
8134
  }
7000
8135
  });
7001
8136
 
7002
- it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => {
7003
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-"));
8137
+ it("does not block stale session ralplan when root ralplan is terminal and another root skill is active", async () => {
8138
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-ralplan-other-root-skill-"));
7004
8139
  try {
7005
8140
  const stateDir = join(cwd, ".omx", "state");
7006
- await mkdir(stateDir, { recursive: true });
8141
+ const sessionId = "sess-stop-stale-ralplan-other-root-skill";
8142
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
8143
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
7007
8144
  await writeJson(join(stateDir, "skill-active-state.json"), {
8145
+ active: true,
8146
+ skill: "deep-interview",
8147
+ phase: "intent-first",
8148
+ session_id: sessionId,
8149
+ active_skills: [{
8150
+ skill: "deep-interview",
8151
+ phase: "intent-first",
8152
+ active: true,
8153
+ session_id: sessionId,
8154
+ }],
8155
+ });
8156
+ await writeJson(join(stateDir, "ralplan-state.json"), {
8157
+ active: false,
8158
+ mode: "ralplan",
8159
+ current_phase: "complete",
8160
+ session_id: sessionId,
8161
+ });
8162
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
7008
8163
  active: true,
7009
8164
  skill: "ralplan",
7010
8165
  phase: "planning",
7011
- });
7012
-
8166
+ session_id: sessionId,
8167
+ active_skills: [{
8168
+ skill: "ralplan",
8169
+ phase: "planning",
8170
+ active: true,
8171
+ session_id: sessionId,
8172
+ }],
8173
+ });
8174
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
8175
+ active: true,
8176
+ mode: "ralplan",
8177
+ current_phase: "planning",
8178
+ session_id: sessionId,
8179
+ });
8180
+
8181
+ const result = await dispatchCodexNativeHook(
8182
+ {
8183
+ hook_event_name: "Stop",
8184
+ cwd,
8185
+ session_id: sessionId,
8186
+ },
8187
+ { cwd },
8188
+ );
8189
+
8190
+ assert.equal(result.omxEventName, "stop");
8191
+ assert.equal(result.outputJson, null);
8192
+ } finally {
8193
+ await rm(cwd, { recursive: true, force: true });
8194
+ }
8195
+ });
8196
+
8197
+ it("keeps blocking session ralplan when canonical root state is not inactive", async () => {
8198
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-active-"));
8199
+ try {
8200
+ const stateDir = join(cwd, ".omx", "state");
8201
+ const sessionId = "sess-stop-session-ralplan-root-active";
8202
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
8203
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
8204
+ await writeJson(join(stateDir, "skill-active-state.json"), {
8205
+ active: true,
8206
+ skill: "ralplan",
8207
+ phase: "planning",
8208
+ session_id: sessionId,
8209
+ active_skills: [{
8210
+ skill: "ralplan",
8211
+ phase: "planning",
8212
+ active: true,
8213
+ session_id: sessionId,
8214
+ }],
8215
+ });
8216
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
8217
+ active: true,
8218
+ skill: "ralplan",
8219
+ phase: "planning",
8220
+ session_id: sessionId,
8221
+ active_skills: [{
8222
+ skill: "ralplan",
8223
+ phase: "planning",
8224
+ active: true,
8225
+ session_id: sessionId,
8226
+ }],
8227
+ });
8228
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
8229
+ active: true,
8230
+ mode: "ralplan",
8231
+ current_phase: "planning",
8232
+ session_id: sessionId,
8233
+ });
8234
+
8235
+ const result = await dispatchCodexNativeHook(
8236
+ {
8237
+ hook_event_name: "Stop",
8238
+ cwd,
8239
+ session_id: sessionId,
8240
+ },
8241
+ { cwd },
8242
+ );
8243
+
8244
+ assert.equal(result.omxEventName, "stop");
8245
+ assert.equal(result.outputJson?.decision, "block");
8246
+ assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
8247
+ } finally {
8248
+ await rm(cwd, { recursive: true, force: true });
8249
+ }
8250
+ });
8251
+
8252
+ it("does not block on stale ralplan skill-active when canonical run-state is terminal", async () => {
8253
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-terminal-ralplan-run-"));
8254
+ try {
8255
+ const stateDir = join(cwd, ".omx", "state");
8256
+ const sessionId = "sess-stop-terminal-ralplan";
8257
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
8258
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
8259
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
8260
+ active: true,
8261
+ skill: "ralplan",
8262
+ phase: "planning",
8263
+ session_id: sessionId,
8264
+ active_skills: [{
8265
+ skill: "ralplan",
8266
+ phase: "planning",
8267
+ active: true,
8268
+ session_id: sessionId,
8269
+ }],
8270
+ });
8271
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
8272
+ active: true,
8273
+ mode: "ralplan",
8274
+ current_phase: "planning",
8275
+ session_id: sessionId,
8276
+ });
8277
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
8278
+ version: 1,
8279
+ mode: "ralplan",
8280
+ active: false,
8281
+ outcome: "finish",
8282
+ lifecycle_outcome: "finished",
8283
+ current_phase: "complete",
8284
+ completed_at: "2026-05-01T00:00:00.000Z",
8285
+ updated_at: "2026-05-01T00:00:00.000Z",
8286
+ });
8287
+
8288
+ const result = await dispatchCodexNativeHook(
8289
+ {
8290
+ hook_event_name: "Stop",
8291
+ cwd,
8292
+ session_id: sessionId,
8293
+ },
8294
+ { cwd },
8295
+ );
8296
+
8297
+ assert.equal(result.omxEventName, "stop");
8298
+ assert.equal(result.outputJson, null);
8299
+ } finally {
8300
+ await rm(cwd, { recursive: true, force: true });
8301
+ }
8302
+ });
8303
+
8304
+ it("does not block on stale ralplan skill-active when pinned mode state belongs to another session", async () => {
8305
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-ralplan-"));
8306
+ try {
8307
+ const stateDir = join(cwd, ".omx", "state");
8308
+ const sessionId = "sess-stop-current-ralplan";
8309
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
8310
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
8311
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
8312
+ active: true,
8313
+ skill: "ralplan",
8314
+ phase: "planning",
8315
+ session_id: sessionId,
8316
+ active_skills: [{
8317
+ skill: "ralplan",
8318
+ phase: "planning",
8319
+ active: true,
8320
+ session_id: sessionId,
8321
+ }],
8322
+ });
8323
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
8324
+ active: true,
8325
+ mode: "ralplan",
8326
+ current_phase: "planning",
8327
+ session_id: "sess-other-ralplan",
8328
+ });
8329
+
8330
+ const result = await dispatchCodexNativeHook(
8331
+ {
8332
+ hook_event_name: "Stop",
8333
+ cwd,
8334
+ session_id: sessionId,
8335
+ },
8336
+ { cwd },
8337
+ );
8338
+
8339
+ assert.equal(result.omxEventName, "stop");
8340
+ assert.equal(result.outputJson, null);
8341
+ } finally {
8342
+ await rm(cwd, { recursive: true, force: true });
8343
+ }
8344
+ });
8345
+
8346
+ it("returns an explicit ralplan waiting status while subagents are still active", async () => {
8347
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
8348
+ try {
8349
+ const stateDir = join(cwd, ".omx", "state");
8350
+ await mkdir(join(stateDir, "sessions", "sess-stop-skill-subagent"), { recursive: true });
8351
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill-subagent" });
8352
+ await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "skill-active-state.json"), {
8353
+ active: true,
8354
+ skill: "ralplan",
8355
+ phase: "planning",
8356
+ });
8357
+ await writeJson(join(stateDir, "sessions", "sess-stop-skill-subagent", "ralplan-state.json"), {
8358
+ active: true,
8359
+ current_phase: "planning",
8360
+ });
8361
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
8362
+ schemaVersion: 1,
8363
+ sessions: {
8364
+ "sess-stop-skill-subagent": {
8365
+ session_id: "sess-stop-skill-subagent",
8366
+ leader_thread_id: "leader-1",
8367
+ updated_at: new Date().toISOString(),
8368
+ threads: {
8369
+ "leader-1": {
8370
+ thread_id: "leader-1",
8371
+ kind: "leader",
8372
+ first_seen_at: new Date().toISOString(),
8373
+ last_seen_at: new Date().toISOString(),
8374
+ turn_count: 1,
8375
+ },
8376
+ "sub-1": {
8377
+ thread_id: "sub-1",
8378
+ kind: "subagent",
8379
+ first_seen_at: new Date().toISOString(),
8380
+ last_seen_at: new Date().toISOString(),
8381
+ turn_count: 1,
8382
+ },
8383
+ },
8384
+ },
8385
+ },
8386
+ });
8387
+
8388
+ const result = await dispatchCodexNativeHook(
8389
+ {
8390
+ hook_event_name: "Stop",
8391
+ cwd,
8392
+ session_id: "sess-stop-skill-subagent",
8393
+ },
8394
+ { cwd },
8395
+ );
8396
+
8397
+ assert.equal(result.omxEventName, "stop");
8398
+ assert.equal(result.outputJson?.decision, "block");
8399
+ assert.match(String(result.outputJson?.reason ?? ""), /Status: waiting/);
8400
+ assert.match(String(result.outputJson?.reason ?? ""), /waiting for 1 active native subagent thread/);
8401
+ assert.match(String(result.outputJson?.reason ?? ""), /then continue from the current ralplan artifact/i);
8402
+ assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_waiting_subagent");
8403
+ } finally {
8404
+ await rm(cwd, { recursive: true, force: true });
8405
+ }
8406
+ });
8407
+
8408
+ it("does not block on stale root ralplan skill when the explicit session-scoped canonical skill state is absent", async () => {
8409
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-skill-"));
8410
+ try {
8411
+ const stateDir = join(cwd, ".omx", "state");
8412
+ await mkdir(stateDir, { recursive: true });
8413
+ await writeJson(join(stateDir, "skill-active-state.json"), {
8414
+ active: true,
8415
+ skill: "ralplan",
8416
+ phase: "planning",
8417
+ });
8418
+
7013
8419
  const result = await dispatchCodexNativeHook(
7014
8420
  {
7015
8421
  hook_event_name: "Stop",
@@ -7748,16 +9154,29 @@ exit 0
7748
9154
  assert.equal(result.omxEventName, "stop");
7749
9155
  const reason = String(result.outputJson?.reason);
7750
9156
  assert.match(reason, /Ralph completion audit is missing required evidence/);
7751
- assert.match(reason, /state\.completion_audit = \{ passed: true, prompt_to_artifact_checklist: \[\.\.\.\], verification_evidence: \[\.\.\.\] \}/);
9157
+ assert.match(reason, /set "completion_audit" on the Ralph state object/);
9158
+ assert.doesNotMatch(reason, /state\.completion_audit/);
7752
9159
  assert.match(reason, /repo-relative JSON file/);
7753
9160
  assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
7754
9161
  assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
7755
9162
  const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
7756
- assert.equal(reopened.active, true);
7757
- assert.equal(reopened.current_phase, "verifying");
9163
+ assert.equal(reopened.active, false);
9164
+ assert.equal(reopened.current_phase, "complete");
7758
9165
  assert.equal(reopened.completion_audit_gate, "blocked");
7759
9166
  assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
7760
- assert.equal(typeof reopened.completed_at, "undefined");
9167
+ assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
9168
+
9169
+ const repeat = await dispatchCodexNativeHook(
9170
+ {
9171
+ hook_event_name: "Stop",
9172
+ cwd,
9173
+ session_id: sessionId,
9174
+ last_assistant_message: "Done. Ralph complete.",
9175
+ },
9176
+ { cwd },
9177
+ );
9178
+ assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
9179
+ assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
7761
9180
  } finally {
7762
9181
  await rm(cwd, { recursive: true, force: true });
7763
9182
  }
@@ -7985,6 +9404,47 @@ exit 0
7985
9404
  }
7986
9405
  });
7987
9406
 
9407
+ it("allows Stop from stale orphaned session-scoped Ralph starting iteration zero state", async () => {
9408
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-orphan-starting-ralph-"));
9409
+ try {
9410
+ const stateDir = join(cwd, ".omx", "state");
9411
+ const sessionId = "sess-stale-orphan-ralph";
9412
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
9413
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd });
9414
+ await writeJson(join(stateDir, "sessions", sessionId, "ralph-state.json"), {
9415
+ active: true,
9416
+ mode: "ralph",
9417
+ current_phase: "starting",
9418
+ iteration: 0,
9419
+ session_id: sessionId,
9420
+ updated_at: "2000-01-01T00:00:00.000Z",
9421
+ });
9422
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
9423
+ active: true,
9424
+ skill: "ralph",
9425
+ phase: "starting",
9426
+ session_id: sessionId,
9427
+ active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: sessionId }],
9428
+ });
9429
+
9430
+ const result = await dispatchCodexNativeHook(
9431
+ {
9432
+ hook_event_name: "Stop",
9433
+ cwd,
9434
+ session_id: sessionId,
9435
+ thread_id: "thread-verifier-terminal",
9436
+ last_assistant_message: "APPROVE: read-only verifier evidence is fresh and sufficient.",
9437
+ },
9438
+ { cwd },
9439
+ );
9440
+
9441
+ assert.equal(result.omxEventName, "stop");
9442
+ assert.equal(result.outputJson, null);
9443
+ } finally {
9444
+ await rm(cwd, { recursive: true, force: true });
9445
+ }
9446
+ });
9447
+
7988
9448
  it("blocks Stop on visible active session-scoped Ralph starting state and reports its path", async () => {
7989
9449
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-visible-starting-ralph-"));
7990
9450
  try {
@@ -8027,6 +9487,138 @@ exit 0
8027
9487
  }
8028
9488
  });
8029
9489
 
9490
+ it("retires prompt-seeded Ralph starting state when canonical Ralph already completed with audit", async () => {
9491
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-shadowed-starting-"));
9492
+ try {
9493
+ const stateDir = join(cwd, ".omx", "state");
9494
+ const nativeSessionId = "native-hook-seed";
9495
+ const canonicalSessionId = "omx-runtime-session";
9496
+ await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true });
9497
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
9498
+ await writeJson(join(stateDir, "session.json"), {
9499
+ session_id: canonicalSessionId,
9500
+ cwd,
9501
+ });
9502
+ await writeJson(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), {
9503
+ active: true,
9504
+ mode: "ralph",
9505
+ current_phase: "starting",
9506
+ session_id: nativeSessionId,
9507
+ iteration: 0,
9508
+ task_slug: "mvp-h-local-method-preflight-execution",
9509
+ started_at: "2026-05-14T07:00:00.000Z",
9510
+ });
9511
+ await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), {
9512
+ active: true,
9513
+ skill: "ralph",
9514
+ phase: "starting",
9515
+ session_id: nativeSessionId,
9516
+ active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: nativeSessionId }],
9517
+ });
9518
+ await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), {
9519
+ active: false,
9520
+ mode: "ralph",
9521
+ current_phase: "complete",
9522
+ session_id: canonicalSessionId,
9523
+ completed_at: "2026-05-14T07:30:00.000Z",
9524
+ completion_audit: {
9525
+ passed: true,
9526
+ prompt_to_artifact_checklist: ["task evidence mapped"],
9527
+ verification_evidence: ["fresh verification evidence recorded"],
9528
+ },
9529
+ });
9530
+
9531
+ const result = await dispatchCodexNativeHook(
9532
+ {
9533
+ hook_event_name: "Stop",
9534
+ cwd,
9535
+ session_id: nativeSessionId,
9536
+ },
9537
+ { cwd },
9538
+ );
9539
+
9540
+ assert.equal(result.omxEventName, "stop");
9541
+ assert.equal(result.outputJson, null);
9542
+ const retiredState = JSON.parse(await readFile(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), "utf-8"));
9543
+ assert.equal(retiredState.active, false);
9544
+ assert.equal(retiredState.current_phase, "complete");
9545
+ assert.equal(retiredState.stop_reason, "shadowed_by_completed_canonical_ralph");
9546
+ assert.equal(retiredState.shadowed_by_completed_canonical_ralph.session_id, canonicalSessionId);
9547
+ } finally {
9548
+ await rm(cwd, { recursive: true, force: true });
9549
+ }
9550
+ });
9551
+
9552
+ it("does not retire prompt-seeded Ralph starting state from a completed canonical Ralph owned by another thread", async () => {
9553
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-shadowed-thread-mismatch-"));
9554
+ try {
9555
+ const stateDir = join(cwd, ".omx", "state");
9556
+ const nativeSessionId = "native-hook-seed";
9557
+ const canonicalSessionId = "omx-runtime-session";
9558
+ await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true });
9559
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
9560
+ await writeJson(join(stateDir, "session.json"), {
9561
+ session_id: canonicalSessionId,
9562
+ cwd,
9563
+ });
9564
+ await writeJson(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), {
9565
+ active: true,
9566
+ mode: "ralph",
9567
+ current_phase: "starting",
9568
+ session_id: nativeSessionId,
9569
+ iteration: 0,
9570
+ task_slug: "mvp-h-local-method-preflight-execution",
9571
+ started_at: "2026-05-14T07:00:00.000Z",
9572
+ });
9573
+ await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), {
9574
+ active: true,
9575
+ skill: "ralph",
9576
+ phase: "starting",
9577
+ session_id: nativeSessionId,
9578
+ active_skills: [{ skill: "ralph", phase: "starting", active: true, session_id: nativeSessionId }],
9579
+ });
9580
+ await writeJson(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), {
9581
+ active: false,
9582
+ mode: "ralph",
9583
+ current_phase: "complete",
9584
+ session_id: canonicalSessionId,
9585
+ owner_codex_thread_id: "thread-A",
9586
+ completed_at: "2026-05-14T07:30:00.000Z",
9587
+ completion_audit: {
9588
+ passed: true,
9589
+ prompt_to_artifact_checklist: ["task evidence mapped"],
9590
+ verification_evidence: ["fresh verification evidence recorded"],
9591
+ },
9592
+ });
9593
+
9594
+ const result = await dispatchCodexNativeHook(
9595
+ {
9596
+ hook_event_name: "Stop",
9597
+ cwd,
9598
+ session_id: nativeSessionId,
9599
+ thread_id: "thread-B",
9600
+ },
9601
+ { cwd },
9602
+ );
9603
+
9604
+ assert.equal(result.omxEventName, "stop");
9605
+ assert.deepEqual(result.outputJson, {
9606
+ decision: "block",
9607
+ reason:
9608
+ "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/native-hook-seed/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
9609
+ stopReason: "ralph_starting",
9610
+ systemMessage:
9611
+ "OMX Ralph is still active (phase: starting; state: .omx/state/sessions/native-hook-seed/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
9612
+ });
9613
+ const preservedState = JSON.parse(await readFile(join(stateDir, "sessions", nativeSessionId, "ralph-state.json"), "utf-8"));
9614
+ assert.equal(preservedState.active, true);
9615
+ assert.equal(preservedState.current_phase, "starting");
9616
+ assert.equal(preservedState.stop_reason, undefined);
9617
+ } finally {
9618
+ await rm(cwd, { recursive: true, force: true });
9619
+ }
9620
+ });
9621
+
8030
9622
  it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => {
8031
9623
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-"));
8032
9624
  try {