oh-my-codex 0.18.6 → 0.18.8

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 (444) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +59 -10
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/definitions.test.js +11 -0
  6. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  7. package/dist/agents/__tests__/native-config.test.js +56 -6
  8. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  9. package/dist/agents/definitions.d.ts +10 -0
  10. package/dist/agents/definitions.d.ts.map +1 -1
  11. package/dist/agents/definitions.js +5 -1
  12. package/dist/agents/definitions.js.map +1 -1
  13. package/dist/agents/native-config.d.ts +5 -1
  14. package/dist/agents/native-config.d.ts.map +1 -1
  15. package/dist/agents/native-config.js +19 -4
  16. package/dist/agents/native-config.js.map +1 -1
  17. package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
  18. package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
  19. package/dist/autopilot/__tests__/fsm.test.js +75 -0
  20. package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
  21. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
  22. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
  24. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
  25. package/dist/autopilot/deep-interview-gate.d.ts +18 -0
  26. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
  27. package/dist/autopilot/deep-interview-gate.js +256 -0
  28. package/dist/autopilot/deep-interview-gate.js.map +1 -0
  29. package/dist/autopilot/fsm.d.ts +13 -0
  30. package/dist/autopilot/fsm.d.ts.map +1 -0
  31. package/dist/autopilot/fsm.js +70 -0
  32. package/dist/autopilot/fsm.js.map +1 -0
  33. package/dist/autopilot/ralplan-gate.d.ts +17 -0
  34. package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
  35. package/dist/autopilot/ralplan-gate.js +61 -0
  36. package/dist/autopilot/ralplan-gate.js.map +1 -0
  37. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  38. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  41. package/dist/cli/__tests__/index.test.js +83 -7
  42. package/dist/cli/__tests__/index.test.js.map +1 -1
  43. package/dist/cli/__tests__/launch-fallback.test.js +175 -6
  44. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  45. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  46. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  47. package/dist/cli/__tests__/question.test.js +100 -0
  48. package/dist/cli/__tests__/question.test.js.map +1 -1
  49. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  50. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  51. package/dist/cli/__tests__/ralph.test.js +14 -0
  52. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  53. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  54. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  55. package/dist/cli/__tests__/setup-refresh.test.js +83 -0
  56. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  57. package/dist/cli/__tests__/state.test.js +21 -0
  58. package/dist/cli/__tests__/state.test.js.map +1 -1
  59. package/dist/cli/__tests__/team.test.js +2 -2
  60. package/dist/cli/__tests__/team.test.js.map +1 -1
  61. package/dist/cli/__tests__/update.test.js +110 -2
  62. package/dist/cli/__tests__/update.test.js.map +1 -1
  63. package/dist/cli/doctor.d.ts.map +1 -1
  64. package/dist/cli/doctor.js +8 -1
  65. package/dist/cli/doctor.js.map +1 -1
  66. package/dist/cli/index.d.ts +14 -3
  67. package/dist/cli/index.d.ts.map +1 -1
  68. package/dist/cli/index.js +298 -50
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/cli/plugin-marketplace.d.ts +14 -2
  71. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  72. package/dist/cli/plugin-marketplace.js +62 -15
  73. package/dist/cli/plugin-marketplace.js.map +1 -1
  74. package/dist/cli/question.d.ts.map +1 -1
  75. package/dist/cli/question.js +36 -5
  76. package/dist/cli/question.js.map +1 -1
  77. package/dist/cli/ralph.d.ts.map +1 -1
  78. package/dist/cli/ralph.js +3 -1
  79. package/dist/cli/ralph.js.map +1 -1
  80. package/dist/cli/setup-preferences.d.ts +2 -0
  81. package/dist/cli/setup-preferences.d.ts.map +1 -1
  82. package/dist/cli/setup-preferences.js +4 -0
  83. package/dist/cli/setup-preferences.js.map +1 -1
  84. package/dist/cli/setup.d.ts +3 -0
  85. package/dist/cli/setup.d.ts.map +1 -1
  86. package/dist/cli/setup.js +166 -27
  87. package/dist/cli/setup.js.map +1 -1
  88. package/dist/cli/state.d.ts.map +1 -1
  89. package/dist/cli/state.js +8 -1
  90. package/dist/cli/state.js.map +1 -1
  91. package/dist/cli/tmux-hook.d.ts.map +1 -1
  92. package/dist/cli/tmux-hook.js +16 -0
  93. package/dist/cli/tmux-hook.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 +47 -3
  97. package/dist/cli/update.js.map +1 -1
  98. package/dist/config/__tests__/deep-interview.test.js +7 -6
  99. package/dist/config/__tests__/deep-interview.test.js.map +1 -1
  100. package/dist/config/__tests__/generator-notify.test.js +1 -0
  101. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  102. package/dist/config/deep-interview.d.ts.map +1 -1
  103. package/dist/config/deep-interview.js +14 -4
  104. package/dist/config/deep-interview.js.map +1 -1
  105. package/dist/config/generator.d.ts +2 -2
  106. package/dist/config/generator.d.ts.map +1 -1
  107. package/dist/config/generator.js +2 -2
  108. package/dist/config/generator.js.map +1 -1
  109. package/dist/config/team-mode.d.ts +12 -0
  110. package/dist/config/team-mode.d.ts.map +1 -0
  111. package/dist/config/team-mode.js +91 -0
  112. package/dist/config/team-mode.js.map +1 -0
  113. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  114. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  115. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
  116. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  117. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  118. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  119. package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
  120. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  121. package/dist/hooks/__tests__/keyword-detector.test.js +1072 -14
  122. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  123. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +64 -1
  124. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  126. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  128. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  129. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  130. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  131. package/dist/hooks/__tests__/session.test.js +25 -0
  132. package/dist/hooks/__tests__/session.test.js.map +1 -1
  133. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  134. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  135. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  136. package/dist/hooks/agents-overlay.js +36 -50
  137. package/dist/hooks/agents-overlay.js.map +1 -1
  138. package/dist/hooks/deep-interview-config-instruction.js +1 -1
  139. package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
  140. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  141. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  142. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  143. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  144. package/dist/hooks/keyword-detector.d.ts +1 -0
  145. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  146. package/dist/hooks/keyword-detector.js +428 -32
  147. package/dist/hooks/keyword-detector.js.map +1 -1
  148. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  149. package/dist/hooks/keyword-registry.js +1 -0
  150. package/dist/hooks/keyword-registry.js.map +1 -1
  151. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  152. package/dist/hooks/prompt-guidance-contract.js +6 -0
  153. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  154. package/dist/hooks/session.d.ts +3 -0
  155. package/dist/hooks/session.d.ts.map +1 -1
  156. package/dist/hooks/session.js +13 -5
  157. package/dist/hooks/session.js.map +1 -1
  158. package/dist/hud/__tests__/authority.test.js +469 -31
  159. package/dist/hud/__tests__/authority.test.js.map +1 -1
  160. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  161. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  162. package/dist/hud/__tests__/index.test.js +210 -2
  163. package/dist/hud/__tests__/index.test.js.map +1 -1
  164. package/dist/hud/__tests__/reconcile.test.js +588 -28
  165. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  166. package/dist/hud/__tests__/render.test.js +61 -0
  167. package/dist/hud/__tests__/render.test.js.map +1 -1
  168. package/dist/hud/__tests__/state.test.js +208 -0
  169. package/dist/hud/__tests__/state.test.js.map +1 -1
  170. package/dist/hud/__tests__/tmux.test.js +314 -22
  171. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  172. package/dist/hud/authority.d.ts +5 -0
  173. package/dist/hud/authority.d.ts.map +1 -1
  174. package/dist/hud/authority.js +337 -30
  175. package/dist/hud/authority.js.map +1 -1
  176. package/dist/hud/index.d.ts +20 -2
  177. package/dist/hud/index.d.ts.map +1 -1
  178. package/dist/hud/index.js +103 -26
  179. package/dist/hud/index.js.map +1 -1
  180. package/dist/hud/reconcile.d.ts +3 -3
  181. package/dist/hud/reconcile.d.ts.map +1 -1
  182. package/dist/hud/reconcile.js +129 -20
  183. package/dist/hud/reconcile.js.map +1 -1
  184. package/dist/hud/render.d.ts.map +1 -1
  185. package/dist/hud/render.js +35 -0
  186. package/dist/hud/render.js.map +1 -1
  187. package/dist/hud/state.d.ts.map +1 -1
  188. package/dist/hud/state.js +64 -50
  189. package/dist/hud/state.js.map +1 -1
  190. package/dist/hud/tmux.d.ts +26 -6
  191. package/dist/hud/tmux.d.ts.map +1 -1
  192. package/dist/hud/tmux.js +173 -38
  193. package/dist/hud/tmux.js.map +1 -1
  194. package/dist/hud/types.d.ts +11 -0
  195. package/dist/hud/types.d.ts.map +1 -1
  196. package/dist/hud/types.js.map +1 -1
  197. package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
  198. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  199. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  200. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  201. package/dist/mcp/__tests__/state-server.test.js +13 -1
  202. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  203. package/dist/mcp/hermes-bridge.d.ts +12 -2
  204. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  205. package/dist/mcp/hermes-bridge.js +83 -9
  206. package/dist/mcp/hermes-bridge.js.map +1 -1
  207. package/dist/mcp/state-paths.d.ts +32 -0
  208. package/dist/mcp/state-paths.d.ts.map +1 -1
  209. package/dist/mcp/state-paths.js +113 -17
  210. package/dist/mcp/state-paths.js.map +1 -1
  211. package/dist/mcp/state-server.d.ts +4 -4
  212. package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
  213. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  214. package/dist/pipeline/__tests__/stages.test.js +130 -0
  215. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  216. package/dist/pipeline/orchestrator.js +1 -1
  217. package/dist/pipeline/orchestrator.js.map +1 -1
  218. package/dist/pipeline/stages/ralplan.d.ts +1 -0
  219. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  220. package/dist/pipeline/stages/ralplan.js +14 -5
  221. package/dist/pipeline/stages/ralplan.js.map +1 -1
  222. package/dist/question/__tests__/deep-interview.test.js +160 -2
  223. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  224. package/dist/question/__tests__/policy.test.js +63 -3
  225. package/dist/question/__tests__/policy.test.js.map +1 -1
  226. package/dist/question/__tests__/renderer.test.js +191 -2
  227. package/dist/question/__tests__/renderer.test.js.map +1 -1
  228. package/dist/question/__tests__/state.test.js +94 -3
  229. package/dist/question/__tests__/state.test.js.map +1 -1
  230. package/dist/question/__tests__/ui.test.js +4 -0
  231. package/dist/question/__tests__/ui.test.js.map +1 -1
  232. package/dist/question/autopilot-wait.d.ts +12 -2
  233. package/dist/question/autopilot-wait.d.ts.map +1 -1
  234. package/dist/question/autopilot-wait.js +158 -47
  235. package/dist/question/autopilot-wait.js.map +1 -1
  236. package/dist/question/deep-interview.d.ts.map +1 -1
  237. package/dist/question/deep-interview.js +22 -6
  238. package/dist/question/deep-interview.js.map +1 -1
  239. package/dist/question/policy.d.ts.map +1 -1
  240. package/dist/question/policy.js +2 -5
  241. package/dist/question/policy.js.map +1 -1
  242. package/dist/question/renderer.d.ts +12 -0
  243. package/dist/question/renderer.d.ts.map +1 -1
  244. package/dist/question/renderer.js +87 -3
  245. package/dist/question/renderer.js.map +1 -1
  246. package/dist/question/state.d.ts +8 -1
  247. package/dist/question/state.d.ts.map +1 -1
  248. package/dist/question/state.js +54 -14
  249. package/dist/question/state.js.map +1 -1
  250. package/dist/question/types.d.ts +1 -1
  251. package/dist/question/types.d.ts.map +1 -1
  252. package/dist/question/ui.d.ts +1 -0
  253. package/dist/question/ui.d.ts.map +1 -1
  254. package/dist/question/ui.js +1 -0
  255. package/dist/question/ui.js.map +1 -1
  256. package/dist/ralplan/__tests__/runtime.test.js +191 -0
  257. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  258. package/dist/ralplan/consensus-gate.d.ts +9 -1
  259. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  260. package/dist/ralplan/consensus-gate.js +84 -2
  261. package/dist/ralplan/consensus-gate.js.map +1 -1
  262. package/dist/ralplan/runtime.d.ts +9 -0
  263. package/dist/ralplan/runtime.d.ts.map +1 -1
  264. package/dist/ralplan/runtime.js +32 -11
  265. package/dist/ralplan/runtime.js.map +1 -1
  266. package/dist/scripts/__tests__/codex-native-hook.test.js +2315 -280
  267. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  268. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  269. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  270. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  271. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  272. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  273. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  274. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  275. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  276. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  277. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  278. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  279. package/dist/scripts/codex-native-hook.js +431 -56
  280. package/dist/scripts/codex-native-hook.js.map +1 -1
  281. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  282. package/dist/scripts/codex-native-pre-post.js +79 -1
  283. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  284. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  285. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  286. package/dist/scripts/hook-payload-guard.d.ts +9 -0
  287. package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
  288. package/dist/scripts/hook-payload-guard.js +111 -0
  289. package/dist/scripts/hook-payload-guard.js.map +1 -0
  290. package/dist/scripts/notify-fallback-watcher.js +8 -1
  291. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  292. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
  293. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
  294. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
  295. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
  296. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  297. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  298. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  299. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  300. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  301. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  302. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  303. package/dist/scripts/notify-hook/state-io.js +62 -38
  304. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  305. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  306. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  307. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  308. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  309. package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
  310. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  311. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  312. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  313. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  314. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  315. package/dist/scripts/notify-hook.js +86 -13
  316. package/dist/scripts/notify-hook.js.map +1 -1
  317. package/dist/scripts/run-test-files.js +193 -22
  318. package/dist/scripts/run-test-files.js.map +1 -1
  319. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  320. package/dist/scripts/sync-plugin-mirror.js +61 -3
  321. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  322. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  323. package/dist/scripts/verify-native-agents.js +58 -1
  324. package/dist/scripts/verify-native-agents.js.map +1 -1
  325. package/dist/state/__tests__/operations.test.js +1125 -1
  326. package/dist/state/__tests__/operations.test.js.map +1 -1
  327. package/dist/state/__tests__/skill-active.test.js +46 -1
  328. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  329. package/dist/state/__tests__/workflow-transition.test.js +98 -7
  330. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  331. package/dist/state/operations.d.ts.map +1 -1
  332. package/dist/state/operations.js +159 -2
  333. package/dist/state/operations.js.map +1 -1
  334. package/dist/state/skill-active.js +6 -8
  335. package/dist/state/skill-active.js.map +1 -1
  336. package/dist/state/workflow-transition-reconcile.d.ts +6 -0
  337. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  338. package/dist/state/workflow-transition-reconcile.js +38 -15
  339. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  340. package/dist/state/workflow-transition.d.ts.map +1 -1
  341. package/dist/state/workflow-transition.js +10 -3
  342. package/dist/state/workflow-transition.js.map +1 -1
  343. package/dist/subagents/__tests__/tracker.test.js +139 -0
  344. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  345. package/dist/subagents/tracker.d.ts +3 -0
  346. package/dist/subagents/tracker.d.ts.map +1 -1
  347. package/dist/subagents/tracker.js +41 -4
  348. package/dist/subagents/tracker.js.map +1 -1
  349. package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
  350. package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
  351. package/dist/team/__tests__/coordination-protocol.test.js +173 -0
  352. package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
  353. package/dist/team/__tests__/runtime.test.js +52 -3
  354. package/dist/team/__tests__/runtime.test.js.map +1 -1
  355. package/dist/team/__tests__/scaling.test.js +9 -4
  356. package/dist/team/__tests__/scaling.test.js.map +1 -1
  357. package/dist/team/__tests__/state.test.js +83 -0
  358. package/dist/team/__tests__/state.test.js.map +1 -1
  359. package/dist/team/__tests__/tmux-session.test.js +240 -2
  360. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  361. package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
  362. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  363. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  364. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  365. package/dist/team/coordination-protocol.d.ts +14 -0
  366. package/dist/team/coordination-protocol.d.ts.map +1 -0
  367. package/dist/team/coordination-protocol.js +244 -0
  368. package/dist/team/coordination-protocol.js.map +1 -0
  369. package/dist/team/runtime.d.ts +1 -0
  370. package/dist/team/runtime.d.ts.map +1 -1
  371. package/dist/team/runtime.js +19 -3
  372. package/dist/team/runtime.js.map +1 -1
  373. package/dist/team/scaling.d.ts.map +1 -1
  374. package/dist/team/scaling.js +3 -2
  375. package/dist/team/scaling.js.map +1 -1
  376. package/dist/team/state/tasks.d.ts.map +1 -1
  377. package/dist/team/state/tasks.js +24 -0
  378. package/dist/team/state/tasks.js.map +1 -1
  379. package/dist/team/state/types.d.ts +21 -1
  380. package/dist/team/state/types.d.ts.map +1 -1
  381. package/dist/team/state/types.js.map +1 -1
  382. package/dist/team/state.d.ts +17 -1
  383. package/dist/team/state.d.ts.map +1 -1
  384. package/dist/team/state.js +12 -5
  385. package/dist/team/state.js.map +1 -1
  386. package/dist/team/team-ops.d.ts +1 -1
  387. package/dist/team/team-ops.d.ts.map +1 -1
  388. package/dist/team/team-ops.js.map +1 -1
  389. package/dist/team/tmux-session.d.ts +2 -0
  390. package/dist/team/tmux-session.d.ts.map +1 -1
  391. package/dist/team/tmux-session.js +161 -13
  392. package/dist/team/tmux-session.js.map +1 -1
  393. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  394. package/dist/team/worker-bootstrap.js +63 -0
  395. package/dist/team/worker-bootstrap.js.map +1 -1
  396. package/dist/utils/__tests__/agents-model-table.test.js +4 -2
  397. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  398. package/dist/utils/agents-model-table.d.ts.map +1 -1
  399. package/dist/utils/agents-model-table.js +3 -0
  400. package/dist/utils/agents-model-table.js.map +1 -1
  401. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  402. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  403. package/package.json +8 -8
  404. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  405. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  406. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  407. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -6
  408. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  409. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
  410. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  411. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
  412. package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
  413. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  414. package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
  415. package/skills/autopilot/SKILL.md +13 -6
  416. package/skills/code-review/SKILL.md +7 -7
  417. package/skills/deep-interview/SKILL.md +9 -4
  418. package/skills/ralph/SKILL.md +22 -22
  419. package/skills/ralplan/SKILL.md +12 -0
  420. package/skills/team/SKILL.md +16 -0
  421. package/skills/ultraqa/SKILL.md +9 -0
  422. package/skills/worker/SKILL.md +14 -0
  423. package/src/scripts/__tests__/codex-native-hook.test.ts +4435 -2083
  424. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  425. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  426. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  427. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  428. package/src/scripts/codex-native-hook.ts +536 -51
  429. package/src/scripts/codex-native-pre-post.ts +80 -0
  430. package/src/scripts/demo-team-e2e.sh +10 -7
  431. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  432. package/src/scripts/hook-payload-guard.ts +113 -0
  433. package/src/scripts/notify-fallback-watcher.ts +8 -1
  434. package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
  435. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  436. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  437. package/src/scripts/notify-hook/state-io.ts +75 -37
  438. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  439. package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
  440. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  441. package/src/scripts/notify-hook.ts +105 -6
  442. package/src/scripts/run-test-files.ts +192 -22
  443. package/src/scripts/sync-plugin-mirror.ts +98 -9
  444. package/src/scripts/verify-native-agents.ts +65 -1
@@ -4,6 +4,7 @@ import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promis
4
4
  import { extname, join, relative, resolve } from "path";
5
5
  import { pathToFileURL } from "url";
6
6
  import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
7
+ import { redactAuthSecrets } from "../auth/redact.js";
7
8
  import {
8
9
  SKILL_ACTIVE_STATE_FILE,
9
10
  extractSessionIdFromInitializedStatePath,
@@ -14,6 +15,7 @@ import {
14
15
  type SkillActiveStateLike,
15
16
  } from "../state/skill-active.js";
16
17
  import {
18
+ isTrustedSubagentThread,
17
19
  readSubagentSessionSummary,
18
20
  readSubagentTrackingState,
19
21
  recordSubagentTurnForSession,
@@ -46,6 +48,7 @@ import {
46
48
  type SkillActiveState,
47
49
  } from "../hooks/keyword-detector.js";
48
50
  import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
51
+ import { readTeamModeConfig } from "../config/team-mode.js";
49
52
  import {
50
53
  detectNativeStopStallPattern,
51
54
  loadAutoNudgeConfig,
@@ -108,6 +111,10 @@ import {
108
111
  isFinalHandoffDocumentRefreshCandidate,
109
112
  } from "../document-refresh/enforcer.js";
110
113
  import { buildExecFollowupStopOutput } from "../exec/followup.js";
114
+ import {
115
+ MAX_NATIVE_STDIN_JSON_BYTES,
116
+ extractRawCodexHookEventName,
117
+ } from "./hook-payload-guard.js";
111
118
 
112
119
  type CodexHookEventName =
113
120
  | "SessionStart"
@@ -142,6 +149,7 @@ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
142
149
  const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
143
150
  const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
144
151
  const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
152
+ const OMX_OWNER_SESSION_ID_PATTERN = /^omx-[A-Za-z0-9_-]{1,60}$/;
145
153
  const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
146
154
  /^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
147
155
  /^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
@@ -172,6 +180,35 @@ function safeObject(value: unknown): Record<string, unknown> {
172
180
  return value && typeof value === "object" ? value as Record<string, unknown> : {};
173
181
  }
174
182
 
183
+ function resolveHudReconcileSessionId(
184
+ currentSessionState: SessionState | null,
185
+ canonicalSessionId: string | null,
186
+ sessionIdForState: string | null,
187
+ ): string | undefined {
188
+ const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
189
+ if (OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId)) return ownerOmxSessionId;
190
+ return canonicalSessionId || sessionIdForState || undefined;
191
+ }
192
+
193
+ function resolveHudReconcileSessionIds(
194
+ currentSessionState: SessionState | null,
195
+ canonicalSessionId: string | null,
196
+ sessionIdForState: string | null,
197
+ nativeSessionId: string | null,
198
+ ): string[] {
199
+ const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
200
+ return uniqueNonEmpty([
201
+ resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState),
202
+ canonicalSessionId ?? undefined,
203
+ sessionIdForState ?? undefined,
204
+ nativeSessionId ?? undefined,
205
+ safeString(currentSessionState?.session_id),
206
+ safeString(currentSessionState?.native_session_id),
207
+ OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId) ? ownerOmxSessionId : undefined,
208
+ safeString(currentSessionState?.owner_codex_session_id),
209
+ ]);
210
+ }
211
+
175
212
  function safeContextSnippet(value: unknown, maxLength = 300): string {
176
213
  const text = safeString(value).replace(/\s+/g, " ").trim();
177
214
  if (text.length <= maxLength) return text;
@@ -248,18 +285,25 @@ async function recordNativeSubagentSessionStart(
248
285
  metadata: NativeSubagentSessionStartMetadata,
249
286
  transcriptPath: string,
250
287
  ): Promise<void> {
288
+ const parentThreadId = metadata.parentThreadId.trim();
289
+ const childThreadId = childSessionId.trim();
251
290
  const trackingSessionIds = [...new Set([
252
291
  canonicalSessionId.trim(),
253
- metadata.parentThreadId.trim(),
292
+ parentThreadId,
254
293
  ].filter(Boolean))];
255
294
  for (const sessionId of trackingSessionIds) {
295
+ if (parentThreadId && parentThreadId !== childThreadId) {
296
+ await recordSubagentTurnForSession(cwd, {
297
+ sessionId,
298
+ threadId: parentThreadId,
299
+ kind: 'leader',
300
+ }).catch(() => {});
301
+ }
256
302
  await recordSubagentTurnForSession(cwd, {
257
303
  sessionId,
258
- threadId: metadata.parentThreadId,
259
- }).catch(() => {});
260
- await recordSubagentTurnForSession(cwd, {
261
- sessionId,
262
- threadId: childSessionId,
304
+ threadId: childThreadId,
305
+ kind: 'subagent',
306
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
263
307
  mode: metadata.agentRole,
264
308
  }).catch(() => {});
265
309
  }
@@ -301,17 +345,54 @@ async function isNativeSubagentHook(
301
345
  canonicalSessionId: string,
302
346
  nativeSessionId: string,
303
347
  threadId: string,
348
+ canonicalLeaderNativeSessionId = "",
304
349
  ): Promise<boolean> {
305
- const candidateIds = [nativeSessionId, threadId]
350
+ const nativeId = nativeSessionId.trim();
351
+ const promptThreadId = threadId.trim();
352
+ const candidateIds = [nativeId, promptThreadId]
306
353
  .map((value) => value.trim())
307
354
  .filter(Boolean);
308
355
  if (candidateIds.length === 0) return false;
309
356
 
310
357
  const sessionId = canonicalSessionId.trim();
311
- if (sessionId) {
312
- const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
313
- if (summary && candidateIds.some((id) => summary.allSubagentThreadIds.includes(id))) {
314
- return true;
358
+ const currentLeaderNativeSessionId = canonicalLeaderNativeSessionId.trim();
359
+ const summary = sessionId
360
+ ? await readSubagentSessionSummary(cwd, sessionId).catch(() => null)
361
+ : null;
362
+ const currentLeaderIds = new Set([
363
+ currentLeaderNativeSessionId,
364
+ summary?.leaderThreadId?.trim(),
365
+ ].filter(Boolean));
366
+ if (
367
+ summary
368
+ && candidateIds.some((id) => !currentLeaderIds.has(id) && summary.allSubagentThreadIds.includes(id))
369
+ ) {
370
+ return true;
371
+ }
372
+ // Native UserPromptSubmit can carry a per-turn thread_id that differs from
373
+ // the long-lived native session id. Treat the current canonical native
374
+ // session as the leader before consulting stale/global tracker state.
375
+ if (
376
+ sessionId
377
+ && currentLeaderNativeSessionId
378
+ && (
379
+ nativeId === currentLeaderNativeSessionId
380
+ || (!nativeId && promptThreadId === currentLeaderNativeSessionId)
381
+ )
382
+ ) {
383
+ return false;
384
+ }
385
+
386
+ if (summary) {
387
+ const leaderThreadId = summary.leaderThreadId?.trim();
388
+ if (
389
+ leaderThreadId
390
+ && (
391
+ nativeId === leaderThreadId
392
+ || (!nativeId && promptThreadId === leaderThreadId)
393
+ )
394
+ ) {
395
+ return false;
315
396
  }
316
397
  }
317
398
 
@@ -326,7 +407,7 @@ async function isNativeSubagentHook(
326
407
  if (!trackingState) return false;
327
408
 
328
409
  return Object.values(trackingState.sessions).some((session) => (
329
- candidateIds.some((id) => session.threads[id]?.kind === "subagent")
410
+ candidateIds.some((id) => isTrustedSubagentThread(session, id))
330
411
  ));
331
412
  }
332
413
 
@@ -1669,6 +1750,7 @@ function buildNativeOutsideTmuxTeamPromptBlockState(
1669
1750
  threadId?: string,
1670
1751
  turnId?: string,
1671
1752
  ): SkillActiveState | null {
1753
+ if (!readTeamModeConfig(cwd).enabled) return null;
1672
1754
  const match = detectPrimaryKeyword(prompt);
1673
1755
  if (match?.skill !== "team") return null;
1674
1756
 
@@ -1702,15 +1784,32 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
1702
1784
  return `skill: ${mode} activated and initial state initialized at ${statePath}; use CLI-first state updates via \`omx state write/read/clear --input '<json>' --json\`; use omx_state MCP only when explicit MCP compatibility is enabled.`;
1703
1785
  }
1704
1786
 
1705
- function buildAutopilotPromptActivationNote(skillState?: SkillActiveState | null): string | null {
1787
+ function buildAutopilotPromptActivationNote(
1788
+ skillState?: SkillActiveState | null,
1789
+ options: { markedQuestionAnswer?: boolean; cwd?: string } = {},
1790
+ ): string | null {
1706
1791
  if (skillState?.initialized_mode !== "autopilot") return null;
1792
+ const teamHandoff = readTeamModeConfig(options.cwd).enabled
1793
+ ? " (+ $team if needed)"
1794
+ : "";
1707
1795
  return [
1708
- "Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
1796
+ `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
1709
1797
  "Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
1798
+ "Deep-interview is a structured question chain, not a one-question gate: after an omx question answer, re-score ambiguity against the active threshold, treat max_rounds as a cap, and crystallize once ambiguity is at or below threshold and readiness gates pass.",
1799
+ options.markedQuestionAnswer
1800
+ ? "This turn is a marked omx question answer. Treat ordinary selected option/freeform answer text as interview input, then re-score. Do not close merely because the first question was answered; if ambiguity is at or below threshold and readiness gates pass, write interview_complete evidence and hand off. Ask another deep-interview follow-up only when a readiness gate remains unresolved and the answer would materially change execution."
1801
+ : null,
1802
+ "Do not advance from deep-interview to ralplan merely because the first question was answered; persist explicit interview_complete evidence before setting current_phase=ralplan, and do advance when threshold plus readiness gates are satisfied.",
1710
1803
  "The ralplan phase is not complete until Planner output has been reviewed sequentially by Architect and then Critic; do not hand off to Ultragoal or implementation until the ralplan state/artifact records both ralplan_architect_review and ralplan_critic_review with approval or an explicit blocker.",
1711
1804
  "Do not silently fall back to ordinary $plan/ralplan-only handling; keep autopilot-state.json, skill-active-state.json, HUD/statusline, and Codex goal-mode handoff guidance visible while the workflow is active.",
1712
1805
  "When Codex goal tools are available, call get_goal/create_goal only from the active thread handoff and treat the active goal as the completion contract until code-review and ultraqa are clean.",
1713
- ].join(" ");
1806
+ ].filter(Boolean).join(" ");
1807
+ }
1808
+
1809
+ function formatExecutionHandoffList(cwd: string): string {
1810
+ return readTeamModeConfig(cwd).enabled
1811
+ ? "`$ultragoal`, `$team`, or `$ralph`"
1812
+ : "`$ultragoal` or `$ralph`";
1714
1813
  }
1715
1814
 
1716
1815
  function buildAdditionalContextMessage(
@@ -1721,8 +1820,9 @@ function buildAdditionalContextMessage(
1721
1820
  ): string | null {
1722
1821
  if (!prompt) return null;
1723
1822
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
1724
- const matches = detectKeywords(prompt);
1725
- const match = detectPrimaryKeyword(prompt);
1823
+ const teamMode = readTeamModeConfig(cwd);
1824
+ const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
1825
+ const match = matches[0] ?? null;
1726
1826
  if (!match) {
1727
1827
  const continuedSkill = safeString(skillState?.skill).trim();
1728
1828
  if (!continuedSkill) return promptPriorityMessage;
@@ -1730,6 +1830,8 @@ function buildAdditionalContextMessage(
1730
1830
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1731
1831
  : null;
1732
1832
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1833
+ const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
1834
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
1733
1835
  return [
1734
1836
  `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
1735
1837
  promptPriorityMessage,
@@ -1738,12 +1840,36 @@ function buildAdditionalContextMessage(
1738
1840
  : null,
1739
1841
  deepInterviewPromptActivationNote,
1740
1842
  deepInterviewConfigPromptActivationNote,
1843
+ autopilotPromptActivationNote,
1741
1844
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1742
1845
  ].filter(Boolean).join(" ");
1743
1846
  }
1744
1847
  const detectedKeywordMessage = matches.length > 1
1745
1848
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
1746
1849
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
1850
+ const continuedSkill = safeString(skillState?.skill).trim();
1851
+ if (
1852
+ continuedSkill
1853
+ && continuedSkill !== match.skill
1854
+ && /^\s*\[omx question answered\]/i.test(prompt)
1855
+ ) {
1856
+ const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
1857
+ ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1858
+ : null;
1859
+ const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1860
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
1861
+ return [
1862
+ `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}"; workflow-like tokens inside the marked omx question answer are treated as answer text, not a new workflow activation.`,
1863
+ promptPriorityMessage,
1864
+ skillState?.initialized_mode && skillState.initialized_state_path
1865
+ ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
1866
+ : null,
1867
+ deepInterviewPromptActivationNote,
1868
+ deepInterviewConfigPromptActivationNote,
1869
+ autopilotPromptActivationNote,
1870
+ "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1871
+ ].filter(Boolean).join(" ");
1872
+ }
1747
1873
  const activeSkills = Array.isArray(skillState?.active_skills)
1748
1874
  ? skillState.active_skills.map((entry) => entry.skill)
1749
1875
  : [];
@@ -1764,7 +1890,7 @@ function buildAdditionalContextMessage(
1764
1890
  const ultragoalPromptActivationNote = match.skill === "ultragoal"
1765
1891
  ? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
1766
1892
  : null;
1767
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
1893
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
1768
1894
  const combinedTransitionMessage = (() => {
1769
1895
  if (!skillState?.transition_message) return null;
1770
1896
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -2369,8 +2495,11 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
2369
2495
  async function resolveInternalSessionIdForPayload(
2370
2496
  cwd: string,
2371
2497
  payloadSessionId: string,
2498
+ stateDir?: string,
2372
2499
  ): Promise<string> {
2373
- const currentSession = await readUsableSessionState(cwd);
2500
+ const currentSession = stateDir
2501
+ ? await readUsableSessionStateFromStateDir(cwd, stateDir)
2502
+ : await readUsableSessionState(cwd);
2374
2503
  const canonicalSessionId = safeString(currentSession?.session_id).trim();
2375
2504
  if (!canonicalSessionId) return payloadSessionId;
2376
2505
 
@@ -2381,6 +2510,22 @@ async function resolveInternalSessionIdForPayload(
2381
2510
  return payloadSessionId;
2382
2511
  }
2383
2512
 
2513
+ async function readUsableSessionStateFromStateDir(
2514
+ cwd: string,
2515
+ stateDir: string,
2516
+ ): Promise<SessionState | null> {
2517
+ const sessionPath = join(stateDir, "session.json");
2518
+ if (!existsSync(sessionPath)) return null;
2519
+
2520
+ try {
2521
+ const content = await readFile(sessionPath, "utf-8");
2522
+ const state = JSON.parse(content) as SessionState;
2523
+ return isSessionStateUsable(state, cwd) ? state : null;
2524
+ } catch {
2525
+ return null;
2526
+ }
2527
+ }
2528
+
2384
2529
  async function readStopSessionPinnedState(
2385
2530
  fileName: string,
2386
2531
  cwd: string,
@@ -2400,14 +2545,35 @@ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
2400
2545
  ".omx/state",
2401
2546
  ] as const;
2402
2547
 
2403
- const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = new Set([
2548
+ const RALPLAN_ALLOWED_WRITE_PREFIXES = [
2549
+ ".omx/context",
2550
+ ".omx/plans",
2551
+ ".omx/specs",
2552
+ ".omx/state",
2553
+ ] as const;
2554
+
2555
+ const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
2404
2556
  "Write",
2405
2557
  "Edit",
2406
2558
  "MultiEdit",
2559
+ "NotebookEdit",
2407
2560
  "apply_patch",
2408
2561
  "ApplyPatch",
2409
2562
  ]);
2410
2563
 
2564
+ const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
2565
+
2566
+ const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
2567
+ // Autopilot is intentionally excluded: it supervises planning phases such as
2568
+ // ralplan/replan and is not by itself an execution authorization.
2569
+ "autoresearch",
2570
+ "ralph",
2571
+ "team",
2572
+ "ultragoal",
2573
+ "ultrawork",
2574
+ "ultraqa",
2575
+ ]);
2576
+
2411
2577
  function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
2412
2578
  if (!state || state.active !== true) return false;
2413
2579
  const mode = safeString(state.mode).trim();
@@ -2417,7 +2583,39 @@ function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): bool
2417
2583
  return true;
2418
2584
  }
2419
2585
 
2420
- function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boolean {
2586
+ function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
2587
+ if (!state || state.active !== true) return false;
2588
+ const mode = safeString(state.mode).trim();
2589
+ if (mode && mode !== "ralplan") return false;
2590
+ const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
2591
+ if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing")) return false;
2592
+ return true;
2593
+ }
2594
+
2595
+ function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): boolean {
2596
+ if (!state || state.active !== true) return false;
2597
+ const mode = safeString(state.mode).trim();
2598
+ if (mode && mode !== "autopilot") return false;
2599
+ const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
2600
+ return phase === "ralplan" || phase === "replan" || phase === "autopilot:replan";
2601
+ }
2602
+
2603
+ function hasExplicitExecutionHandoffSkill(
2604
+ state: SkillActiveStateLike | null,
2605
+ sessionId: string,
2606
+ threadId: string,
2607
+ ): boolean {
2608
+ return listActiveSkills(state ?? {}).some((entry) => (
2609
+ RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
2610
+ && matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)
2611
+ ));
2612
+ }
2613
+
2614
+ function isAllowedPlanningArtifactPath(
2615
+ cwd: string,
2616
+ rawPath: string,
2617
+ allowedPrefixes: readonly string[],
2618
+ ): boolean {
2421
2619
  const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
2422
2620
  if (!trimmed || trimmed.includes("\0")) return false;
2423
2621
  let relativePath: string;
@@ -2428,11 +2626,19 @@ function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boole
2428
2626
  return false;
2429
2627
  }
2430
2628
  if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/")) return false;
2431
- return DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES.some((prefix) => (
2629
+ return allowedPrefixes.some((prefix) => (
2432
2630
  relativePath === prefix || relativePath.startsWith(`${prefix}/`)
2433
2631
  ));
2434
2632
  }
2435
2633
 
2634
+ function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boolean {
2635
+ return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
2636
+ }
2637
+
2638
+ function isAllowedRalplanArtifactPath(cwd: string, rawPath: string): boolean {
2639
+ return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
2640
+ }
2641
+
2436
2642
  function readPreToolUseCommand(payload: CodexHookPayload): string {
2437
2643
  const toolInput = safeObject(payload.tool_input);
2438
2644
  return safeString(toolInput.command).trim();
@@ -2494,7 +2700,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
2494
2700
  const canonicalState = sessionId
2495
2701
  ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
2496
2702
  : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2497
- if (!canonicalState) return modeState;
2703
+ if (!canonicalState) return null;
2498
2704
  const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
2499
2705
  entry.skill === "deep-interview"
2500
2706
  && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
@@ -2502,12 +2708,102 @@ async function readActiveDeepInterviewStateForPreToolUse(
2502
2708
  return hasActiveDeepInterviewSkill ? modeState : null;
2503
2709
  }
2504
2710
 
2711
+ async function readActiveRalplanStateForPreToolUse(
2712
+ cwd: string,
2713
+ stateDir: string,
2714
+ sessionId: string,
2715
+ threadId: string,
2716
+ ): Promise<Record<string, unknown> | null> {
2717
+ const modeState = sessionId
2718
+ ? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
2719
+ : await readJsonIfExists(join(stateDir, "ralplan-state.json"));
2720
+ const canonicalState = sessionId
2721
+ ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
2722
+ : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2723
+ if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
2724
+ if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId)) return null;
2725
+ if (!canonicalState) return null;
2726
+ const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (
2727
+ entry.skill === "ralplan"
2728
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2729
+ ));
2730
+ if (hasActiveRalplanSkill) return modeState;
2731
+ }
2732
+
2733
+ const autopilotState = sessionId
2734
+ ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
2735
+ : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
2736
+ if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState) return null;
2737
+ if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
2738
+ const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
2739
+ if (terminalAutopilotRunState) return null;
2740
+ if (!canonicalState) return null;
2741
+ const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
2742
+ entry.skill === "autopilot"
2743
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2744
+ ));
2745
+ return hasActiveAutopilotSkill ? autopilotState : null;
2746
+ }
2747
+
2748
+ function isAllowedRalplanBashWrite(cwd: string, command: string): boolean {
2749
+ if (!commandHasDeepInterviewWriteIntent(command)) return true;
2750
+ if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command)) return true;
2751
+ const targets = extractDeepInterviewCommandWriteTargets(command);
2752
+ return targets.length > 0 && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
2753
+ }
2754
+
2755
+ async function buildRalplanPreToolUseBoundaryOutput(
2756
+ payload: CodexHookPayload,
2757
+ cwd: string,
2758
+ stateDir: string,
2759
+ resolvedSessionId?: string,
2760
+ ): Promise<Record<string, unknown> | null> {
2761
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
2762
+ const threadId = readPayloadThreadId(payload);
2763
+ const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
2764
+ if (!activeState) return null;
2765
+
2766
+ const toolName = safeString(payload.tool_name).trim();
2767
+ const command = readPreToolUseCommand(payload);
2768
+ const pathCandidates = readPreToolUsePathCandidates(payload);
2769
+ let blocked = false;
2770
+
2771
+ if (toolName === "Bash") {
2772
+ blocked = !isAllowedRalplanBashWrite(cwd, command);
2773
+ } else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
2774
+ blocked = pathCandidates.length === 0
2775
+ || !pathCandidates.every((candidate) => isAllowedRalplanArtifactPath(cwd, candidate));
2776
+ }
2777
+
2778
+ if (!blocked) return null;
2779
+
2780
+ const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
2781
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
2782
+ const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
2783
+ const planningModeDescription = activeMode === "autopilot"
2784
+ ? "Autopilot is supervising a planning phase"
2785
+ : "Ralplan is consensus-planning mode";
2786
+ return {
2787
+ decision: "block",
2788
+ reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
2789
+ hookSpecificOutput: {
2790
+ hookEventName: "PreToolUse",
2791
+ additionalContext:
2792
+ `${planningModeDescription}. `
2793
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. "
2794
+ + "Do not edit implementation files or run implementation-focused writes from planning phases. "
2795
+ + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
2796
+ },
2797
+ };
2798
+ }
2799
+
2505
2800
  async function buildDeepInterviewPreToolUseBoundaryOutput(
2506
2801
  payload: CodexHookPayload,
2507
2802
  cwd: string,
2508
2803
  stateDir: string,
2804
+ resolvedSessionId?: string,
2509
2805
  ): Promise<Record<string, unknown> | null> {
2510
- const sessionId = readPayloadSessionId(payload);
2806
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
2511
2807
  const threadId = readPayloadThreadId(payload);
2512
2808
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
2513
2809
  if (!activeState) return null;
@@ -2533,7 +2829,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
2533
2829
  hookSpecificOutput: {
2534
2830
  hookEventName: "PreToolUse",
2535
2831
  additionalContext:
2536
- "Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under `.omx/context/`, `.omx/interviews/`, `.omx/specs/`, or required `.omx/state/` files. To implement, first ask for or process an explicit transition such as `$ralplan`, `$autopilot`, `$ralph`, `$team`, or `$ultragoal`.",
2832
+ `Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
2537
2833
  },
2538
2834
  };
2539
2835
  }
@@ -2792,6 +3088,7 @@ async function reconcileStaleRootSkillActiveStateForStop(
2792
3088
  function buildRalplanContinuationStatus(
2793
3089
  blocker: { phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string },
2794
3090
  activeSubagentCount: number,
3091
+ cwd: string,
2795
3092
  ): { reason: string; systemMessage: string; stopReasonSuffix: string } {
2796
3093
  const phase = blocker.phase || "planning";
2797
3094
  const artifact = blocker.latestPlanPath
@@ -2827,15 +3124,15 @@ function buildRalplanContinuationStatus(
2827
3124
  }
2828
3125
 
2829
3126
  const completeHint = blocker.planningComplete
2830
- ? " The planning artifacts are present; if consensus is approved, emit the final complete/approved handoff instead of stopping here."
3127
+ ? ` The planning artifacts are present; if consensus is approved, emit terminal ralplan complete/approved handoff state and stop planning. Implementation must wait for an explicit ${formatExecutionHandoffList(cwd).replaceAll("`", "")} handoff.`
2831
3128
  : "";
2832
3129
 
2833
3130
  return {
2834
3131
  reason:
2835
- `Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping.${artifact}${completeHint}`,
3132
+ `Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping; do not begin implementation from ralplan.${artifact}${completeHint}`,
2836
3133
  stopReasonSuffix: "continue_artifact",
2837
3134
  systemMessage:
2838
- `OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing.`,
3135
+ `OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing; do not begin implementation from ralplan.`,
2839
3136
  };
2840
3137
  }
2841
3138
 
@@ -3047,6 +3344,11 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
3047
3344
  stateDir: string,
3048
3345
  canonicalSessionId?: string,
3049
3346
  ): Promise<Record<string, unknown> | null> {
3347
+ const lastAssistantMessage = safeString(
3348
+ payload.last_assistant_message ?? payload.lastAssistantMessage,
3349
+ ).trim();
3350
+ if (!lastAssistantMessage) return null;
3351
+
3050
3352
  const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
3051
3353
  const state = await readJsonIfExists(statePath) ?? {};
3052
3354
  const sessions = safeObject(state.sessions);
@@ -3078,9 +3380,6 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
3078
3380
  await mkdir(stateDir, { recursive: true });
3079
3381
  await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
3080
3382
 
3081
- const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
3082
- if (!stopHookActive) return null;
3083
-
3084
3383
  const maxRepeats = parseBoundedPositiveInteger(
3085
3384
  process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
3086
3385
  ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
@@ -3285,7 +3584,7 @@ async function buildSkillStopOutput(
3285
3584
  const activeSubagentCount = subagentSummary?.activeSubagentThreadIds.length ?? 0;
3286
3585
 
3287
3586
  if (blocker.skill === "ralplan") {
3288
- const status = buildRalplanContinuationStatus(blocker, activeSubagentCount);
3587
+ const status = buildRalplanContinuationStatus(blocker, activeSubagentCount, cwd);
3289
3588
  return {
3290
3589
  decision: "block",
3291
3590
  reason: status.reason,
@@ -3732,10 +4031,20 @@ export async function dispatchCodexNativeHook(
3732
4031
  ): Promise<NativeHookDispatchResult> {
3733
4032
  const hookEventName = readHookEventName(payload);
3734
4033
  const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
4034
+ if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
4035
+ return {
4036
+ hookEventName,
4037
+ omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
4038
+ skillState: null,
4039
+ outputJson: null,
4040
+ };
4041
+ }
3735
4042
  // Native hooks must use the same authoritative runtime state root as HUD/MCP
3736
4043
  // when boxed/team roots are active; do not bypass it with cwd/.omx/state.
3737
4044
  const stateDir = getBaseStateDir(cwd);
3738
- await mkdir(stateDir, { recursive: true });
4045
+ if (hookEventName !== "Stop") {
4046
+ await mkdir(stateDir, { recursive: true });
4047
+ }
3739
4048
 
3740
4049
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
3741
4050
  let skillState: SkillActiveState | null = null;
@@ -3814,7 +4123,13 @@ export async function dispatchCodexNativeHook(
3814
4123
  const sessionIdForState = canonicalSessionId || nativeSessionId;
3815
4124
  let outputJson: Record<string, unknown> | null = null;
3816
4125
  const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
3817
- ? await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId)
4126
+ ? await isNativeSubagentHook(
4127
+ cwd,
4128
+ canonicalSessionId,
4129
+ nativeSessionId,
4130
+ threadId,
4131
+ safeString(currentSessionState?.native_session_id).trim(),
4132
+ )
3818
4133
  : false;
3819
4134
  const isSubagentStop = hookEventName === "Stop"
3820
4135
  ? (await Promise.all(
@@ -3822,7 +4137,15 @@ export async function dispatchCodexNativeHook(
3822
4137
  canonicalSessionId,
3823
4138
  safeString(currentSessionState?.session_id).trim(),
3824
4139
  ].filter(Boolean))]
3825
- .map((candidateSessionId) => isNativeSubagentHook(cwd, candidateSessionId, nativeSessionId, threadId)),
4140
+ .map((candidateSessionId) => isNativeSubagentHook(
4141
+ cwd,
4142
+ candidateSessionId,
4143
+ nativeSessionId,
4144
+ threadId,
4145
+ candidateSessionId === safeString(currentSessionState?.session_id).trim()
4146
+ ? safeString(currentSessionState?.native_session_id).trim()
4147
+ : "",
4148
+ )),
3826
4149
  )).some(Boolean)
3827
4150
  : false;
3828
4151
  const suppressNoisySubagentLifecycleDispatch =
@@ -3922,11 +4245,23 @@ export async function dispatchCodexNativeHook(
3922
4245
  triageAdditionalContext = null;
3923
4246
  }
3924
4247
  }
4248
+ const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
3925
4249
  const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
3926
4250
  && await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
3927
- if (!skipHudReconcileForTeamWorkerPane) {
4251
+ if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
3928
4252
  const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
3929
- await reconcileHudForPromptSubmitFn(cwd, { sessionId: canonicalSessionId || sessionIdForState || undefined }).catch(() => {});
4253
+ const hudSessionId = resolveHudReconcileSessionId(
4254
+ currentSessionState,
4255
+ canonicalSessionId,
4256
+ sessionIdForState,
4257
+ );
4258
+ const hudSessionIds = resolveHudReconcileSessionIds(
4259
+ currentSessionState,
4260
+ canonicalSessionId,
4261
+ sessionIdForState,
4262
+ nativeSessionId,
4263
+ );
4264
+ await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId, sessionIds: hudSessionIds }).catch(() => {});
3930
4265
  }
3931
4266
  }
3932
4267
 
@@ -3986,7 +4321,12 @@ export async function dispatchCodexNativeHook(
3986
4321
  };
3987
4322
  }
3988
4323
  } else if (hookEventName === "PreToolUse") {
3989
- outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
4324
+ const payloadSessionId = readPayloadSessionId(payload);
4325
+ const preToolUseSessionId = payloadSessionId
4326
+ ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
4327
+ : "";
4328
+ outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
4329
+ ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
3990
4330
  ?? buildNativePreToolUseOutput(payload);
3991
4331
  } else if (hookEventName === "PostToolUse") {
3992
4332
  if (detectMcpTransportFailure(payload)) {
@@ -4008,9 +4348,31 @@ export async function dispatchCodexNativeHook(
4008
4348
  };
4009
4349
  }
4010
4350
 
4351
+ function hasNativeStopRuntimeSurface(cwd: string): boolean {
4352
+ if (existsSync(join(cwd, ".omx"))) return true;
4353
+ if (findGitLayout(cwd)) return true;
4354
+ const omxRoot = safeString(process.env.OMX_ROOT).trim();
4355
+ if (omxRoot && existsSync(join(omxRoot, ".omx"))) return true;
4356
+ const stateRoot = safeString(process.env.OMX_STATE_ROOT).trim();
4357
+ if (stateRoot && existsSync(stateRoot)) return true;
4358
+ return [
4359
+ process.env.OMX_SESSION_ID,
4360
+ process.env.OMX_TEAM_INTERNAL_WORKER,
4361
+ process.env.OMX_TEAM_WORKER,
4362
+ process.env.OMX_TEAM_STATE_ROOT,
4363
+ process.env.OMX_TEAM_LEADER_CWD,
4364
+ process.env.OMX_NOTIFY_HOOK_TRUSTED_MANAGED_CWD,
4365
+ process.env.OMX_TMUX_HUD_OWNER,
4366
+ process.env.OMX_TMUX_HUD_LEADER_PANE,
4367
+ ].some((value) => safeString(value).trim() !== "");
4368
+ }
4369
+
4011
4370
  interface NativeHookCliReadResult {
4012
4371
  payload: CodexHookPayload;
4013
4372
  parseError: Error | null;
4373
+ rawInput: string;
4374
+ oversized: boolean;
4375
+ rawHookEventName: CodexHookEventName | null;
4014
4376
  }
4015
4377
 
4016
4378
  export function isCodexNativeHookMainModule(
@@ -4023,36 +4385,156 @@ export function isCodexNativeHookMainModule(
4023
4385
 
4024
4386
  async function readStdinJson(): Promise<NativeHookCliReadResult> {
4025
4387
  const chunks: Buffer[] = [];
4388
+ let totalBytes = 0;
4389
+ let oversized = false;
4026
4390
  for await (const chunk of process.stdin) {
4027
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
4391
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
4392
+ totalBytes += buffer.byteLength;
4393
+ if (totalBytes > MAX_NATIVE_STDIN_JSON_BYTES) {
4394
+ const remaining = Math.max(0, MAX_NATIVE_STDIN_JSON_BYTES - (totalBytes - buffer.byteLength));
4395
+ if (remaining > 0) chunks.push(Buffer.from(buffer.subarray(0, remaining)));
4396
+ oversized = true;
4397
+ process.stdin.destroy();
4398
+ break;
4399
+ }
4400
+ chunks.push(buffer);
4028
4401
  }
4029
4402
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
4403
+ const rawHookEventName = extractRawCodexHookEventName(raw);
4404
+ if (oversized) {
4405
+ return {
4406
+ payload: {},
4407
+ parseError: null,
4408
+ rawInput: raw,
4409
+ oversized: true,
4410
+ rawHookEventName,
4411
+ };
4412
+ }
4030
4413
  if (!raw) {
4031
- return { payload: {}, parseError: null };
4414
+ return { payload: {}, parseError: null, rawInput: raw, oversized: false, rawHookEventName };
4032
4415
  }
4033
4416
 
4034
4417
  try {
4035
4418
  return {
4036
4419
  payload: safeObject(JSON.parse(raw)),
4037
4420
  parseError: null,
4421
+ rawInput: raw,
4422
+ oversized: false,
4423
+ rawHookEventName,
4038
4424
  };
4039
4425
  } catch (error) {
4040
4426
  return {
4041
4427
  payload: {},
4042
4428
  parseError: error instanceof Error ? error : new Error(String(error)),
4429
+ rawInput: raw,
4430
+ oversized: false,
4431
+ rawHookEventName,
4043
4432
  };
4044
4433
  }
4045
4434
  }
4046
4435
 
4436
+ function inferHookEventNameFromMalformedInput(raw: string): CodexHookEventName | null {
4437
+ const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|Stop)\b/i);
4438
+ const value = match?.[1];
4439
+ if (!value) return null;
4440
+ return readHookEventName({ hook_event_name: value });
4441
+ }
4442
+
4443
+ function buildMalformedStdinHookOutput(parseError: Error, rawInput: string): Record<string, unknown> {
4444
+ const reason =
4445
+ "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
4446
+ const systemMessage =
4447
+ `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
4448
+ if (inferHookEventNameFromMalformedInput(rawInput) === "Stop") {
4449
+ return {
4450
+ decision: "block",
4451
+ reason,
4452
+ stopReason: "native_hook_stdin_parse_error",
4453
+ systemMessage,
4454
+ };
4455
+ }
4456
+ return {
4457
+ continue: false,
4458
+ stopReason: "native_hook_stdin_parse_error",
4459
+ systemMessage,
4460
+ };
4461
+ }
4462
+
4463
+ async function buildOversizedStopActiveWorkflowOutput(cwd: string): Promise<Record<string, unknown> | null> {
4464
+ const currentSession = await readUsableSessionState(cwd);
4465
+ const currentSessionId = safeString(currentSession?.session_id).trim()
4466
+ || safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
4467
+ if (!currentSessionId) return null;
4468
+
4469
+ if (await readCanonicalTerminalRunStateForStop(cwd, currentSessionId, "autopilot")) return null;
4470
+
4471
+ const autopilotState = await readModeStateForActiveDecision("autopilot", currentSessionId, cwd);
4472
+ if (!autopilotState || !shouldContinueRun(autopilotState)) return null;
4473
+
4474
+ const phase = formatPhase(autopilotState.current_phase);
4475
+ const reason =
4476
+ `OMX native Stop received oversized stdin before parsing while the current session has active OMX autopilot state (phase: ${phase}); continue once with a compact response or reduce hook payload size so normal Stop gates can run.`;
4477
+ return {
4478
+ decision: "block",
4479
+ reason,
4480
+ stopReason: "native_stop_stdin_oversized_active_workflow",
4481
+ systemMessage:
4482
+ "OMX native Stop rejected oversized stdin before parsing; active current-session workflow state is present, so Stop is blocked instead of silently allowing termination.",
4483
+ };
4484
+ }
4485
+
4486
+ async function buildOversizedStdinHookOutput(
4487
+ rawHookEventName: CodexHookEventName | null,
4488
+ cwd: string,
4489
+ ): Promise<Record<string, unknown>> {
4490
+ if (rawHookEventName === "Stop") {
4491
+ return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
4492
+ }
4493
+ const systemMessage =
4494
+ `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
4495
+ return {
4496
+ continue: false,
4497
+ stopReason: "native_hook_stdin_oversized",
4498
+ systemMessage,
4499
+ };
4500
+ }
4501
+
4047
4502
  function writeNativeHookJsonStdout(output: Record<string, unknown>): void {
4048
4503
  process.stdout.write(`${JSON.stringify(output)}\n`);
4049
4504
  }
4050
4505
 
4506
+ function redactMalformedHookPreview(rawInput: string): string {
4507
+ const withoutControls = rawInput.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
4508
+ const withoutAuthSecrets = redactAuthSecrets(withoutControls);
4509
+ return withoutAuthSecrets
4510
+ .replace(
4511
+ /(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|(?!\2)[^\\])*\2/gi,
4512
+ "$1$2[REDACTED]$2",
4513
+ )
4514
+ .replace(
4515
+ /(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|[^\\])*$/gi,
4516
+ "$1$2[REDACTED]$2",
4517
+ )
4518
+ .replace(
4519
+ /(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(?!["'])[^,}]*/gi,
4520
+ "$1[REDACTED]",
4521
+ );
4522
+ }
4523
+
4524
+ function buildRawInputLogFields(rawInput: string): Record<string, unknown> {
4525
+ if (!rawInput) return {};
4526
+ return {
4527
+ raw_input_length: Buffer.byteLength(rawInput, "utf-8"),
4528
+ raw_input_prefix: redactMalformedHookPreview(rawInput).slice(0, 240),
4529
+ };
4530
+ }
4531
+
4051
4532
  async function logNativeHookCliError(
4052
4533
  cwd: string,
4053
4534
  type: string,
4054
4535
  error: unknown,
4055
4536
  payload: CodexHookPayload = {},
4537
+ details: Record<string, unknown> = {},
4056
4538
  ): Promise<void> {
4057
4539
  const logsDir = join(cwd || process.cwd(), ".omx", "logs");
4058
4540
  await mkdir(logsDir, { recursive: true }).catch(() => {});
@@ -4067,6 +4549,7 @@ async function logNativeHookCliError(
4067
4549
  thread_id: readPayloadThreadId(payload) || undefined,
4068
4550
  turn_id: readPayloadTurnId(payload) || undefined,
4069
4551
  error: error instanceof Error ? error.message : String(error),
4552
+ ...details,
4070
4553
  }) + "\n",
4071
4554
  ).catch(() => {});
4072
4555
  }
@@ -4095,18 +4578,20 @@ function buildStopDispatchFailureOutput(error: unknown): Record<string, unknown>
4095
4578
  }
4096
4579
 
4097
4580
  export async function runCodexNativeHookCli(): Promise<void> {
4098
- const { payload, parseError } = await readStdinJson();
4581
+ const { payload, parseError, rawInput, oversized, rawHookEventName } = await readStdinJson();
4582
+ if (oversized) {
4583
+ writeNativeHookJsonStdout(await buildOversizedStdinHookOutput(rawHookEventName, process.cwd()));
4584
+ return;
4585
+ }
4099
4586
  if (parseError) {
4100
- await logNativeHookCliError(process.cwd(), "native_hook_stdin_parse_error", parseError);
4101
- writeNativeHookJsonStdout({
4102
- decision: "block",
4103
- reason: "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.",
4104
- hookSpecificOutput: {
4105
- hookEventName: "Unknown",
4106
- additionalContext:
4107
- `stdin JSON parsing failed inside codex-native-hook: ${parseError.message}. Emit valid JSON from the native hook caller before retrying.`,
4108
- },
4109
- });
4587
+ await logNativeHookCliError(
4588
+ process.cwd(),
4589
+ "native_hook_stdin_parse_error",
4590
+ parseError,
4591
+ {},
4592
+ buildRawInputLogFields(rawInput),
4593
+ );
4594
+ writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput));
4110
4595
  return;
4111
4596
  }
4112
4597