oh-my-codex 0.15.3 → 0.16.0

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 (452) hide show
  1. package/Cargo.lock +10 -7
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -0
  4. package/crates/omx-explore/Cargo.toml +3 -0
  5. package/crates/omx-explore/src/main.rs +517 -16
  6. package/dist/autoresearch/goal.d.ts +90 -0
  7. package/dist/autoresearch/goal.d.ts.map +1 -0
  8. package/dist/autoresearch/goal.js +237 -0
  9. package/dist/autoresearch/goal.js.map +1 -0
  10. package/dist/autoresearch/skill-validation.d.ts +1 -0
  11. package/dist/autoresearch/skill-validation.d.ts.map +1 -1
  12. package/dist/autoresearch/skill-validation.js +10 -3
  13. package/dist/autoresearch/skill-validation.js.map +1 -1
  14. package/dist/catalog/__tests__/generator.test.js +9 -4
  15. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  16. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +20 -1
  17. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  18. package/dist/catalog/__tests__/schema.test.js +14 -3
  19. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  20. package/dist/catalog/schema.js +1 -1
  21. package/dist/catalog/schema.js.map +1 -1
  22. package/dist/cli/__tests__/autoresearch-goal.test.d.ts +2 -0
  23. package/dist/cli/__tests__/autoresearch-goal.test.d.ts.map +1 -0
  24. package/dist/cli/__tests__/autoresearch-goal.test.js +194 -0
  25. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -0
  26. package/dist/cli/__tests__/cleanup.test.js +82 -1
  27. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  28. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -4
  29. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  30. package/dist/cli/__tests__/doctor-warning-copy.test.js +23 -0
  31. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  32. package/dist/cli/__tests__/explore.test.js +8 -1
  33. package/dist/cli/__tests__/explore.test.js.map +1 -1
  34. package/dist/cli/__tests__/index.test.js +82 -3
  35. package/dist/cli/__tests__/index.test.js.map +1 -1
  36. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  37. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  38. package/dist/cli/__tests__/native-assets.test.js +26 -1
  39. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  40. package/dist/cli/__tests__/package-bin-contract.test.js +2 -2
  41. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  42. package/dist/cli/__tests__/performance-goal.test.d.ts +2 -0
  43. package/dist/cli/__tests__/performance-goal.test.d.ts.map +1 -0
  44. package/dist/cli/__tests__/performance-goal.test.js +144 -0
  45. package/dist/cli/__tests__/performance-goal.test.js.map +1 -0
  46. package/dist/cli/__tests__/question.test.js +8 -0
  47. package/dist/cli/__tests__/question.test.js.map +1 -1
  48. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts +2 -0
  49. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts.map +1 -0
  50. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +31 -0
  51. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -0
  52. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +5 -4
  53. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  54. package/dist/cli/__tests__/ralph-prd-smoke.test.js +7 -0
  55. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  56. package/dist/cli/__tests__/setup-install-mode.test.js +57 -21
  57. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  58. package/dist/cli/__tests__/setup-refresh.test.js +27 -8
  59. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  60. package/dist/cli/__tests__/setup-scope.test.js +18 -9
  61. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  62. package/dist/cli/__tests__/setup-skill-validation.test.js +11 -11
  63. package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -1
  64. package/dist/cli/__tests__/setup-skills-overwrite.test.js +12 -12
  65. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  66. package/dist/cli/__tests__/team.test.js +187 -0
  67. package/dist/cli/__tests__/team.test.js.map +1 -1
  68. package/dist/cli/__tests__/ultragoal.test.d.ts +2 -0
  69. package/dist/cli/__tests__/ultragoal.test.d.ts.map +1 -0
  70. package/dist/cli/__tests__/ultragoal.test.js +106 -0
  71. package/dist/cli/__tests__/ultragoal.test.js.map +1 -0
  72. package/dist/cli/__tests__/uninstall.test.js +11 -0
  73. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  74. package/dist/cli/autoresearch-goal.d.ts +3 -0
  75. package/dist/cli/autoresearch-goal.d.ts.map +1 -0
  76. package/dist/cli/autoresearch-goal.js +175 -0
  77. package/dist/cli/autoresearch-goal.js.map +1 -0
  78. package/dist/cli/cleanup.d.ts +3 -1
  79. package/dist/cli/cleanup.d.ts.map +1 -1
  80. package/dist/cli/cleanup.js +42 -2
  81. package/dist/cli/cleanup.js.map +1 -1
  82. package/dist/cli/doctor.d.ts.map +1 -1
  83. package/dist/cli/doctor.js +49 -0
  84. package/dist/cli/doctor.js.map +1 -1
  85. package/dist/cli/explore.d.ts.map +1 -1
  86. package/dist/cli/explore.js +10 -2
  87. package/dist/cli/explore.js.map +1 -1
  88. package/dist/cli/index.d.ts +6 -2
  89. package/dist/cli/index.d.ts.map +1 -1
  90. package/dist/cli/index.js +145 -18
  91. package/dist/cli/index.js.map +1 -1
  92. package/dist/cli/native-assets.js +1 -1
  93. package/dist/cli/native-assets.js.map +1 -1
  94. package/dist/cli/performance-goal.d.ts +3 -0
  95. package/dist/cli/performance-goal.d.ts.map +1 -0
  96. package/dist/cli/performance-goal.js +186 -0
  97. package/dist/cli/performance-goal.js.map +1 -0
  98. package/dist/cli/ralph.d.ts.map +1 -1
  99. package/dist/cli/ralph.js +8 -0
  100. package/dist/cli/ralph.js.map +1 -1
  101. package/dist/cli/setup.d.ts.map +1 -1
  102. package/dist/cli/setup.js +13 -6
  103. package/dist/cli/setup.js.map +1 -1
  104. package/dist/cli/team.d.ts +2 -0
  105. package/dist/cli/team.d.ts.map +1 -1
  106. package/dist/cli/team.js +72 -17
  107. package/dist/cli/team.js.map +1 -1
  108. package/dist/cli/tmux-hook.d.ts.map +1 -1
  109. package/dist/cli/tmux-hook.js +2 -1
  110. package/dist/cli/tmux-hook.js.map +1 -1
  111. package/dist/cli/ultragoal.d.ts +3 -0
  112. package/dist/cli/ultragoal.d.ts.map +1 -0
  113. package/dist/cli/ultragoal.js +191 -0
  114. package/dist/cli/ultragoal.js.map +1 -0
  115. package/dist/cli/uninstall.d.ts.map +1 -1
  116. package/dist/cli/uninstall.js +4 -2
  117. package/dist/cli/uninstall.js.map +1 -1
  118. package/dist/config/__tests__/generator-idempotent.test.js +12 -1
  119. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  120. package/dist/config/__tests__/generator-notify.test.js +5 -0
  121. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  122. package/dist/config/commit-lore-guard.d.ts +3 -0
  123. package/dist/config/commit-lore-guard.d.ts.map +1 -0
  124. package/dist/config/commit-lore-guard.js +9 -0
  125. package/dist/config/commit-lore-guard.js.map +1 -0
  126. package/dist/config/generator.d.ts +3 -2
  127. package/dist/config/generator.d.ts.map +1 -1
  128. package/dist/config/generator.js +52 -8
  129. package/dist/config/generator.js.map +1 -1
  130. package/dist/config/omx-first-party-mcp.d.ts +1 -0
  131. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  132. package/dist/config/omx-first-party-mcp.js +4 -1
  133. package/dist/config/omx-first-party-mcp.js.map +1 -1
  134. package/dist/goal-workflows/__tests__/artifacts.test.d.ts +2 -0
  135. package/dist/goal-workflows/__tests__/artifacts.test.d.ts.map +1 -0
  136. package/dist/goal-workflows/__tests__/artifacts.test.js +96 -0
  137. package/dist/goal-workflows/__tests__/artifacts.test.js.map +1 -0
  138. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts +2 -0
  139. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts.map +1 -0
  140. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +54 -0
  141. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -0
  142. package/dist/goal-workflows/artifacts.d.ts +62 -0
  143. package/dist/goal-workflows/artifacts.d.ts.map +1 -0
  144. package/dist/goal-workflows/artifacts.js +132 -0
  145. package/dist/goal-workflows/artifacts.js.map +1 -0
  146. package/dist/goal-workflows/codex-goal-snapshot.d.ts +28 -0
  147. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -0
  148. package/dist/goal-workflows/codex-goal-snapshot.js +110 -0
  149. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -0
  150. package/dist/goal-workflows/handoff.d.ts +10 -0
  151. package/dist/goal-workflows/handoff.d.ts.map +1 -0
  152. package/dist/goal-workflows/handoff.js +31 -0
  153. package/dist/goal-workflows/handoff.js.map +1 -0
  154. package/dist/goal-workflows/validation.d.ts +13 -0
  155. package/dist/goal-workflows/validation.d.ts.map +1 -0
  156. package/dist/goal-workflows/validation.js +36 -0
  157. package/dist/goal-workflows/validation.js.map +1 -0
  158. package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
  159. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  160. package/dist/hooks/__tests__/keyword-detector.test.js +45 -32
  161. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  162. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +3 -3
  163. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  164. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
  165. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  166. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +17 -24
  167. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  168. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +3 -3
  169. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  170. package/dist/hooks/__tests__/task-size-detector.test.js +1 -1
  171. package/dist/hooks/__tests__/task-size-detector.test.js.map +1 -1
  172. package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
  173. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  174. package/dist/hooks/__tests__/visual-verdict-loop.test.js +7 -11
  175. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  176. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  177. package/dist/hooks/agents-overlay.js +2 -2
  178. package/dist/hooks/agents-overlay.js.map +1 -1
  179. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  180. package/dist/hooks/keyword-detector.js +12 -13
  181. package/dist/hooks/keyword-detector.js.map +1 -1
  182. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  183. package/dist/hooks/keyword-registry.js +2 -10
  184. package/dist/hooks/keyword-registry.js.map +1 -1
  185. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  186. package/dist/hooks/prompt-guidance-contract.js +0 -4
  187. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  188. package/dist/hooks/session.js +2 -2
  189. package/dist/hooks/session.js.map +1 -1
  190. package/dist/hooks/task-size-detector.d.ts.map +1 -1
  191. package/dist/hooks/task-size-detector.js +1 -0
  192. package/dist/hooks/task-size-detector.js.map +1 -1
  193. package/dist/hud/__tests__/reconcile.test.js +29 -7
  194. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  195. package/dist/hud/reconcile.d.ts +2 -1
  196. package/dist/hud/reconcile.d.ts.map +1 -1
  197. package/dist/hud/reconcile.js +12 -0
  198. package/dist/hud/reconcile.js.map +1 -1
  199. package/dist/mcp/__tests__/bootstrap.test.js +15 -2
  200. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  201. package/dist/mcp/__tests__/state-paths.test.js +54 -0
  202. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  203. package/dist/mcp/__tests__/state-server.test.js +36 -0
  204. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  205. package/dist/mcp/bootstrap.d.ts +1 -1
  206. package/dist/mcp/bootstrap.d.ts.map +1 -1
  207. package/dist/mcp/bootstrap.js +9 -7
  208. package/dist/mcp/bootstrap.js.map +1 -1
  209. package/dist/mcp/state-paths.d.ts +17 -0
  210. package/dist/mcp/state-paths.d.ts.map +1 -1
  211. package/dist/mcp/state-paths.js +36 -2
  212. package/dist/mcp/state-paths.js.map +1 -1
  213. package/dist/modes/__tests__/base-session-scope.test.js +26 -0
  214. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  215. package/dist/modes/base.d.ts +1 -0
  216. package/dist/modes/base.d.ts.map +1 -1
  217. package/dist/modes/base.js +35 -5
  218. package/dist/modes/base.js.map +1 -1
  219. package/dist/notifications/__tests__/http-client.test.d.ts +2 -0
  220. package/dist/notifications/__tests__/http-client.test.d.ts.map +1 -0
  221. package/dist/notifications/__tests__/http-client.test.js +90 -0
  222. package/dist/notifications/__tests__/http-client.test.js.map +1 -0
  223. package/dist/notifications/__tests__/notifier.test.js +22 -60
  224. package/dist/notifications/__tests__/notifier.test.js.map +1 -1
  225. package/dist/notifications/dispatcher.d.ts.map +1 -1
  226. package/dist/notifications/dispatcher.js +35 -60
  227. package/dist/notifications/dispatcher.js.map +1 -1
  228. package/dist/notifications/http-client.d.ts +22 -0
  229. package/dist/notifications/http-client.d.ts.map +1 -0
  230. package/dist/notifications/http-client.js +298 -0
  231. package/dist/notifications/http-client.js.map +1 -0
  232. package/dist/notifications/notifier.d.ts +3 -2
  233. package/dist/notifications/notifier.d.ts.map +1 -1
  234. package/dist/notifications/notifier.js +17 -22
  235. package/dist/notifications/notifier.js.map +1 -1
  236. package/dist/openclaw/__tests__/dispatcher.test.js +62 -1
  237. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  238. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  239. package/dist/openclaw/dispatcher.js +3 -2
  240. package/dist/openclaw/dispatcher.js.map +1 -1
  241. package/dist/performance-goal/artifacts.d.ts +76 -0
  242. package/dist/performance-goal/artifacts.d.ts.map +1 -0
  243. package/dist/performance-goal/artifacts.js +221 -0
  244. package/dist/performance-goal/artifacts.js.map +1 -0
  245. package/dist/pipeline/__tests__/stages.test.js +30 -5
  246. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  247. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  248. package/dist/pipeline/stages/team-exec.js +2 -19
  249. package/dist/pipeline/stages/team-exec.js.map +1 -1
  250. package/dist/planning/__tests__/artifacts.test.js +16 -1
  251. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  252. package/dist/planning/artifacts.d.ts +1 -0
  253. package/dist/planning/artifacts.d.ts.map +1 -1
  254. package/dist/planning/artifacts.js +9 -12
  255. package/dist/planning/artifacts.js.map +1 -1
  256. package/dist/ralplan/__tests__/runtime.test.js +2 -0
  257. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  258. package/dist/ralplan/runtime.d.ts.map +1 -1
  259. package/dist/ralplan/runtime.js +6 -0
  260. package/dist/ralplan/runtime.js.map +1 -1
  261. package/dist/scripts/__tests__/codex-native-hook.test.js +1516 -205
  262. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  263. package/dist/scripts/__tests__/hook-derived-watcher.test.js +33 -1
  264. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  265. package/dist/scripts/__tests__/run-test-files.test.js +36 -0
  266. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  267. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  268. package/dist/scripts/codex-native-hook.js +497 -51
  269. package/dist/scripts/codex-native-hook.js.map +1 -1
  270. package/dist/scripts/codex-native-pre-post.d.ts +7 -0
  271. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  272. package/dist/scripts/codex-native-pre-post.js +222 -19
  273. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  274. package/dist/scripts/hook-derived-watcher.js +2 -1
  275. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  276. package/dist/scripts/notify-fallback-watcher.js +2 -1
  277. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  278. package/dist/scripts/notify-hook/orchestration-intent.d.ts +1 -2
  279. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  280. package/dist/scripts/notify-hook/orchestration-intent.js +2 -3
  281. package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -1
  282. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +0 -2
  283. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  284. package/dist/scripts/notify-hook/team-leader-nudge.js +8 -60
  285. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  286. package/dist/scripts/notify-hook/team-worker-stop.d.ts +15 -0
  287. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -0
  288. package/dist/scripts/notify-hook/team-worker-stop.js +224 -0
  289. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -0
  290. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  291. package/dist/scripts/notify-hook/team-worker.js +26 -18
  292. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  293. package/dist/scripts/run-test-files.js +17 -1
  294. package/dist/scripts/run-test-files.js.map +1 -1
  295. package/dist/scripts/sync-plugin-mirror.js +2 -2
  296. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  297. package/dist/state/__tests__/operations.test.js +26 -0
  298. package/dist/state/__tests__/operations.test.js.map +1 -1
  299. package/dist/state/__tests__/skill-active.test.js +35 -0
  300. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  301. package/dist/state/operations.d.ts +3 -1
  302. package/dist/state/operations.d.ts.map +1 -1
  303. package/dist/state/operations.js +8 -4
  304. package/dist/state/operations.js.map +1 -1
  305. package/dist/state/skill-active.d.ts +1 -0
  306. package/dist/state/skill-active.d.ts.map +1 -1
  307. package/dist/state/skill-active.js +54 -13
  308. package/dist/state/skill-active.js.map +1 -1
  309. package/dist/team/__tests__/api-interop.test.js +59 -0
  310. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  311. package/dist/team/__tests__/approved-execution.test.d.ts +2 -0
  312. package/dist/team/__tests__/approved-execution.test.d.ts.map +1 -0
  313. package/dist/team/__tests__/approved-execution.test.js +124 -0
  314. package/dist/team/__tests__/approved-execution.test.js.map +1 -0
  315. package/dist/team/__tests__/delivery-e2e-smoke.test.js +2 -4
  316. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  317. package/dist/team/__tests__/delivery-log.test.d.ts +2 -0
  318. package/dist/team/__tests__/delivery-log.test.d.ts.map +1 -0
  319. package/dist/team/__tests__/delivery-log.test.js +44 -0
  320. package/dist/team/__tests__/delivery-log.test.js.map +1 -0
  321. package/dist/team/__tests__/role-router.test.js +4 -4
  322. package/dist/team/__tests__/role-router.test.js.map +1 -1
  323. package/dist/team/__tests__/runtime-boxed-state.test.d.ts +2 -0
  324. package/dist/team/__tests__/runtime-boxed-state.test.d.ts.map +1 -0
  325. package/dist/team/__tests__/runtime-boxed-state.test.js +39 -0
  326. package/dist/team/__tests__/runtime-boxed-state.test.js.map +1 -0
  327. package/dist/team/__tests__/runtime.test.js +118 -6
  328. package/dist/team/__tests__/runtime.test.js.map +1 -1
  329. package/dist/team/__tests__/state-root.test.js +13 -0
  330. package/dist/team/__tests__/state-root.test.js.map +1 -1
  331. package/dist/team/__tests__/tmux-session.test.js +3 -0
  332. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  333. package/dist/team/__tests__/worker-bootstrap.test.js +50 -0
  334. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  335. package/dist/team/api-interop.d.ts.map +1 -1
  336. package/dist/team/api-interop.js +4 -3
  337. package/dist/team/api-interop.js.map +1 -1
  338. package/dist/team/approved-execution.d.ts +37 -0
  339. package/dist/team/approved-execution.d.ts.map +1 -0
  340. package/dist/team/approved-execution.js +136 -0
  341. package/dist/team/approved-execution.js.map +1 -0
  342. package/dist/team/delivery-log.d.ts.map +1 -1
  343. package/dist/team/delivery-log.js +2 -1
  344. package/dist/team/delivery-log.js.map +1 -1
  345. package/dist/team/followup-planner.js +2 -2
  346. package/dist/team/followup-planner.js.map +1 -1
  347. package/dist/team/goal-workflow.d.ts +20 -0
  348. package/dist/team/goal-workflow.d.ts.map +1 -0
  349. package/dist/team/goal-workflow.js +57 -0
  350. package/dist/team/goal-workflow.js.map +1 -0
  351. package/dist/team/orchestrator.js +2 -2
  352. package/dist/team/orchestrator.js.map +1 -1
  353. package/dist/team/role-router.js +5 -5
  354. package/dist/team/role-router.js.map +1 -1
  355. package/dist/team/runtime.d.ts +6 -0
  356. package/dist/team/runtime.d.ts.map +1 -1
  357. package/dist/team/runtime.js +46 -6
  358. package/dist/team/runtime.js.map +1 -1
  359. package/dist/team/scaling.d.ts.map +1 -1
  360. package/dist/team/scaling.js +2 -0
  361. package/dist/team/scaling.js.map +1 -1
  362. package/dist/team/tmux-session.d.ts.map +1 -1
  363. package/dist/team/tmux-session.js +4 -2
  364. package/dist/team/tmux-session.js.map +1 -1
  365. package/dist/team/worker-bootstrap.d.ts +2 -0
  366. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  367. package/dist/team/worker-bootstrap.js +19 -2
  368. package/dist/team/worker-bootstrap.js.map +1 -1
  369. package/dist/ultragoal/__tests__/artifacts.test.d.ts +2 -0
  370. package/dist/ultragoal/__tests__/artifacts.test.d.ts.map +1 -0
  371. package/dist/ultragoal/__tests__/artifacts.test.js +93 -0
  372. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -0
  373. package/dist/ultragoal/artifacts.d.ts +89 -0
  374. package/dist/ultragoal/artifacts.d.ts.map +1 -0
  375. package/dist/ultragoal/artifacts.js +233 -0
  376. package/dist/ultragoal/artifacts.js.map +1 -0
  377. package/dist/utils/__tests__/agents-model-table.test.js +3 -1
  378. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  379. package/dist/utils/__tests__/paths.test.js +31 -1
  380. package/dist/utils/__tests__/paths.test.js.map +1 -1
  381. package/dist/utils/agents-model-table.d.ts.map +1 -1
  382. package/dist/utils/agents-model-table.js +12 -1
  383. package/dist/utils/agents-model-table.js.map +1 -1
  384. package/dist/utils/paths.d.ts +2 -0
  385. package/dist/utils/paths.d.ts.map +1 -1
  386. package/dist/utils/paths.js +23 -7
  387. package/dist/utils/paths.js.map +1 -1
  388. package/dist/verification/__tests__/ci-rust-gates.test.js +30 -19
  389. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  390. package/package.json +5 -5
  391. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  392. package/plugins/oh-my-codex/skills/ask/SKILL.md +58 -0
  393. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +36 -0
  394. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +2 -2
  395. package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +65 -0
  396. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  397. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -3
  398. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -2
  399. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +49 -0
  400. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +9 -9
  401. package/prompts/api-reviewer.md +1 -1
  402. package/prompts/code-reviewer.md +2 -0
  403. package/prompts/performance-reviewer.md +1 -1
  404. package/prompts/quality-reviewer.md +1 -1
  405. package/prompts/quality-strategist.md +2 -2
  406. package/prompts/style-reviewer.md +1 -1
  407. package/prompts/test-engineer.md +1 -1
  408. package/skills/ask/SKILL.md +58 -0
  409. package/skills/ask-claude/SKILL.md +3 -54
  410. package/skills/ask-gemini/SKILL.md +3 -54
  411. package/skills/autoresearch-goal/SKILL.md +36 -0
  412. package/skills/build-fix/SKILL.md +4 -139
  413. package/skills/deepsearch/SKILL.md +4 -32
  414. package/skills/ecomode/SKILL.md +4 -108
  415. package/skills/help/SKILL.md +4 -196
  416. package/skills/note/SKILL.md +4 -56
  417. package/skills/omx-setup/SKILL.md +2 -2
  418. package/skills/performance-goal/SKILL.md +65 -0
  419. package/skills/plan/SKILL.md +1 -1
  420. package/skills/ralph/SKILL.md +22 -3
  421. package/skills/ralph-init/SKILL.md +4 -40
  422. package/skills/review/SKILL.md +4 -32
  423. package/skills/security-review/SKILL.md +4 -294
  424. package/skills/swarm/SKILL.md +4 -19
  425. package/skills/tdd/SKILL.md +4 -100
  426. package/skills/team/SKILL.md +6 -2
  427. package/skills/trace/SKILL.md +4 -27
  428. package/skills/ultragoal/SKILL.md +49 -0
  429. package/skills/visual-ralph/SKILL.md +9 -9
  430. package/skills/visual-verdict/SKILL.md +4 -70
  431. package/skills/web-clone/SKILL.md +4 -18
  432. package/src/scripts/__tests__/codex-native-hook.test.ts +1654 -157
  433. package/src/scripts/__tests__/hook-derived-watcher.test.ts +45 -1
  434. package/src/scripts/__tests__/run-test-files.test.ts +46 -0
  435. package/src/scripts/codex-native-hook.ts +592 -52
  436. package/src/scripts/codex-native-pre-post.ts +252 -20
  437. package/src/scripts/hook-derived-watcher.ts +2 -1
  438. package/src/scripts/notify-fallback-watcher.ts +2 -1
  439. package/src/scripts/notify-hook/orchestration-intent.ts +1 -3
  440. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -63
  441. package/src/scripts/notify-hook/team-worker-stop.ts +246 -0
  442. package/src/scripts/notify-hook/team-worker.ts +23 -14
  443. package/src/scripts/run-test-files.ts +20 -1
  444. package/src/scripts/sync-plugin-mirror.ts +2 -2
  445. package/templates/catalog-manifest.json +45 -27
  446. package/plugins/oh-my-codex/skills/ask-claude/SKILL.md +0 -61
  447. package/plugins/oh-my-codex/skills/ask-gemini/SKILL.md +0 -61
  448. package/plugins/oh-my-codex/skills/help/SKILL.md +0 -202
  449. package/plugins/oh-my-codex/skills/note/SKILL.md +0 -62
  450. package/plugins/oh-my-codex/skills/security-review/SKILL.md +0 -300
  451. package/plugins/oh-my-codex/skills/trace/SKILL.md +0 -33
  452. package/plugins/oh-my-codex/skills/visual-verdict/SKILL.md +0 -76
@@ -1,10 +1,11 @@
1
1
  import { execFileSync } from "child_process";
2
2
  import { closeSync, existsSync, openSync, readFileSync, readSync } from "fs";
3
3
  import { mkdir, readFile, readdir, writeFile } from "fs/promises";
4
- import { join, relative, resolve } from "path";
4
+ import { extname, join, relative, resolve } from "path";
5
5
  import { pathToFileURL } from "url";
6
- import { readModeState, readModeStateForSession, updateModeState } from "../modes/base.js";
6
+ import { readModeState, readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
7
7
  import {
8
+ extractSessionIdFromInitializedStatePath,
8
9
  listActiveSkills,
9
10
  readVisibleSkillActiveState,
10
11
  } from "../state/skill-active.js";
@@ -45,11 +46,16 @@ import {
45
46
  resolveEffectiveAutoNudgeResponse,
46
47
  } from "./notify-hook/auto-nudge.js";
47
48
  import {
49
+ SLOPPY_FALLBACK_GROUNDING_PATTERNS,
50
+ SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS,
51
+ SLOPPY_FALLBACK_PHRASE_PATTERNS,
48
52
  buildNativePostToolUseOutput,
49
53
  buildNativePreToolUseOutput,
50
54
  detectMcpTransportFailure,
55
+ hasAnyPattern,
51
56
  } from "./codex-native-pre-post.js";
52
57
  import { handleTeamWorkerPostToolUseSuccess } from "./notify-hook/team-worker-posttooluse.js";
58
+ import { maybeNudgeLeaderForAllowedWorkerStop } from "./notify-hook/team-worker-stop.js";
53
59
  import {
54
60
  resolveCodexExecutionSurface,
55
61
  type CodexLauncherKind,
@@ -59,10 +65,10 @@ import {
59
65
  buildNativeHookEvent,
60
66
  } from "../hooks/extensibility/events.js";
61
67
  import type { HookEventEnvelope } from "../hooks/extensibility/types.js";
62
- import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
68
+ import { dispatchHookEventRuntime } from "../hooks/extensibility/runtime.js";
63
69
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
64
70
  import { onSessionStart as buildWikiSessionStartContext } from "../wiki/lifecycle.js";
65
- import { readAutoresearchCompletionStatus, readAutoresearchModeState } from "../autoresearch/skill-validation.js";
71
+ import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
66
72
  import { readRunState } from "../runtime/run-state.js";
67
73
  import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
68
74
  import { triagePrompt } from "../hooks/triage-heuristic.js";
@@ -109,8 +115,8 @@ export interface NativeHookDispatchResult {
109
115
 
110
116
  const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancelled"]);
111
117
  const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
112
- const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
113
- const TEAM_WORKER_STOP_ACTIVE_STATES = new Set(["working", "blocked"]);
118
+ const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
119
+ const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
114
120
  const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
115
121
  const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
116
122
  /^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
@@ -260,6 +266,26 @@ async function nativeSubagentSessionStartBelongsToCanonicalSession(
260
266
  return summary.allThreadIds.includes(parentThreadId);
261
267
  }
262
268
 
269
+ async function isNativeSubagentHook(
270
+ cwd: string,
271
+ canonicalSessionId: string,
272
+ nativeSessionId: string,
273
+ threadId: string,
274
+ ): Promise<boolean> {
275
+ const sessionId = canonicalSessionId.trim();
276
+ if (!sessionId) return false;
277
+
278
+ const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
279
+ if (!summary) return false;
280
+
281
+ const candidateIds = [nativeSessionId, threadId]
282
+ .map((value) => value.trim())
283
+ .filter(Boolean);
284
+ if (candidateIds.length === 0) return false;
285
+
286
+ return candidateIds.some((id) => summary.allSubagentThreadIds.includes(id));
287
+ }
288
+
263
289
  async function recordIgnoredNativeSubagentSessionStart(
264
290
  cwd: string,
265
291
  canonicalSessionId: string,
@@ -434,7 +460,7 @@ async function readActiveAutoresearchState(
434
460
  ): Promise<Record<string, unknown> | null> {
435
461
  const normalizedSessionId = sessionId?.trim() || undefined;
436
462
  if (!normalizedSessionId) return null;
437
- const state = await readAutoresearchModeState(cwd, normalizedSessionId);
463
+ const state = await readAutoresearchModeStateForActiveDecision(cwd, normalizedSessionId);
438
464
  if (state?.active !== true) return null;
439
465
  if (!isNonTerminalPhase(state.current_phase ?? state.currentPhase ?? 'executing')) return null;
440
466
  return state;
@@ -528,6 +554,19 @@ async function isVisibleRalphActiveForSession(cwd: string, sessionId: string): P
528
554
  ));
529
555
  }
530
556
 
557
+ async function hasConsistentRalphSkillActivation(cwd: string, sessionId: string): Promise<boolean> {
558
+ const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
559
+ if (!canonicalState) return true;
560
+
561
+ const initializedMode = safeString(canonicalState.initialized_mode).trim();
562
+ if (initializedMode && initializedMode !== "ralph") return true;
563
+
564
+ const initializedPathSessionId = extractSessionIdFromInitializedStatePath(canonicalState.initialized_state_path);
565
+ if (initializedPathSessionId && initializedPathSessionId !== sessionId) return false;
566
+
567
+ return true;
568
+ }
569
+
531
570
  async function readActiveRalphState(
532
571
  stateDir: string,
533
572
  preferredSessionId?: string,
@@ -581,6 +620,7 @@ async function readActiveRalphState(
581
620
  currentNativeSessionId,
582
621
  tmuxPaneId: safeString(ownerContext?.tmuxPaneId).trim(),
583
622
  })
623
+ && await hasConsistentRalphSkillActivation(cwd, sessionId)
584
624
  ) {
585
625
  return { state: sessionScoped, path: sessionScopedPath };
586
626
  }
@@ -709,6 +749,192 @@ function tryReadGitValue(cwd: string, args: string[]): string | null {
709
749
  }
710
750
  }
711
751
 
752
+ interface SloppyFallbackDiffFinding {
753
+ path: string;
754
+ line: string;
755
+ source: "staged" | "unstaged" | "untracked";
756
+ }
757
+
758
+ const SOURCE_DIFF_EXTENSIONS = new Set([
759
+ ".c",
760
+ ".cc",
761
+ ".cjs",
762
+ ".cpp",
763
+ ".cs",
764
+ ".cts",
765
+ ".go",
766
+ ".h",
767
+ ".hpp",
768
+ ".java",
769
+ ".js",
770
+ ".jsx",
771
+ ".kt",
772
+ ".mjs",
773
+ ".mts",
774
+ ".php",
775
+ ".py",
776
+ ".rb",
777
+ ".rs",
778
+ ".sh",
779
+ ".swift",
780
+ ".ts",
781
+ ".tsx",
782
+ ]);
783
+
784
+ function gitOutput(cwd: string, args: string[]): string {
785
+ try {
786
+ return execFileSync("git", args, {
787
+ cwd,
788
+ encoding: "utf-8",
789
+ stdio: ["ignore", "pipe", "ignore"],
790
+ windowsHide: true,
791
+ maxBuffer: 10 * 1024 * 1024,
792
+ });
793
+ } catch {
794
+ return "";
795
+ }
796
+ }
797
+
798
+ function normalizeGitPath(path: string): string {
799
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
800
+ }
801
+
802
+ function isDiffAuditableSourcePath(path: string): boolean {
803
+ const normalized = normalizeGitPath(path).toLowerCase();
804
+ if (!normalized || normalized.startsWith(".git/") || normalized.startsWith(".omx/")) return false;
805
+ if (/(^|\/)(?:docs?|documentation|changelog|changeset|\.github)(?:\/|$)/i.test(normalized)) return false;
806
+ if (/(^|\/)(?:__tests__|__test__|test|tests|spec|specs|fixtures?|mocks?)(?:\/|$)/i.test(normalized)) return false;
807
+ if (/(?:^|\/)[^\/]+\.(?:test|spec)\.[^.\/]+$/i.test(normalized)) return false;
808
+ if (/(?:^|\/)(?:readme|changelog|changes|license|notice)(?:\.[^\/]*)?$/i.test(normalized)) return false;
809
+ if (/\.(?:md|mdx|markdown|txt|rst|adoc|ya?ml|json|lock)$/i.test(normalized)) return false;
810
+ return SOURCE_DIFF_EXTENSIONS.has(extname(normalized));
811
+ }
812
+
813
+ function isDiffHeaderLine(line: string): boolean {
814
+ return line.startsWith("+++") || line.startsWith("---") || line.startsWith("@@") || line.startsWith("diff --git ");
815
+ }
816
+
817
+ function isSuspiciousSloppyFallbackAddedLine(line: string, nearbyContext: string): boolean {
818
+ const trimmed = line.trim();
819
+ if (!trimmed) return false;
820
+ if (!hasAnyPattern(trimmed, SLOPPY_FALLBACK_PHRASE_PATTERNS)) return false;
821
+ if (!hasAnyPattern(trimmed, SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS)) return false;
822
+ if (hasAnyPattern(nearbyContext, SLOPPY_FALLBACK_GROUNDING_PATTERNS)) return false;
823
+ if (/compatib(?:le|ility)|fail-?safe|tested|regression|coverage|because|issue|PR\s*#?\d|#\d/i.test(nearbyContext)) return false;
824
+ return true;
825
+ }
826
+
827
+ interface SloppyFallbackCandidateLine {
828
+ text: string;
829
+ added: boolean;
830
+ }
831
+
832
+ function collectFindingsFromCandidateLines(
833
+ path: string,
834
+ lines: SloppyFallbackCandidateLine[],
835
+ source: SloppyFallbackDiffFinding["source"],
836
+ ): SloppyFallbackDiffFinding[] {
837
+ if (!path || !isDiffAuditableSourcePath(path)) return [];
838
+ const findings: SloppyFallbackDiffFinding[] = [];
839
+ for (let index = 0; index < lines.length; index += 1) {
840
+ const candidate = lines[index];
841
+ if (!candidate?.added) continue;
842
+ const nearbyContext = lines
843
+ .slice(Math.max(0, index - 2), Math.min(lines.length, index + 3))
844
+ .map((line) => line.text)
845
+ .join("\n");
846
+ if (isSuspiciousSloppyFallbackAddedLine(candidate.text, nearbyContext)) {
847
+ findings.push({ path, line: candidate.text.trim(), source });
848
+ }
849
+ }
850
+ return findings;
851
+ }
852
+
853
+ function collectSloppyFallbackFindingsFromPatch(
854
+ patch: string,
855
+ source: SloppyFallbackDiffFinding["source"],
856
+ ): SloppyFallbackDiffFinding[] {
857
+ const findings: SloppyFallbackDiffFinding[] = [];
858
+ let currentPath = "";
859
+ let hunkLines: SloppyFallbackCandidateLine[] = [];
860
+
861
+ const flushHunk = () => {
862
+ findings.push(...collectFindingsFromCandidateLines(currentPath, hunkLines, source));
863
+ hunkLines = [];
864
+ };
865
+
866
+ for (const rawLine of patch.split(/\r?\n/)) {
867
+ const fileMatch = rawLine.match(/^diff --git a\/(.*?) b\/(.*)$/);
868
+ if (fileMatch) {
869
+ flushHunk();
870
+ currentPath = normalizeGitPath(fileMatch[2] || fileMatch[1] || "");
871
+ continue;
872
+ }
873
+ const renameMatch = rawLine.match(/^\+\+\+ b\/(.*)$/);
874
+ if (renameMatch) {
875
+ currentPath = normalizeGitPath(renameMatch[1] || currentPath);
876
+ continue;
877
+ }
878
+ if (rawLine.startsWith("@@")) {
879
+ flushHunk();
880
+ continue;
881
+ }
882
+ if (!currentPath || !isDiffAuditableSourcePath(currentPath) || isDiffHeaderLine(rawLine)) continue;
883
+ if (rawLine.startsWith("+")) {
884
+ hunkLines.push({ text: rawLine.slice(1), added: true });
885
+ } else if (rawLine.startsWith(" ")) {
886
+ hunkLines.push({ text: rawLine.slice(1), added: false });
887
+ }
888
+ }
889
+ flushHunk();
890
+ return findings;
891
+ }
892
+
893
+ function collectSloppyFallbackFindingsFromUntracked(cwd: string): SloppyFallbackDiffFinding[] {
894
+ const output = gitOutput(cwd, ["ls-files", "--others", "--exclude-standard", "-z"]);
895
+ if (!output) return [];
896
+ const findings: SloppyFallbackDiffFinding[] = [];
897
+ for (const rawPath of output.split("\0")) {
898
+ const path = normalizeGitPath(rawPath.trim());
899
+ if (!path || !isDiffAuditableSourcePath(path)) continue;
900
+ let content = "";
901
+ try {
902
+ content = readFileSync(join(cwd, path), "utf-8");
903
+ } catch {
904
+ continue;
905
+ }
906
+ findings.push(...collectFindingsFromCandidateLines(path, content.split(/\r?\n/).map((text) => ({ text, added: true })), "untracked"));
907
+ }
908
+ return findings;
909
+ }
910
+
911
+ function findSloppyFallbackDiffFindings(cwd: string): SloppyFallbackDiffFinding[] {
912
+ const layout = findGitLayout(cwd);
913
+ if (!layout) return [];
914
+ const auditRoot = layout.worktreeRoot;
915
+ return [
916
+ ...collectSloppyFallbackFindingsFromPatch(gitOutput(auditRoot, ["diff", "--cached", "--no-ext-diff", "--unified=3"]), "staged"),
917
+ ...collectSloppyFallbackFindingsFromPatch(gitOutput(auditRoot, ["diff", "--no-ext-diff", "--unified=3"]), "unstaged"),
918
+ ...collectSloppyFallbackFindingsFromUntracked(auditRoot),
919
+ ];
920
+ }
921
+
922
+ function buildSloppyFallbackDiffStopOutput(findings: SloppyFallbackDiffFinding[]): Record<string, unknown> | null {
923
+ if (findings.length === 0) return null;
924
+ const preview = findings
925
+ .slice(0, 3)
926
+ .map((finding) => `${finding.path} (${finding.source}): ${finding.line}`)
927
+ .join("; ");
928
+ const systemMessage =
929
+ `Sloppy fallback/workaround diff audit detected ungrounded fallback code in added source lines: ${preview}. `
930
+ + "Continue by replacing the bypass/workaround with a grounded design, or add explicit compatibility/fail-safe/tested/issue rationale near the code if the fallback is intentional.";
931
+ return {
932
+ decision: "block",
933
+ reason: systemMessage,
934
+ stopReason: "sloppy_fallback_diff_audit",
935
+ systemMessage,
936
+ };
937
+ }
712
938
 
713
939
  function localExcludeAlreadyIgnoresOmx(cwd: string): boolean {
714
940
  const layout = findGitLayout(cwd);
@@ -1101,6 +1327,9 @@ function buildAdditionalContextMessage(
1101
1327
  const ultraworkPromptActivationNote = skillState?.initialized_mode === "ultrawork"
1102
1328
  ? "Ultrawork protocol: ground the task before editing, define pass/fail acceptance criteria, keep shared-file work local, and use direct-tool plus background evidence lanes only for truly independent work. Direct ultrawork provides lightweight verification only; Ralph owns persistence and the full verified-completion promise."
1103
1329
  : null;
1330
+ const ultragoalPromptActivationNote = match.skill === "ultragoal"
1331
+ ? "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."
1332
+ : null;
1104
1333
  const combinedTransitionMessage = (() => {
1105
1334
  if (!skillState?.transition_message) return null;
1106
1335
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -1127,6 +1356,7 @@ function buildAdditionalContextMessage(
1127
1356
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
1128
1357
  : null,
1129
1358
  promptPriorityMessage,
1359
+ ultragoalPromptActivationNote,
1130
1360
  skillState.initialized_mode && skillState.initialized_state_path
1131
1361
  ? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
1132
1362
  : null,
@@ -1152,6 +1382,7 @@ function buildAdditionalContextMessage(
1152
1382
  initializedStateMessage,
1153
1383
  deepInterviewPromptActivationNote,
1154
1384
  ultraworkPromptActivationNote,
1385
+ ultragoalPromptActivationNote,
1155
1386
  buildTeamRuntimeInstruction(cwd, payload),
1156
1387
  buildTeamHelpInstruction(cwd, payload),
1157
1388
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
@@ -1169,12 +1400,13 @@ function buildAdditionalContextMessage(
1169
1400
  `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
1170
1401
  deepInterviewPromptActivationNote,
1171
1402
  ultraworkPromptActivationNote,
1403
+ ultragoalPromptActivationNote,
1172
1404
  ralphPromptActivationNote,
1173
1405
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1174
1406
  ].join(" ");
1175
1407
  }
1176
1408
 
1177
- return [detectedKeywordMessage, promptPriorityMessage, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
1409
+ return [detectedKeywordMessage, promptPriorityMessage, ultragoalPromptActivationNote, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
1178
1410
  }
1179
1411
 
1180
1412
  function parseTeamWorkerEnv(rawValue: string): { teamName: string; workerName: string } | null {
@@ -1190,26 +1422,79 @@ async function resolveTeamStateDirForWorkerContext(
1190
1422
  cwd: string,
1191
1423
  workerContext: { teamName: string; workerName: string },
1192
1424
  ): Promise<string | null> {
1193
- return resolveWorkerNotifyTeamStateRootPath(cwd, workerContext, process.env);
1425
+ const resolved = await resolveWorkerNotifyTeamStateRootPath(cwd, workerContext, process.env).catch(() => null);
1426
+ if (resolved) return resolved;
1427
+ const explicit = safeString(process.env.OMX_TEAM_STATE_ROOT).trim();
1428
+ if (explicit) {
1429
+ const candidate = resolve(cwd, explicit);
1430
+ const workerRoot = join(candidate, "team", workerContext.teamName, "workers", workerContext.workerName);
1431
+ if (existsSync(workerRoot)) return candidate;
1432
+ return candidate;
1433
+ }
1434
+ return null;
1194
1435
  }
1195
1436
 
1196
1437
 
1197
- async function buildTeamWorkerStopOutput(
1438
+ type TeamWorkerStopDecision =
1439
+ | {
1440
+ kind: "blocked";
1441
+ stateDir: string;
1442
+ workerContext: { teamName: string; workerName: string };
1443
+ output: Record<string, unknown>;
1444
+ allowRepeatDuringStopHook: boolean;
1445
+ }
1446
+ | {
1447
+ kind: "allowed";
1448
+ stateDir: string;
1449
+ workerContext: { teamName: string; workerName: string };
1450
+ }
1451
+ | {
1452
+ kind: "unresolved";
1453
+ reason: string;
1454
+ };
1455
+
1456
+ async function resolveTeamWorkerStopDecision(
1198
1457
  cwd: string,
1199
- ): Promise<Record<string, unknown> | null> {
1200
- const workerContext = parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER));
1201
- if (!workerContext) return null;
1458
+ ): Promise<TeamWorkerStopDecision> {
1459
+ const workerContext =
1460
+ parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER))
1461
+ || parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_WORKER));
1462
+ if (!workerContext) return { kind: "unresolved", reason: "missing_worker_context" };
1463
+
1464
+ const blockWorkerStop = (
1465
+ reasonCode: string,
1466
+ detail: string,
1467
+ stateDirForDecision = join(cwd, ".omx", "state"),
1468
+ ): TeamWorkerStopDecision => ({
1469
+ kind: "blocked",
1470
+ stateDir: stateDirForDecision,
1471
+ workerContext,
1472
+ allowRepeatDuringStopHook: false,
1473
+ output: {
1474
+ decision: "block",
1475
+ reason:
1476
+ `OMX team worker ${workerContext.workerName} Stop cannot be allowed for ${reasonCode}: ${detail}. ` +
1477
+ "Continue the assigned task, repair worker state, or report a concrete blocker before stopping.",
1478
+ stopReason: `team_worker_${workerContext.workerName}_${reasonCode}`,
1479
+ systemMessage:
1480
+ `OMX team worker ${workerContext.workerName} Stop lacks completed task evidence (${reasonCode}).`,
1481
+ },
1482
+ });
1202
1483
 
1203
1484
  const stateDir = await resolveTeamStateDirForWorkerContext(cwd, workerContext);
1204
- if (!stateDir) return null;
1485
+ if (!stateDir) {
1486
+ return blockWorkerStop("missing_state_dir", "team state root could not be resolved");
1487
+ }
1205
1488
  const workerRoot = join(stateDir, "team", workerContext.teamName, "workers", workerContext.workerName);
1206
1489
  const [identity, status] = await Promise.all([
1207
1490
  readJsonIfExists(join(workerRoot, "identity.json")),
1208
1491
  readJsonIfExists(join(workerRoot, "status.json")),
1209
1492
  ]);
1210
-
1211
- const workerState = safeString(status?.state).trim().toLowerCase();
1212
- if (!TEAM_WORKER_STOP_ACTIVE_STATES.has(workerState)) return null;
1493
+ const workerRunState = safeString(status?.state).trim().toLowerCase();
1494
+ const workerRunStateIsTerminal = TEAM_WORKER_TERMINAL_RUN_STATES.has(workerRunState);
1495
+ if (!identity && !status && !existsSync(workerRoot)) {
1496
+ return blockWorkerStop("missing_worker_state", "worker identity/status state is missing", stateDir);
1497
+ }
1213
1498
 
1214
1499
  const candidateTaskIds = new Set<string>();
1215
1500
  const currentTaskId = safeString(status?.current_task_id).trim();
@@ -1220,27 +1505,66 @@ async function buildTeamWorkerStopOutput(
1220
1505
  if (normalized) candidateTaskIds.add(normalized);
1221
1506
  }
1222
1507
 
1508
+ const tasksDir = join(stateDir, "team", workerContext.teamName, "tasks");
1509
+ if (existsSync(tasksDir)) {
1510
+ const taskFiles = await readdir(tasksDir).catch(() => []);
1511
+ for (const entry of taskFiles) {
1512
+ if (!/^task-\d+\.json$/.test(entry)) continue;
1513
+ const task = await readJsonIfExists(join(tasksDir, entry));
1514
+ const taskOwner = safeString(task?.owner).trim();
1515
+ const taskClaimOwner = safeString(safeObject(task?.claim).owner).trim();
1516
+ if (taskOwner !== workerContext.workerName && taskClaimOwner !== workerContext.workerName) continue;
1517
+ const idFromFile = /^task-(\d+)\.json$/.exec(entry)?.[1] ?? "";
1518
+ const taskId = safeString(task?.id).trim() || idFromFile;
1519
+ if (taskId) candidateTaskIds.add(taskId);
1520
+ }
1521
+ }
1522
+
1523
+ if (candidateTaskIds.size === 0) {
1524
+ return blockWorkerStop("missing_task_assignment", "no current_task_id or assigned_tasks are recorded", stateDir);
1525
+ }
1526
+
1527
+ let completedTaskCount = 0;
1223
1528
  for (const taskId of candidateTaskIds) {
1224
1529
  const task = await readJsonIfExists(
1225
1530
  join(stateDir, "team", workerContext.teamName, "tasks", `task-${taskId}.json`),
1226
1531
  );
1227
1532
  const statusValue = safeString(task?.status).trim().toLowerCase();
1228
- if (!statusValue || TEAM_TERMINAL_TASK_STATUSES.has(statusValue)) continue;
1533
+ if (!statusValue) {
1534
+ return blockWorkerStop(`missing_task_state_${taskId}`, `task ${taskId} has no readable status`, stateDir);
1535
+ }
1536
+ if (statusValue === "completed") {
1537
+ completedTaskCount += 1;
1538
+ continue;
1539
+ }
1540
+ if (!TEAM_STOP_BLOCKING_TASK_STATUSES.has(statusValue)) {
1541
+ return blockWorkerStop(
1542
+ `non_completed_task_${taskId}_${statusValue}`,
1543
+ `task ${taskId} is ${statusValue}, not completed`,
1544
+ stateDir,
1545
+ );
1546
+ }
1229
1547
  return {
1230
- decision: "block",
1231
- reason:
1232
- `OMX team worker ${workerContext.workerName} is still assigned non-terminal task ${taskId} (${statusValue}); continue the current assigned task or report a concrete blocker before stopping.`,
1233
- stopReason: `team_worker_${workerContext.workerName}_${taskId}_${statusValue}`,
1234
- systemMessage:
1235
- `OMX team worker ${workerContext.workerName} is still assigned task ${taskId} (${statusValue}).`,
1548
+ kind: "blocked",
1549
+ stateDir,
1550
+ workerContext,
1551
+ allowRepeatDuringStopHook: !workerRunStateIsTerminal,
1552
+ output: {
1553
+ decision: "block",
1554
+ reason:
1555
+ `OMX team worker ${workerContext.workerName} is still assigned non-terminal task ${taskId} (${statusValue}); continue the current assigned task or report a concrete blocker before stopping.`,
1556
+ stopReason: `team_worker_${workerContext.workerName}_${taskId}_${statusValue}`,
1557
+ systemMessage:
1558
+ `OMX team worker ${workerContext.workerName} is still assigned task ${taskId} (${statusValue}).`,
1559
+ },
1236
1560
  };
1237
1561
  }
1238
1562
 
1239
- return null;
1240
- }
1563
+ if (completedTaskCount === candidateTaskIds.size) {
1564
+ return { kind: "allowed", stateDir, workerContext };
1565
+ }
1241
1566
 
1242
- function hasTeamWorkerContext(): boolean {
1243
- return parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER)) !== null;
1567
+ return blockWorkerStop("missing_completed_task_evidence", "no referenced worker task is completed", stateDir);
1244
1568
  }
1245
1569
 
1246
1570
  function isStopExempt(payload: CodexHookPayload): boolean {
@@ -1267,9 +1591,7 @@ async function buildModeBasedStopOutput(
1267
1591
  cwd: string,
1268
1592
  sessionId?: string,
1269
1593
  ): Promise<Record<string, unknown> | null> {
1270
- const state = sessionId
1271
- ? await readModeStateForSession(mode, sessionId, cwd)
1272
- : await readModeState(mode, cwd);
1594
+ const state = await readModeStateForActiveDecision(mode, sessionId?.trim() || undefined, cwd);
1273
1595
  if (!state || !shouldContinueRun(state)) return null;
1274
1596
  const phase = formatPhase(state.current_phase);
1275
1597
  return {
@@ -1280,6 +1602,81 @@ async function buildModeBasedStopOutput(
1280
1602
  };
1281
1603
  }
1282
1604
 
1605
+ function looksLikeGoalCompletionPrompt(text: string): boolean {
1606
+ return /\b(?:complete|checkpoint|finish|close|mark)\b.{0,80}\b(?:goal|ultragoal|performance-goal|autoresearch-goal)\b/i.test(text)
1607
+ || /\bupdate_goal\s*\(/i.test(text)
1608
+ || /\bomx\s+(?:ultragoal|performance-goal|autoresearch-goal)\s+(?:checkpoint|complete)\b/i.test(text);
1609
+ }
1610
+
1611
+ async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string } | null> {
1612
+ const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
1613
+ const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
1614
+ const activeUltragoal = ultragoals.find((goal) => safeString(goal.status) === "in_progress" || safeString(goal.id) === safeString(ultragoal?.activeGoalId));
1615
+ if (activeUltragoal) {
1616
+ return {
1617
+ workflow: "ultragoal",
1618
+ command: `omx ultragoal checkpoint --goal-id ${safeString(activeUltragoal.id) || "<goal-id>"} --status complete --codex-goal-json '<get_goal JSON or path>' --evidence '<evidence>'`,
1619
+ };
1620
+ }
1621
+
1622
+ const performanceRoot = join(cwd, ".omx", "goals", "performance");
1623
+ for (const entry of await readdir(performanceRoot, { withFileTypes: true }).catch(() => [])) {
1624
+ if (!entry.isDirectory()) continue;
1625
+ const state = await readJsonIfExists(join(performanceRoot, entry.name, "state.json"));
1626
+ const status = safeString(state?.status);
1627
+ if (state?.workflow === "performance-goal" && status && status !== "complete") {
1628
+ return {
1629
+ workflow: "performance-goal",
1630
+ command: `omx performance-goal complete --slug ${safeString(state.slug) || entry.name} --codex-goal-json '<get_goal JSON or path>' --evidence '<evidence>'`,
1631
+ };
1632
+ }
1633
+ }
1634
+
1635
+ const autoresearchRoot = join(cwd, ".omx", "goals", "autoresearch");
1636
+ for (const entry of await readdir(autoresearchRoot, { withFileTypes: true }).catch(() => [])) {
1637
+ if (!entry.isDirectory()) continue;
1638
+ const mission = await readJsonIfExists(join(autoresearchRoot, entry.name, "mission.json"));
1639
+ const status = safeString(mission?.status);
1640
+ if (mission?.workflow === "autoresearch-goal" && status && status !== "complete") {
1641
+ return {
1642
+ workflow: "autoresearch-goal",
1643
+ command: `omx autoresearch-goal complete --slug ${safeString(mission.slug) || entry.name} --codex-goal-json '<get_goal JSON or path>'`,
1644
+ };
1645
+ }
1646
+ }
1647
+
1648
+ return null;
1649
+ }
1650
+
1651
+ async function buildGoalWorkflowReconciliationPromptWarning(cwd: string, prompt: string): Promise<string | null> {
1652
+ if (!looksLikeGoalCompletionPrompt(prompt)) return null;
1653
+ const requirement = await findActiveGoalWorkflowReconciliationRequirement(cwd);
1654
+ if (!requirement) return null;
1655
+ return [
1656
+ `OMX ${requirement.workflow} goal workflow requires Codex goal snapshot reconciliation before completion.`,
1657
+ "Call get_goal, pass the resulting JSON or a path with --codex-goal-json, and do not rely on hooks or shell commands to mutate Codex-owned goal state.",
1658
+ `Required command shape: ${requirement.command}.`,
1659
+ ].join(" ");
1660
+ }
1661
+
1662
+ async function buildGoalWorkflowReconciliationStopOutput(
1663
+ payload: CodexHookPayload,
1664
+ cwd: string,
1665
+ ): Promise<Record<string, unknown> | null> {
1666
+ const lastAssistantMessage = safeString(payload.last_assistant_message ?? payload.lastAssistantMessage);
1667
+ if (!looksLikeGoalCompletionPrompt(lastAssistantMessage)) return null;
1668
+ const requirement = await findActiveGoalWorkflowReconciliationRequirement(cwd);
1669
+ if (!requirement) return null;
1670
+ const systemMessage =
1671
+ `OMX ${requirement.workflow} requires get_goal snapshot reconciliation before completion; call get_goal and pass --codex-goal-json to ${requirement.command}. Hooks must not mutate Codex goal state.`;
1672
+ return {
1673
+ decision: "block",
1674
+ reason: systemMessage,
1675
+ stopReason: `${requirement.workflow}_codex_goal_snapshot_required`,
1676
+ systemMessage,
1677
+ };
1678
+ }
1679
+
1283
1680
  async function readTeamModeStateForStop(
1284
1681
  cwd: string,
1285
1682
  sessionId?: string,
@@ -1470,7 +1867,7 @@ async function readBlockingSkillForStop(
1470
1867
  sessionId: string,
1471
1868
  threadId: string,
1472
1869
  requiredSkill?: string,
1473
- ): Promise<{ skill: string; phase: string } | null> {
1870
+ ): Promise<{ skill: string; phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string } | null> {
1474
1871
  const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
1475
1872
  const visibleEntries = canonicalState ? listActiveSkills(canonicalState) : [];
1476
1873
  const candidateSkills = requiredSkill
@@ -1500,7 +1897,13 @@ async function readBlockingSkillForStop(
1500
1897
  }
1501
1898
 
1502
1899
  if (!canonicalState) {
1503
- return { skill, phase };
1900
+ return {
1901
+ skill,
1902
+ phase,
1903
+ latestPlanPath: safeString(modeState.latest_plan_path ?? modeState.latestPlanPath).trim() || undefined,
1904
+ planningComplete: modeState.planning_complete === true || modeState.planningComplete === true,
1905
+ runOutcome: safeString(modeState.run_outcome ?? modeState.outcome).trim() || undefined,
1906
+ };
1504
1907
  }
1505
1908
 
1506
1909
  const blocker = visibleEntries.find((entry) => (
@@ -1512,12 +1915,65 @@ async function readBlockingSkillForStop(
1512
1915
  return {
1513
1916
  skill,
1514
1917
  phase: formatPhase(modeState.current_phase ?? blocker.phase ?? canonicalState.phase, "planning"),
1918
+ latestPlanPath: safeString(modeState.latest_plan_path ?? modeState.latestPlanPath).trim() || undefined,
1919
+ planningComplete: modeState.planning_complete === true || modeState.planningComplete === true,
1920
+ runOutcome: safeString(modeState.run_outcome ?? modeState.outcome).trim() || undefined,
1515
1921
  };
1516
1922
  }
1517
1923
 
1518
1924
  return null;
1519
1925
  }
1520
1926
 
1927
+ function buildRalplanContinuationStatus(
1928
+ blocker: { phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string },
1929
+ activeSubagentCount: number,
1930
+ ): { reason: string; systemMessage: string; stopReasonSuffix: string } {
1931
+ const phase = blocker.phase || "planning";
1932
+ const artifact = blocker.latestPlanPath
1933
+ ? ` Artifact: ${blocker.latestPlanPath}.`
1934
+ : " Artifact: use the latest `.omx/plans/` ralplan artifact if present.";
1935
+
1936
+ if (activeSubagentCount > 0) {
1937
+ return {
1938
+ reason:
1939
+ `Status: waiting — ralplan is waiting for ${activeSubagentCount} active native subagent thread(s) to finish (phase: ${phase}). Do not stop silently; wait for the subagent result, then continue from the current ralplan artifact and proceed to the next planning/review step.${artifact}`,
1940
+ stopReasonSuffix: "waiting_subagent",
1941
+ systemMessage:
1942
+ `OMX ralplan status: waiting for ${activeSubagentCount} active native subagent thread(s) at phase ${phase}; after they finish, continue from the current ralplan artifact and state the next status explicitly.`,
1943
+ };
1944
+ }
1945
+
1946
+ const normalizedPhase = phase.toLowerCase();
1947
+ const normalizedOutcome = (blocker.runOutcome ?? "").toLowerCase();
1948
+ const waitingForInput =
1949
+ normalizedOutcome === "blocked_on_user"
1950
+ || normalizedPhase.includes("blocked")
1951
+ || normalizedPhase.includes("input")
1952
+ || normalizedPhase.includes("question");
1953
+
1954
+ if (waitingForInput) {
1955
+ return {
1956
+ reason:
1957
+ `Status: waiting_for_input — ralplan is paused for required user/operator input (phase: ${phase}). Ask the missing question or present the review choice explicitly before stopping.${artifact}`,
1958
+ stopReasonSuffix: "waiting_input",
1959
+ systemMessage:
1960
+ `OMX ralplan status: waiting for input at phase ${phase}; ask the required question or present the explicit review choice before stopping.`,
1961
+ };
1962
+ }
1963
+
1964
+ const completeHint = blocker.planningComplete
1965
+ ? " The planning artifacts are present; if consensus is approved, emit the final complete/approved handoff instead of stopping here."
1966
+ : "";
1967
+
1968
+ return {
1969
+ reason:
1970
+ `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}`,
1971
+ stopReasonSuffix: "continue_artifact",
1972
+ systemMessage:
1973
+ `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.`,
1974
+ };
1975
+ }
1976
+
1521
1977
  async function readStopAutoNudgePhase(
1522
1978
  cwd: string,
1523
1979
  sessionId: string,
@@ -1612,7 +2068,12 @@ function resolveRepeatableStopSessionId(
1612
2068
  payload: CodexHookPayload,
1613
2069
  canonicalSessionId?: string,
1614
2070
  ): string {
1615
- return canonicalSessionId?.trim() || readPayloadSessionId(payload) || "";
2071
+ const inheritedSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
2072
+ return canonicalSessionId?.trim() || readPayloadSessionId(payload) || inheritedSessionId || "";
2073
+ }
2074
+
2075
+ function isStateLevelStopSignatureKind(kind: string): boolean {
2076
+ return kind === "team-worker-stop" || kind === "team-stop";
1616
2077
  }
1617
2078
 
1618
2079
  function buildRepeatableStopSignature(
@@ -1623,8 +2084,11 @@ function buildRepeatableStopSignature(
1623
2084
  ): string {
1624
2085
  const sessionId = resolveRepeatableStopSessionId(payload, canonicalSessionId) || "no-session";
1625
2086
  const threadId = readPayloadThreadId(payload) || "no-thread";
1626
- const turnId = readPayloadTurnId(payload);
1627
2087
  const normalizedDetail = normalizeAutoNudgeSignatureText(detail) || safeString(detail).trim().toLowerCase();
2088
+ if (isStateLevelStopSignatureKind(kind)) {
2089
+ return [kind, sessionId, threadId, normalizedDetail || "no-detail"].join("|");
2090
+ }
2091
+ const turnId = readPayloadTurnId(payload);
1628
2092
  const transcriptPath = safeString(payload.transcript_path ?? payload.transcriptPath).trim() || "no-transcript";
1629
2093
  const lastAssistantMessage = normalizeAutoNudgeSignatureText(
1630
2094
  payload.last_assistant_message ?? payload.lastAssistantMessage,
@@ -1840,7 +2304,19 @@ async function buildSkillStopOutput(
1840
2304
  if (!blocker) return null;
1841
2305
 
1842
2306
  const subagentSummary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
1843
- if (subagentSummary && subagentSummary.activeSubagentThreadIds.length > 0) {
2307
+ const activeSubagentCount = subagentSummary?.activeSubagentThreadIds.length ?? 0;
2308
+
2309
+ if (blocker.skill === "ralplan") {
2310
+ const status = buildRalplanContinuationStatus(blocker, activeSubagentCount);
2311
+ return {
2312
+ decision: "block",
2313
+ reason: status.reason,
2314
+ stopReason: `skill_${blocker.skill}_${blocker.phase}_${status.stopReasonSuffix}`,
2315
+ systemMessage: status.systemMessage,
2316
+ };
2317
+ }
2318
+
2319
+ if (activeSubagentCount > 0) {
1844
2320
  return null;
1845
2321
  }
1846
2322
 
@@ -1961,6 +2437,7 @@ async function buildStopHookOutput(
1961
2437
  payload: CodexHookPayload,
1962
2438
  cwd: string,
1963
2439
  stateDir: string,
2440
+ options: { skipRalphStopBlock?: boolean } = {},
1964
2441
  ): Promise<Record<string, unknown> | null> {
1965
2442
  if (isStopExempt(payload)) {
1966
2443
  return null;
@@ -1971,11 +2448,13 @@ async function buildStopHookOutput(
1971
2448
  const threadId = readPayloadThreadId(payload);
1972
2449
  const execFollowupOutput = await buildExecFollowupStopOutput(cwd, canonicalSessionId);
1973
2450
  if (execFollowupOutput) return execFollowupOutput;
1974
- const ralphState = await readActiveRalphState(stateDir, canonicalSessionId, {
1975
- payloadSessionId: sessionId,
1976
- threadId,
1977
- tmuxPaneId: safeString(process.env.TMUX_PANE).trim(),
1978
- });
2451
+ const ralphState = options.skipRalphStopBlock === true
2452
+ ? null
2453
+ : await readActiveRalphState(stateDir, canonicalSessionId, {
2454
+ payloadSessionId: sessionId,
2455
+ threadId,
2456
+ tmuxPaneId: safeString(process.env.TMUX_PANE).trim(),
2457
+ });
1979
2458
  if (!ralphState) {
1980
2459
  const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
1981
2460
  if (autoresearchState) {
@@ -1999,18 +2478,30 @@ async function buildStopHookOutput(
1999
2478
  }
2000
2479
  }
2001
2480
 
2002
- const teamWorkerOutput = await buildTeamWorkerStopOutput(cwd);
2003
- if (hasTeamWorkerContext() && teamWorkerOutput) {
2481
+ const teamWorkerDecision = await resolveTeamWorkerStopDecision(cwd);
2482
+ if (teamWorkerDecision.kind === "blocked") {
2004
2483
  return await returnPersistentStopBlock(
2005
2484
  payload,
2006
2485
  stateDir,
2007
2486
  "team-worker-stop",
2008
- safeString(teamWorkerOutput.stopReason),
2009
- teamWorkerOutput,
2487
+ safeString(teamWorkerDecision.output.stopReason),
2488
+ teamWorkerDecision.output,
2010
2489
  canonicalSessionId,
2011
- { allowRepeatDuringStopHook: false },
2490
+ { allowRepeatDuringStopHook: teamWorkerDecision.allowRepeatDuringStopHook },
2012
2491
  );
2013
2492
  }
2493
+ if (teamWorkerDecision.kind === "allowed") {
2494
+ try {
2495
+ await maybeNudgeLeaderForAllowedWorkerStop({
2496
+ stateDir: teamWorkerDecision.stateDir,
2497
+ logsDir: join(cwd, ".omx", "logs"),
2498
+ workerContext: teamWorkerDecision.workerContext,
2499
+ });
2500
+ } catch (err) {
2501
+ void err;
2502
+ }
2503
+ return null;
2504
+ }
2014
2505
 
2015
2506
  const autopilotOutput = await buildModeBasedStopOutput("autopilot", cwd, canonicalSessionId);
2016
2507
  if (autopilotOutput) {
@@ -2123,6 +2614,18 @@ async function buildStopHookOutput(
2123
2614
  const lastAssistantMessage = safeString(
2124
2615
  payload.last_assistant_message ?? payload.lastAssistantMessage,
2125
2616
  );
2617
+ const goalWorkflowStopOutput = await buildGoalWorkflowReconciliationStopOutput(payload, cwd);
2618
+ if (goalWorkflowStopOutput) {
2619
+ return await returnPersistentStopBlock(
2620
+ payload,
2621
+ stateDir,
2622
+ "goal-workflow-reconciliation-stop",
2623
+ safeString(goalWorkflowStopOutput.stopReason),
2624
+ goalWorkflowStopOutput,
2625
+ canonicalSessionId,
2626
+ { allowRepeatDuringStopHook: true },
2627
+ );
2628
+ }
2126
2629
  const autoNudgeConfig = await loadAutoNudgeConfig();
2127
2630
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, canonicalSessionId, threadId);
2128
2631
 
@@ -2147,6 +2650,20 @@ async function buildStopHookOutput(
2147
2650
  );
2148
2651
  }
2149
2652
 
2653
+ const sloppyFallbackDiffFindings = findSloppyFallbackDiffFindings(cwd);
2654
+ const sloppyFallbackDiffOutput = buildSloppyFallbackDiffStopOutput(sloppyFallbackDiffFindings);
2655
+ if (sloppyFallbackDiffOutput) {
2656
+ return await returnPersistentStopBlock(
2657
+ payload,
2658
+ stateDir,
2659
+ "sloppy-fallback-diff-stop",
2660
+ JSON.stringify(sloppyFallbackDiffFindings),
2661
+ sloppyFallbackDiffOutput,
2662
+ canonicalSessionId,
2663
+ { allowRepeatDuringStopHook: true },
2664
+ );
2665
+ }
2666
+
2150
2667
  if (isFinalHandoffDocumentRefreshCandidate(lastAssistantMessage)) {
2151
2668
  const documentRefreshWarning = evaluateFinalHandoffDocumentRefresh(cwd, lastAssistantMessage);
2152
2669
  if (documentRefreshWarning) {
@@ -2202,6 +2719,7 @@ export async function dispatchCodexNativeHook(
2202
2719
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
2203
2720
  let skillState: SkillActiveState | null = null;
2204
2721
  let triageAdditionalContext: string | null = null;
2722
+ let goalWorkflowAdditionalContext: string | null = null;
2205
2723
 
2206
2724
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
2207
2725
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
@@ -2254,9 +2772,10 @@ export async function dispatchCodexNativeHook(
2254
2772
  }
2255
2773
 
2256
2774
  if (hookEventName === "Stop") {
2775
+ const inheritedSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
2257
2776
  const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(
2258
2777
  cwd,
2259
- readPayloadSessionId(payload),
2778
+ readPayloadSessionId(payload) || inheritedSessionId,
2260
2779
  );
2261
2780
  if (stopCanonicalSessionId) {
2262
2781
  canonicalSessionId = stopCanonicalSessionId;
@@ -2270,10 +2789,23 @@ export async function dispatchCodexNativeHook(
2270
2789
  const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
2271
2790
  const sessionIdForState = canonicalSessionId || nativeSessionId;
2272
2791
  let outputJson: Record<string, unknown> | null = null;
2792
+ const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
2793
+ ? await isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId)
2794
+ : false;
2795
+ const isSubagentStop = hookEventName === "Stop"
2796
+ ? (await Promise.all(
2797
+ [...new Set([
2798
+ canonicalSessionId,
2799
+ safeString(currentSessionState?.session_id).trim(),
2800
+ ].filter(Boolean))]
2801
+ .map((candidateSessionId) => isNativeSubagentHook(cwd, candidateSessionId, nativeSessionId, threadId)),
2802
+ )).some(Boolean)
2803
+ : false;
2273
2804
 
2274
2805
  if (hookEventName === "UserPromptSubmit") {
2275
2806
  const prompt = readPromptText(payload);
2276
- if (prompt) {
2807
+ goalWorkflowAdditionalContext = await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
2808
+ if (prompt && !isSubagentPromptSubmit) {
2277
2809
  skillState = buildNativeOutsideTmuxTeamPromptBlockState(
2278
2810
  prompt,
2279
2811
  cwd,
@@ -2290,7 +2822,7 @@ export async function dispatchCodexNativeHook(
2290
2822
  });
2291
2823
  }
2292
2824
  // --- Triage classifier (advisory-only, non-keyword prompts) ---
2293
- if (prompt && skillState === null) {
2825
+ if (prompt && skillState === null && !isSubagentPromptSubmit) {
2294
2826
  try {
2295
2827
  if (readTriageConfig().enabled) {
2296
2828
  const normalized = prompt.trim().toLowerCase();
@@ -2382,7 +2914,11 @@ export async function dispatchCodexNativeHook(
2382
2914
  mode: safeString(payload.mode).trim() || undefined,
2383
2915
  },
2384
2916
  );
2385
- await dispatchHookEvent(event, { cwd });
2917
+ await dispatchHookEventRuntime({
2918
+ event,
2919
+ cwd,
2920
+ allowTeamWorkerSideEffects: false,
2921
+ });
2386
2922
  }
2387
2923
 
2388
2924
  if ((hookEventName === "SessionStart" && !skipCanonicalSessionStartContext) || hookEventName === "UserPromptSubmit") {
@@ -2393,7 +2929,9 @@ export async function dispatchCodexNativeHook(
2393
2929
  canonicalSessionId,
2394
2930
  nativeSessionId: resolvedNativeSessionId || nativeSessionId,
2395
2931
  })
2396
- : (buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload) ?? triageAdditionalContext);
2932
+ : isSubagentPromptSubmit
2933
+ ? null
2934
+ : (buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload) ?? goalWorkflowAdditionalContext ?? triageAdditionalContext);
2397
2935
  if (additionalContext) {
2398
2936
  outputJson = {
2399
2937
  hookSpecificOutput: {
@@ -2411,7 +2949,9 @@ export async function dispatchCodexNativeHook(
2411
2949
  outputJson = buildNativePostToolUseOutput(payload);
2412
2950
  await handleTeamWorkerPostToolUseSuccess(payload, cwd);
2413
2951
  } else if (hookEventName === "Stop") {
2414
- outputJson = await buildStopHookOutput(payload, cwd, stateDir);
2952
+ outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
2953
+ skipRalphStopBlock: isSubagentStop,
2954
+ });
2415
2955
  }
2416
2956
 
2417
2957
  return {