oh-my-codex 0.12.4 → 0.12.6

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 (475) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +27 -3
  4. package/dist/cli/__tests__/ask.test.js +26 -0
  5. package/dist/cli/__tests__/ask.test.js.map +1 -1
  6. package/dist/cli/__tests__/doctor-warning-copy.test.js +28 -0
  7. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  8. package/dist/cli/__tests__/explore.test.js +95 -8
  9. package/dist/cli/__tests__/explore.test.js.map +1 -1
  10. package/dist/cli/__tests__/index.test.js +102 -4
  11. package/dist/cli/__tests__/index.test.js.map +1 -1
  12. package/dist/cli/__tests__/launch-fallback.test.js +169 -0
  13. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  14. package/dist/cli/__tests__/mcp-parity.test.js +31 -0
  15. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  16. package/dist/cli/__tests__/setup-agents-overwrite.test.js +66 -2
  17. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  18. package/dist/cli/__tests__/setup-refresh.test.js +51 -1
  19. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  20. package/dist/cli/__tests__/team.test.js +148 -3
  21. package/dist/cli/__tests__/team.test.js.map +1 -1
  22. package/dist/cli/__tests__/uninstall.test.js +14 -1
  23. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  24. package/dist/cli/cleanup.js +1 -1
  25. package/dist/cli/cleanup.js.map +1 -1
  26. package/dist/cli/constants.d.ts +1 -0
  27. package/dist/cli/constants.d.ts.map +1 -1
  28. package/dist/cli/constants.js +1 -0
  29. package/dist/cli/constants.js.map +1 -1
  30. package/dist/cli/doctor.d.ts.map +1 -1
  31. package/dist/cli/doctor.js +15 -0
  32. package/dist/cli/doctor.js.map +1 -1
  33. package/dist/cli/explore.d.ts +1 -0
  34. package/dist/cli/explore.d.ts.map +1 -1
  35. package/dist/cli/explore.js +49 -1
  36. package/dist/cli/explore.js.map +1 -1
  37. package/dist/cli/index.d.ts +2 -1
  38. package/dist/cli/index.d.ts.map +1 -1
  39. package/dist/cli/index.js +127 -14
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/mcp-parity.d.ts +1 -1
  42. package/dist/cli/mcp-parity.d.ts.map +1 -1
  43. package/dist/cli/mcp-parity.js +24 -0
  44. package/dist/cli/mcp-parity.js.map +1 -1
  45. package/dist/cli/setup.d.ts.map +1 -1
  46. package/dist/cli/setup.js +17 -5
  47. package/dist/cli/setup.js.map +1 -1
  48. package/dist/cli/team.d.ts.map +1 -1
  49. package/dist/cli/team.js +80 -6
  50. package/dist/cli/team.js.map +1 -1
  51. package/dist/cli/uninstall.d.ts.map +1 -1
  52. package/dist/cli/uninstall.js +1 -0
  53. package/dist/cli/uninstall.js.map +1 -1
  54. package/dist/config/__tests__/generator-idempotent.test.js +60 -0
  55. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  56. package/dist/config/__tests__/mcp-registry.test.js +61 -0
  57. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  58. package/dist/config/__tests__/wiki-config-contract.test.d.ts +2 -0
  59. package/dist/config/__tests__/wiki-config-contract.test.d.ts.map +1 -0
  60. package/dist/config/__tests__/wiki-config-contract.test.js +19 -0
  61. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -0
  62. package/dist/config/generator.d.ts +1 -0
  63. package/dist/config/generator.d.ts.map +1 -1
  64. package/dist/config/generator.js +88 -3
  65. package/dist/config/generator.js.map +1 -1
  66. package/dist/config/mcp-registry.d.ts +2 -0
  67. package/dist/config/mcp-registry.d.ts.map +1 -1
  68. package/dist/config/mcp-registry.js +12 -0
  69. package/dist/config/mcp-registry.js.map +1 -1
  70. package/dist/hooks/__tests__/agents-overlay.test.js +39 -0
  71. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  72. package/dist/hooks/__tests__/keyword-detector.test.js +297 -4
  73. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  74. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +392 -22
  75. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  76. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +166 -67
  77. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +112 -2
  79. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  80. package/dist/hooks/__tests__/notify-hook-modules.test.js +52 -12
  81. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  82. package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts +2 -3
  83. package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts.map +1 -1
  84. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +18 -23
  85. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +33 -0
  87. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +176 -1
  89. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +355 -7
  91. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  92. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +90 -2
  93. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  94. package/dist/hooks/__tests__/session.test.js +142 -2
  95. package/dist/hooks/__tests__/session.test.js.map +1 -1
  96. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts +2 -0
  97. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts.map +1 -0
  98. package/dist/hooks/__tests__/wiki-docs-contract.test.js +34 -0
  99. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -0
  100. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  101. package/dist/hooks/agents-overlay.js +0 -1
  102. package/dist/hooks/agents-overlay.js.map +1 -1
  103. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +32 -0
  104. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  105. package/dist/hooks/extensibility/__tests__/runtime.test.js +31 -0
  106. package/dist/hooks/extensibility/__tests__/runtime.test.js.map +1 -1
  107. package/dist/hooks/extensibility/__tests__/sdk.test.js +33 -3
  108. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  109. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  110. package/dist/hooks/extensibility/dispatcher.js +41 -0
  111. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  112. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  113. package/dist/hooks/extensibility/sdk/runtime-state.js +7 -1
  114. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  115. package/dist/hooks/extensibility/types.d.ts +1 -0
  116. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  117. package/dist/hooks/keyword-detector.d.ts +6 -1
  118. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  119. package/dist/hooks/keyword-detector.js +207 -10
  120. package/dist/hooks/keyword-detector.js.map +1 -1
  121. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  122. package/dist/hooks/keyword-registry.js +3 -0
  123. package/dist/hooks/keyword-registry.js.map +1 -1
  124. package/dist/hooks/session.d.ts +14 -2
  125. package/dist/hooks/session.d.ts.map +1 -1
  126. package/dist/hooks/session.js +120 -16
  127. package/dist/hooks/session.js.map +1 -1
  128. package/dist/hud/__tests__/state.test.js +111 -2
  129. package/dist/hud/__tests__/state.test.js.map +1 -1
  130. package/dist/hud/state.d.ts.map +1 -1
  131. package/dist/hud/state.js +18 -21
  132. package/dist/hud/state.js.map +1 -1
  133. package/dist/mcp/__tests__/bootstrap.test.js +88 -1
  134. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  135. package/dist/mcp/__tests__/server-lifecycle.test.js +3 -0
  136. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  137. package/dist/mcp/__tests__/state-paths.test.js +30 -2
  138. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  139. package/dist/mcp/__tests__/state-server.test.js +415 -0
  140. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  141. package/dist/mcp/__tests__/wiki-server.test.d.ts +2 -0
  142. package/dist/mcp/__tests__/wiki-server.test.d.ts.map +1 -0
  143. package/dist/mcp/__tests__/wiki-server.test.js +30 -0
  144. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -0
  145. package/dist/mcp/bootstrap.d.ts +19 -1
  146. package/dist/mcp/bootstrap.d.ts.map +1 -1
  147. package/dist/mcp/bootstrap.js +185 -0
  148. package/dist/mcp/bootstrap.js.map +1 -1
  149. package/dist/mcp/state-paths.d.ts +5 -0
  150. package/dist/mcp/state-paths.d.ts.map +1 -1
  151. package/dist/mcp/state-paths.js +41 -11
  152. package/dist/mcp/state-paths.js.map +1 -1
  153. package/dist/mcp/state-server.d.ts +4 -4
  154. package/dist/mcp/state-server.d.ts.map +1 -1
  155. package/dist/mcp/state-server.js +49 -2
  156. package/dist/mcp/state-server.js.map +1 -1
  157. package/dist/mcp/wiki-server.d.ts +181 -0
  158. package/dist/mcp/wiki-server.d.ts.map +1 -0
  159. package/dist/mcp/wiki-server.js +235 -0
  160. package/dist/mcp/wiki-server.js.map +1 -0
  161. package/dist/modes/__tests__/base-autoresearch-contract.test.js +74 -2
  162. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  163. package/dist/modes/__tests__/base-multi-state-compat.test.d.ts +2 -0
  164. package/dist/modes/__tests__/base-multi-state-compat.test.d.ts.map +1 -0
  165. package/dist/modes/__tests__/base-multi-state-compat.test.js +38 -0
  166. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -0
  167. package/dist/modes/__tests__/base-tmux-pane.test.js +1 -1
  168. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  169. package/dist/modes/base.d.ts +2 -1
  170. package/dist/modes/base.d.ts.map +1 -1
  171. package/dist/modes/base.js +55 -31
  172. package/dist/modes/base.js.map +1 -1
  173. package/dist/notifications/__tests__/formatter.test.js +11 -0
  174. package/dist/notifications/__tests__/formatter.test.js.map +1 -1
  175. package/dist/notifications/__tests__/idle-cooldown.test.js +32 -1
  176. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  177. package/dist/notifications/__tests__/index.test.d.ts +2 -0
  178. package/dist/notifications/__tests__/index.test.d.ts.map +1 -0
  179. package/dist/notifications/__tests__/index.test.js +113 -0
  180. package/dist/notifications/__tests__/index.test.js.map +1 -0
  181. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts +2 -0
  182. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts.map +1 -0
  183. package/dist/notifications/__tests__/lifecycle-dedupe.test.js +86 -0
  184. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -0
  185. package/dist/notifications/__tests__/reply-listener.test.js +174 -0
  186. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  187. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts +2 -0
  188. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts.map +1 -0
  189. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +93 -0
  190. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -0
  191. package/dist/notifications/__tests__/session-registry.test.js +48 -1
  192. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  193. package/dist/notifications/__tests__/session-status.test.d.ts +2 -0
  194. package/dist/notifications/__tests__/session-status.test.d.ts.map +1 -0
  195. package/dist/notifications/__tests__/session-status.test.js +159 -0
  196. package/dist/notifications/__tests__/session-status.test.js.map +1 -0
  197. package/dist/notifications/__tests__/tmux.test.js +58 -1
  198. package/dist/notifications/__tests__/tmux.test.js.map +1 -1
  199. package/dist/notifications/idle-cooldown.d.ts +11 -0
  200. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  201. package/dist/notifications/idle-cooldown.js +42 -8
  202. package/dist/notifications/idle-cooldown.js.map +1 -1
  203. package/dist/notifications/index.d.ts +1 -1
  204. package/dist/notifications/index.d.ts.map +1 -1
  205. package/dist/notifications/index.js +41 -8
  206. package/dist/notifications/index.js.map +1 -1
  207. package/dist/notifications/lifecycle-dedupe.d.ts +8 -0
  208. package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -0
  209. package/dist/notifications/lifecycle-dedupe.js +112 -0
  210. package/dist/notifications/lifecycle-dedupe.js.map +1 -0
  211. package/dist/notifications/reply-listener.d.ts +10 -1
  212. package/dist/notifications/reply-listener.d.ts.map +1 -1
  213. package/dist/notifications/reply-listener.js +49 -11
  214. package/dist/notifications/reply-listener.js.map +1 -1
  215. package/dist/notifications/session-registry.d.ts.map +1 -1
  216. package/dist/notifications/session-registry.js +7 -1
  217. package/dist/notifications/session-registry.js.map +1 -1
  218. package/dist/notifications/session-status.d.ts +23 -0
  219. package/dist/notifications/session-status.d.ts.map +1 -0
  220. package/dist/notifications/session-status.js +187 -0
  221. package/dist/notifications/session-status.js.map +1 -0
  222. package/dist/notifications/tmux.d.ts +10 -0
  223. package/dist/notifications/tmux.d.ts.map +1 -1
  224. package/dist/notifications/tmux.js +59 -5
  225. package/dist/notifications/tmux.js.map +1 -1
  226. package/dist/notifications/types.d.ts +2 -0
  227. package/dist/notifications/types.d.ts.map +1 -1
  228. package/dist/openclaw/__tests__/index.test.js +84 -0
  229. package/dist/openclaw/__tests__/index.test.js.map +1 -1
  230. package/dist/openclaw/index.d.ts.map +1 -1
  231. package/dist/openclaw/index.js +7 -14
  232. package/dist/openclaw/index.js.map +1 -1
  233. package/dist/openclaw/types.d.ts +2 -2
  234. package/dist/openclaw/types.d.ts.map +1 -1
  235. package/dist/scripts/__tests__/codex-native-hook.test.js +692 -40
  236. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  237. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts +2 -0
  238. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts.map +1 -0
  239. package/dist/scripts/__tests__/hook-derived-watcher.test.js +87 -0
  240. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -0
  241. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  242. package/dist/scripts/codex-native-hook.js +309 -77
  243. package/dist/scripts/codex-native-hook.js.map +1 -1
  244. package/dist/scripts/hook-derived-watcher.js +43 -1
  245. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  246. package/dist/scripts/notify-fallback-watcher.js +95 -21
  247. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  248. package/dist/scripts/notify-hook/active-team.d.ts +9 -0
  249. package/dist/scripts/notify-hook/active-team.d.ts.map +1 -0
  250. package/dist/scripts/notify-hook/active-team.js +44 -0
  251. package/dist/scripts/notify-hook/active-team.js.map +1 -0
  252. package/dist/scripts/notify-hook/auto-nudge.d.ts +5 -3
  253. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  254. package/dist/scripts/notify-hook/auto-nudge.js +121 -78
  255. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  256. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  257. package/dist/scripts/notify-hook/managed-tmux.js +18 -4
  258. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  259. package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
  260. package/dist/scripts/notify-hook/operational-events.js +21 -0
  261. package/dist/scripts/notify-hook/operational-events.js.map +1 -1
  262. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  263. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -2
  264. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  265. package/dist/scripts/notify-hook/state-io.d.ts +10 -1
  266. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  267. package/dist/scripts/notify-hook/state-io.js +56 -12
  268. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  269. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  270. package/dist/scripts/notify-hook/team-dispatch.js +305 -167
  271. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  272. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  273. package/dist/scripts/notify-hook/team-leader-nudge.js +87 -15
  274. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  275. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  276. package/dist/scripts/notify-hook/tmux-injection.js +11 -2
  277. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  278. package/dist/scripts/notify-hook.js +26 -16
  279. package/dist/scripts/notify-hook.js.map +1 -1
  280. package/dist/scripts/run-provider-advisor.js +20 -2
  281. package/dist/scripts/run-provider-advisor.js.map +1 -1
  282. package/dist/scripts/smoke-packed-install.d.ts +1 -8
  283. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  284. package/dist/scripts/smoke-packed-install.js +12 -68
  285. package/dist/scripts/smoke-packed-install.js.map +1 -1
  286. package/dist/state/__tests__/operations.test.js +113 -0
  287. package/dist/state/__tests__/operations.test.js.map +1 -1
  288. package/dist/state/__tests__/skill-active.test.js +35 -0
  289. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  290. package/dist/state/__tests__/workflow-transition.test.d.ts +2 -0
  291. package/dist/state/__tests__/workflow-transition.test.d.ts.map +1 -0
  292. package/dist/state/__tests__/workflow-transition.test.js +56 -0
  293. package/dist/state/__tests__/workflow-transition.test.js.map +1 -0
  294. package/dist/state/operations.d.ts +1 -1
  295. package/dist/state/operations.d.ts.map +1 -1
  296. package/dist/state/operations.js +88 -2
  297. package/dist/state/operations.js.map +1 -1
  298. package/dist/state/skill-active.d.ts +2 -2
  299. package/dist/state/skill-active.d.ts.map +1 -1
  300. package/dist/state/skill-active.js +119 -33
  301. package/dist/state/skill-active.js.map +1 -1
  302. package/dist/state/workflow-transition-reconcile.d.ts +15 -0
  303. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -0
  304. package/dist/state/workflow-transition-reconcile.js +100 -0
  305. package/dist/state/workflow-transition-reconcile.js.map +1 -0
  306. package/dist/state/workflow-transition.d.ts +22 -0
  307. package/dist/state/workflow-transition.d.ts.map +1 -0
  308. package/dist/state/workflow-transition.js +188 -0
  309. package/dist/state/workflow-transition.js.map +1 -0
  310. package/dist/team/__tests__/api-interop.test.js +90 -0
  311. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  312. package/dist/team/__tests__/current-task-baseline.test.d.ts +2 -0
  313. package/dist/team/__tests__/current-task-baseline.test.d.ts.map +1 -0
  314. package/dist/team/__tests__/current-task-baseline.test.js +87 -0
  315. package/dist/team/__tests__/current-task-baseline.test.js.map +1 -0
  316. package/dist/team/__tests__/hardening-e2e.test.js +17 -0
  317. package/dist/team/__tests__/hardening-e2e.test.js.map +1 -1
  318. package/dist/team/__tests__/runtime.test.js +673 -65
  319. package/dist/team/__tests__/runtime.test.js.map +1 -1
  320. package/dist/team/__tests__/shutdown-fallback.test.js +11 -1
  321. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  322. package/dist/team/__tests__/tmux-session.test.js +447 -4
  323. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  324. package/dist/team/api-interop.d.ts.map +1 -1
  325. package/dist/team/api-interop.js +10 -1
  326. package/dist/team/api-interop.js.map +1 -1
  327. package/dist/team/current-task-baseline.d.ts +32 -0
  328. package/dist/team/current-task-baseline.d.ts.map +1 -0
  329. package/dist/team/current-task-baseline.js +85 -0
  330. package/dist/team/current-task-baseline.js.map +1 -0
  331. package/dist/team/delivery-log.d.ts +1 -1
  332. package/dist/team/delivery-log.d.ts.map +1 -1
  333. package/dist/team/delivery-log.js.map +1 -1
  334. package/dist/team/leader-activity.d.ts +1 -0
  335. package/dist/team/leader-activity.d.ts.map +1 -1
  336. package/dist/team/leader-activity.js +4 -2
  337. package/dist/team/leader-activity.js.map +1 -1
  338. package/dist/team/progress-evidence.d.ts +2 -0
  339. package/dist/team/progress-evidence.d.ts.map +1 -0
  340. package/dist/team/progress-evidence.js +77 -0
  341. package/dist/team/progress-evidence.js.map +1 -0
  342. package/dist/team/runtime.d.ts.map +1 -1
  343. package/dist/team/runtime.js +269 -64
  344. package/dist/team/runtime.js.map +1 -1
  345. package/dist/team/scaling.d.ts.map +1 -1
  346. package/dist/team/scaling.js +1 -1
  347. package/dist/team/scaling.js.map +1 -1
  348. package/dist/team/state.d.ts.map +1 -1
  349. package/dist/team/state.js +2 -13
  350. package/dist/team/state.js.map +1 -1
  351. package/dist/team/tmux-session.d.ts +12 -3
  352. package/dist/team/tmux-session.d.ts.map +1 -1
  353. package/dist/team/tmux-session.js +174 -20
  354. package/dist/team/tmux-session.js.map +1 -1
  355. package/dist/team/worktree.d.ts +6 -1
  356. package/dist/team/worktree.d.ts.map +1 -1
  357. package/dist/team/worktree.js +28 -4
  358. package/dist/team/worktree.js.map +1 -1
  359. package/dist/utils/__tests__/agents-md.test.js +21 -1
  360. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  361. package/dist/utils/__tests__/repo-deps.test.d.ts +2 -0
  362. package/dist/utils/__tests__/repo-deps.test.d.ts.map +1 -0
  363. package/dist/utils/__tests__/repo-deps.test.js +71 -0
  364. package/dist/utils/__tests__/repo-deps.test.js.map +1 -0
  365. package/dist/utils/agents-md.d.ts +1 -0
  366. package/dist/utils/agents-md.d.ts.map +1 -1
  367. package/dist/utils/agents-md.js +7 -3
  368. package/dist/utils/agents-md.js.map +1 -1
  369. package/dist/utils/paths.d.ts +4 -0
  370. package/dist/utils/paths.d.ts.map +1 -1
  371. package/dist/utils/paths.js +20 -0
  372. package/dist/utils/paths.js.map +1 -1
  373. package/dist/utils/repo-deps.d.ts +20 -0
  374. package/dist/utils/repo-deps.d.ts.map +1 -0
  375. package/dist/utils/repo-deps.js +78 -0
  376. package/dist/utils/repo-deps.js.map +1 -0
  377. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts +2 -0
  378. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts.map +1 -0
  379. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +54 -0
  380. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -0
  381. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts +12 -0
  382. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts.map +1 -0
  383. package/dist/wiki/__tests__/cjk-tokenize.test.js +139 -0
  384. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -0
  385. package/dist/wiki/__tests__/crlf-parse.test.d.ts +2 -0
  386. package/dist/wiki/__tests__/crlf-parse.test.d.ts.map +1 -0
  387. package/dist/wiki/__tests__/crlf-parse.test.js +24 -0
  388. package/dist/wiki/__tests__/crlf-parse.test.js.map +1 -0
  389. package/dist/wiki/__tests__/escape-newline.test.d.ts +2 -0
  390. package/dist/wiki/__tests__/escape-newline.test.d.ts.map +1 -0
  391. package/dist/wiki/__tests__/escape-newline.test.js +45 -0
  392. package/dist/wiki/__tests__/escape-newline.test.js.map +1 -0
  393. package/dist/wiki/__tests__/ingest.test.d.ts +5 -0
  394. package/dist/wiki/__tests__/ingest.test.d.ts.map +1 -0
  395. package/dist/wiki/__tests__/ingest.test.js +181 -0
  396. package/dist/wiki/__tests__/ingest.test.js.map +1 -0
  397. package/dist/wiki/__tests__/lint.test.d.ts +5 -0
  398. package/dist/wiki/__tests__/lint.test.d.ts.map +1 -0
  399. package/dist/wiki/__tests__/lint.test.js +163 -0
  400. package/dist/wiki/__tests__/lint.test.js.map +1 -0
  401. package/dist/wiki/__tests__/query.test.d.ts +5 -0
  402. package/dist/wiki/__tests__/query.test.d.ts.map +1 -0
  403. package/dist/wiki/__tests__/query.test.js +141 -0
  404. package/dist/wiki/__tests__/query.test.js.map +1 -0
  405. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts +2 -0
  406. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts.map +1 -0
  407. package/dist/wiki/__tests__/reserved-file-guard.test.js +44 -0
  408. package/dist/wiki/__tests__/reserved-file-guard.test.js.map +1 -0
  409. package/dist/wiki/__tests__/session-hooks.test.d.ts +5 -0
  410. package/dist/wiki/__tests__/session-hooks.test.d.ts.map +1 -0
  411. package/dist/wiki/__tests__/session-hooks.test.js +36 -0
  412. package/dist/wiki/__tests__/session-hooks.test.js.map +1 -0
  413. package/dist/wiki/__tests__/slug-nonascii.test.d.ts +2 -0
  414. package/dist/wiki/__tests__/slug-nonascii.test.d.ts.map +1 -0
  415. package/dist/wiki/__tests__/slug-nonascii.test.js +24 -0
  416. package/dist/wiki/__tests__/slug-nonascii.test.js.map +1 -0
  417. package/dist/wiki/__tests__/storage.test.d.ts +5 -0
  418. package/dist/wiki/__tests__/storage.test.d.ts.map +1 -0
  419. package/dist/wiki/__tests__/storage.test.js +278 -0
  420. package/dist/wiki/__tests__/storage.test.js.map +1 -0
  421. package/dist/wiki/__tests__/test-helpers.d.ts +31 -0
  422. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -0
  423. package/dist/wiki/__tests__/test-helpers.js +108 -0
  424. package/dist/wiki/__tests__/test-helpers.js.map +1 -0
  425. package/dist/wiki/index.d.ts +14 -0
  426. package/dist/wiki/index.d.ts.map +1 -0
  427. package/dist/wiki/index.js +17 -0
  428. package/dist/wiki/index.js.map +1 -0
  429. package/dist/wiki/ingest.d.ts +20 -0
  430. package/dist/wiki/ingest.d.ts.map +1 -0
  431. package/dist/wiki/ingest.js +115 -0
  432. package/dist/wiki/ingest.js.map +1 -0
  433. package/dist/wiki/lifecycle.d.ts +20 -0
  434. package/dist/wiki/lifecycle.d.ts.map +1 -0
  435. package/dist/wiki/lifecycle.js +212 -0
  436. package/dist/wiki/lifecycle.js.map +1 -0
  437. package/dist/wiki/lint.d.ts +25 -0
  438. package/dist/wiki/lint.d.ts.map +1 -0
  439. package/dist/wiki/lint.js +166 -0
  440. package/dist/wiki/lint.js.map +1 -0
  441. package/dist/wiki/query.d.ts +36 -0
  442. package/dist/wiki/query.d.ts.map +1 -0
  443. package/dist/wiki/query.js +138 -0
  444. package/dist/wiki/query.js.map +1 -0
  445. package/dist/wiki/storage.d.ts +33 -0
  446. package/dist/wiki/storage.d.ts.map +1 -0
  447. package/dist/wiki/storage.js +321 -0
  448. package/dist/wiki/storage.js.map +1 -0
  449. package/dist/wiki/types.d.ts +83 -0
  450. package/dist/wiki/types.d.ts.map +1 -0
  451. package/dist/wiki/types.js +15 -0
  452. package/dist/wiki/types.js.map +1 -0
  453. package/package.json +3 -1
  454. package/skills/configure-notifications/SKILL.md +1 -0
  455. package/skills/doctor/SKILL.md +11 -0
  456. package/skills/omx-setup/SKILL.md +1 -1
  457. package/skills/wiki/SKILL.md +57 -0
  458. package/src/scripts/__tests__/codex-native-hook.test.ts +920 -56
  459. package/src/scripts/__tests__/hook-derived-watcher.test.ts +111 -0
  460. package/src/scripts/codex-native-hook.ts +377 -83
  461. package/src/scripts/hook-derived-watcher.ts +43 -1
  462. package/src/scripts/notify-fallback-watcher.ts +99 -20
  463. package/src/scripts/notify-hook/active-team.ts +54 -0
  464. package/src/scripts/notify-hook/auto-nudge.ts +132 -79
  465. package/src/scripts/notify-hook/managed-tmux.ts +22 -4
  466. package/src/scripts/notify-hook/operational-events.ts +21 -0
  467. package/src/scripts/notify-hook/ralph-session-resume.ts +3 -2
  468. package/src/scripts/notify-hook/state-io.ts +89 -12
  469. package/src/scripts/notify-hook/team-dispatch.ts +326 -168
  470. package/src/scripts/notify-hook/team-leader-nudge.ts +91 -14
  471. package/src/scripts/notify-hook/tmux-injection.ts +11 -2
  472. package/src/scripts/notify-hook.ts +36 -22
  473. package/src/scripts/run-provider-advisor.ts +20 -2
  474. package/src/scripts/smoke-packed-install.ts +16 -83
  475. package/templates/AGENTS.md +3 -4
@@ -2,13 +2,14 @@ import { execFileSync } from "child_process";
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { mkdir, readFile, readdir, writeFile } from "fs/promises";
4
4
  import { join, resolve } from "path";
5
- import { readModeState, updateModeState } from "../modes/base.js";
5
+ import { readModeState, readModeStateForSession, updateModeState } from "../modes/base.js";
6
6
  import {
7
7
  listActiveSkills,
8
8
  readVisibleSkillActiveState,
9
9
  } from "../state/skill-active.js";
10
10
  import { readSubagentSessionSummary } from "../subagents/tracker.js";
11
11
  import { resolveCanonicalTeamStateRoot } from "../team/state-root.js";
12
+ import { readUsableSessionState, reconcileNativeSessionStart } from "../hooks/session.js";
12
13
  import {
13
14
  appendTeamEvent,
14
15
  readTeamLeaderAttention,
@@ -17,8 +18,10 @@ import {
17
18
  writeTeamLeaderAttention,
18
19
  writeTeamPhase,
19
20
  } from "../team/state.js";
20
- import { omxNotepadPath, omxProjectMemoryPath, omxStateDir } from "../utils/paths.js";
21
+ import { omxNotepadPath, omxProjectMemoryPath } from "../utils/paths.js";
22
+ import { getStateFilePath } from "../mcp/state-paths.js";
21
23
  import {
24
+ detectKeywords,
22
25
  detectPrimaryKeyword,
23
26
  recordSkillActivation,
24
27
  type SkillActiveState,
@@ -27,6 +30,7 @@ import {
27
30
  detectStallPattern,
28
31
  loadAutoNudgeConfig,
29
32
  normalizeAutoNudgeSignatureText,
33
+ resolveEffectiveAutoNudgeResponse,
30
34
  } from "./notify-hook/auto-nudge.js";
31
35
  import {
32
36
  buildNativePostToolUseOutput,
@@ -38,8 +42,8 @@ import {
38
42
  } from "../hooks/extensibility/events.js";
39
43
  import type { HookEventEnvelope } from "../hooks/extensibility/types.js";
40
44
  import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
41
- import { writeSessionStart } from "../hooks/session.js";
42
45
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
46
+ import { onSessionStart as buildWikiSessionStartContext } from "../wiki/lifecycle.js";
43
47
 
44
48
  type CodexHookEventName =
45
49
  | "SessionStart"
@@ -64,9 +68,17 @@ export interface NativeHookDispatchResult {
64
68
 
65
69
  const TERMINAL_RALPH_PHASES = new Set(["complete", "failed", "cancelled"]);
66
70
  const TERMINAL_MODE_PHASES = new Set(["complete", "failed", "cancelled"]);
67
- const SKILL_STOP_BLOCKERS = new Set(["ralplan", "deep-interview"]);
71
+ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
68
72
  const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
69
73
  const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
74
+ const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
75
+ /^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
76
+ /^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
77
+ /^\s*(?:final\s+)?recommendation\s*:\s*(?:yes|no|ship|hold|release|do not release|proceed|do not proceed)\b[^\n\r]*/im,
78
+ /^\s*decision\s*:\s*(?:yes|no|ship|hold|release|do not release|proceed|do not proceed)\b[^\n\r]*/im,
79
+ ] as const;
80
+ const RELEASE_READINESS_FINALIZE_SYSTEM_MESSAGE =
81
+ "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.";
70
82
 
71
83
  function safeString(value: unknown): string {
72
84
  return typeof value === "string" ? value : "";
@@ -138,6 +150,21 @@ function readPromptText(payload: CodexHookPayload): string {
138
150
  return "";
139
151
  }
140
152
 
153
+ function sanitizePayloadForHookContext(
154
+ payload: CodexHookPayload,
155
+ hookEventName: CodexHookEventName,
156
+ ): CodexHookPayload {
157
+ if (hookEventName !== "UserPromptSubmit") return payload;
158
+
159
+ const sanitized = { ...payload };
160
+ delete sanitized.prompt;
161
+ delete sanitized.input;
162
+ delete sanitized.user_prompt;
163
+ delete sanitized.userPrompt;
164
+ delete sanitized.text;
165
+ return sanitized;
166
+ }
167
+
141
168
  function buildBaseContext(
142
169
  cwd: string,
143
170
  payload: CodexHookPayload,
@@ -148,10 +175,7 @@ function buildBaseContext(
148
175
  project_path: cwd,
149
176
  transcript_path: safeString(payload.transcript_path ?? payload.transcriptPath) || null,
150
177
  source: safeString(payload.source),
151
- payload,
152
- ...(hookEventName === "UserPromptSubmit"
153
- ? { prompt: readPromptText(payload) }
154
- : {}),
178
+ payload: sanitizePayloadForHookContext(payload, hookEventName),
155
179
  };
156
180
  }
157
181
 
@@ -174,12 +198,20 @@ function formatPhase(value: unknown, fallback = "active"): string {
174
198
  return phase || fallback;
175
199
  }
176
200
 
177
- async function readActiveRalphState(stateDir: string): Promise<Record<string, unknown> | null> {
178
- const sessionInfo = await readJsonIfExists(join(stateDir, "session.json"));
201
+ async function readActiveRalphState(
202
+ stateDir: string,
203
+ preferredSessionId?: string,
204
+ ): Promise<Record<string, unknown> | null> {
205
+ const sessionInfo = await readUsableSessionState(resolve(stateDir, "..", ".."));
179
206
  const currentOmxSessionId = safeString(sessionInfo?.session_id).trim();
180
- if (currentOmxSessionId) {
207
+ const sessionCandidates = [...new Set([
208
+ safeString(preferredSessionId).trim(),
209
+ currentOmxSessionId,
210
+ ].filter(Boolean))];
211
+
212
+ for (const sessionId of sessionCandidates) {
181
213
  const sessionScoped = await readJsonIfExists(
182
- join(stateDir, "sessions", currentOmxSessionId, "ralph-state.json"),
214
+ join(stateDir, "sessions", sessionId, "ralph-state.json"),
183
215
  );
184
216
  if (
185
217
  sessionScoped?.active === true
@@ -191,13 +223,13 @@ async function readActiveRalphState(stateDir: string): Promise<Record<string, un
191
223
  }
192
224
  }
193
225
 
226
+ if (sessionCandidates.length > 0) return null;
227
+
194
228
  const direct = await readJsonIfExists(join(stateDir, "ralph-state.json"));
195
229
  if (direct?.active === true && !TERMINAL_RALPH_PHASES.has(safeString(direct.current_phase).trim().toLowerCase())) {
196
230
  return direct;
197
231
  }
198
232
 
199
- if (currentOmxSessionId) return null;
200
-
201
233
  const sessionsRoot = join(stateDir, "sessions");
202
234
  if (!existsSync(sessionsRoot)) return null;
203
235
  const entries = await readdir(sessionsRoot, { withFileTypes: true }).catch(() => []);
@@ -415,6 +447,11 @@ async function buildSessionStartContext(
415
447
  }
416
448
  }
417
449
 
450
+ const wikiContext = buildWikiSessionStartContext({ cwd });
451
+ if (wikiContext.additionalContext) {
452
+ sections.push(wikiContext.additionalContext);
453
+ }
454
+
418
455
  const subagentSummary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
419
456
  if (subagentSummary && subagentSummary.activeSubagentThreadIds.length > 0) {
420
457
  sections.push(`[Subagents]\n- active subagent threads: ${subagentSummary.activeSubagentThreadIds.length}`);
@@ -425,31 +462,84 @@ async function buildSessionStartContext(
425
462
 
426
463
  function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveState | null): string | null {
427
464
  if (!prompt) return null;
465
+ const matches = detectKeywords(prompt);
428
466
  const match = detectPrimaryKeyword(prompt);
429
467
  if (!match) return null;
468
+ const detectedKeywordMessage = matches.length > 1
469
+ ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
470
+ : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
471
+ const activeSkills = Array.isArray(skillState?.active_skills)
472
+ ? skillState.active_skills.map((entry) => entry.skill)
473
+ : [];
474
+ const deferredSkills = Array.isArray(skillState?.deferred_skills)
475
+ ? skillState.deferred_skills
476
+ : [];
477
+ const teamDetected = activeSkills.includes("team");
478
+ const combinedTransitionMessage = (() => {
479
+ if (!skillState?.transition_message) return null;
480
+ if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
481
+ const source = skillState.transition_message.match(/^mode transiting: (.+?) -> /)?.[1];
482
+ if (!source) return skillState.transition_message;
483
+ return `mode transiting: ${source} -> ${activeSkills.join(" + ")}`;
484
+ })();
485
+
486
+ if (skillState?.transition_error) {
487
+ return [
488
+ `OMX native UserPromptSubmit denied workflow keyword "${match.keyword}" -> ${match.skill}.`,
489
+ skillState.transition_error,
490
+ 'Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.',
491
+ ].join(' ');
492
+ }
493
+
494
+ if (skillState?.transition_message) {
495
+ return [
496
+ detectedKeywordMessage,
497
+ combinedTransitionMessage,
498
+ activeSkills.length > 1 ? `active skills: ${activeSkills.join(", ")}.` : null,
499
+ deferredSkills.length > 0
500
+ ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
501
+ : null,
502
+ skillState.initialized_mode && skillState.initialized_state_path
503
+ ? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
504
+ : null,
505
+ teamDetected
506
+ ? "Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout."
507
+ : null,
508
+ teamDetected ? "If you need help, run `omx team --help`." : null,
509
+ 'Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.',
510
+ ].filter(Boolean).join(' ');
511
+ }
430
512
 
431
- if (match.skill === "team") {
513
+ if (teamDetected) {
432
514
  const initializedStateMessage = skillState?.initialized_mode && skillState.initialized_state_path
433
515
  ? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
434
516
  : null;
435
517
  return [
436
- `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`,
518
+ detectedKeywordMessage,
519
+ activeSkills.length > 1 ? `active skills: ${activeSkills.join(", ")}.` : null,
520
+ deferredSkills.length > 0
521
+ ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
522
+ : null,
437
523
  initializedStateMessage,
438
524
  "Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout.",
439
525
  "If you need help, run `omx team --help`.",
440
- "Follow AGENTS.md routing and preserve ralplan/ralph execution gates.",
526
+ "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
441
527
  ].filter(Boolean).join(" ");
442
528
  }
443
529
 
444
530
  if (skillState?.initialized_mode && skillState.initialized_state_path) {
445
531
  return [
446
- `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`,
532
+ detectedKeywordMessage,
533
+ activeSkills.length > 1 ? `active skills: ${activeSkills.join(", ")}.` : null,
534
+ deferredSkills.length > 0
535
+ ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
536
+ : null,
447
537
  `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
448
- "Follow AGENTS.md routing and preserve ralplan/ralph execution gates.",
538
+ "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
449
539
  ].join(" ");
450
540
  }
451
541
 
452
- return `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}. Follow AGENTS.md routing and preserve ralplan/ralph execution gates.`;
542
+ return `${detectedKeywordMessage} Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.`;
453
543
  }
454
544
 
455
545
  function parseTeamWorkerEnv(rawValue: string): { teamName: string; workerName: string } | null {
@@ -570,8 +660,11 @@ function isStopExempt(payload: CodexHookPayload): boolean {
570
660
  async function buildModeBasedStopOutput(
571
661
  mode: "autopilot" | "ultrawork" | "ultraqa",
572
662
  cwd: string,
663
+ sessionId?: string,
573
664
  ): Promise<Record<string, unknown> | null> {
574
- const state = await readModeState(mode, cwd);
665
+ const state = sessionId
666
+ ? await readModeStateForSession(mode, sessionId, cwd)
667
+ : await readModeState(mode, cwd);
575
668
  if (state?.active !== true || !isNonTerminalPhase(state.current_phase)) return null;
576
669
  const phase = formatPhase(state.current_phase);
577
670
  return {
@@ -582,10 +675,39 @@ async function buildModeBasedStopOutput(
582
675
  };
583
676
  }
584
677
 
585
- async function buildTeamStopOutput(cwd: string): Promise<Record<string, unknown> | null> {
586
- const teamState = await readModeState("team", cwd);
678
+ async function readTeamModeStateForStop(
679
+ cwd: string,
680
+ sessionId?: string,
681
+ ): Promise<Record<string, unknown> | null> {
682
+ const normalizedSessionId = safeString(sessionId).trim();
683
+ if (!normalizedSessionId) {
684
+ return await readModeState("team", cwd);
685
+ }
686
+
687
+ const scopedState = await readStopSessionPinnedState("team-state.json", cwd, normalizedSessionId);
688
+ if (scopedState) return scopedState;
689
+
690
+ const rootState = await readJsonIfExists(join(cwd, ".omx", "state", "team-state.json"));
691
+ if (rootState?.active !== true) return null;
692
+
693
+ const ownerSessionId = safeString(rootState.session_id).trim();
694
+ if (ownerSessionId && ownerSessionId !== normalizedSessionId) {
695
+ return null;
696
+ }
697
+
698
+ return rootState;
699
+ }
700
+
701
+ async function buildTeamStopOutput(cwd: string, sessionId?: string): Promise<Record<string, unknown> | null> {
702
+ const teamState = await readTeamModeStateForStop(cwd, sessionId);
587
703
  if (teamState?.active !== true) return null;
588
704
  const teamName = safeString(teamState.team_name).trim();
705
+ if (teamName) {
706
+ const canonicalTeamDir = join(resolveCanonicalTeamStateRoot(cwd), "team", teamName);
707
+ if (!existsSync(canonicalTeamDir)) {
708
+ return null;
709
+ }
710
+ }
589
711
  const coarsePhase = teamState.current_phase;
590
712
  const canonicalPhase = teamName ? (await readTeamPhase(teamName, cwd))?.current_phase ?? coarsePhase : coarsePhase;
591
713
  if (!isNonTerminalPhase(canonicalPhase)) return null;
@@ -606,6 +728,54 @@ function buildTeamStopOutputForPhase(teamName: string, phase: string): Record<st
606
728
  };
607
729
  }
608
730
 
731
+ function extractStableFinalRecommendationSummary(message: string): string {
732
+ for (const pattern of STABLE_FINAL_RECOMMENDATION_PATTERNS) {
733
+ const match = pattern.exec(message);
734
+ if (!match) continue;
735
+ const summary = match[0]?.trim().replace(/\s+/g, " ");
736
+ if (!summary) continue;
737
+ return /[.!?]$/.test(summary) ? summary : `${summary}.`;
738
+ }
739
+ return "";
740
+ }
741
+
742
+ function buildStableFinalRecommendationStopSignature(
743
+ payload: CodexHookPayload,
744
+ teamName: string,
745
+ summary: string,
746
+ ): string {
747
+ const sessionId = readPayloadSessionId(payload) || "no-session";
748
+ const threadId = readPayloadThreadId(payload) || "no-thread";
749
+ const normalizedSummary = normalizeAutoNudgeSignatureText(summary) || summary.toLowerCase();
750
+ return ["release-readiness-finalize", sessionId, threadId, teamName, normalizedSummary].join("|");
751
+ }
752
+
753
+ function hasReleaseReadinessMode(payload: CodexHookPayload): boolean {
754
+ const mode = safeString(payload.mode).trim().toLowerCase();
755
+ return mode === "release-readiness";
756
+ }
757
+
758
+ async function hasReleaseReadinessStopMarker(
759
+ cwd: string,
760
+ sessionId: string,
761
+ teamName: string,
762
+ ): Promise<boolean> {
763
+ if (!sessionId) return false;
764
+
765
+ const markerState = await readStopSessionPinnedState("release-readiness-state.json", cwd, sessionId);
766
+ if (markerState?.active !== true || markerState.stable_final_recommendation_emitted !== true) {
767
+ return false;
768
+ }
769
+
770
+ const markerTeamName = safeString(markerState.team_name).trim();
771
+ if (markerTeamName && markerTeamName !== teamName) return false;
772
+
773
+ const markerSessionId = safeString(markerState.session_id).trim();
774
+ if (markerSessionId && markerSessionId !== sessionId) return false;
775
+
776
+ return true;
777
+ }
778
+
609
779
  function readPayloadSessionId(payload: CodexHookPayload): string {
610
780
  return safeString(payload.session_id ?? payload.sessionId).trim();
611
781
  }
@@ -618,29 +788,19 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
618
788
  return safeString(payload.turn_id ?? payload.turnId).trim();
619
789
  }
620
790
 
621
- async function isDeepInterviewSuppressedForStop(
791
+ async function resolveInternalSessionIdForPayload(
622
792
  cwd: string,
623
- sessionId: string,
624
- threadId: string,
625
- ): Promise<boolean> {
626
- const scopedModeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
627
- if (scopedModeState?.active === true) return true;
628
-
629
- const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
630
- const deepInterviewEntry = canonicalState
631
- ? listActiveSkills(canonicalState).find((entry) => (
632
- entry.skill === "deep-interview"
633
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
634
- ))
635
- : null;
636
- if (
637
- deepInterviewEntry
638
- && safeObject(canonicalState?.input_lock).active === true
639
- ) {
640
- return true;
641
- }
642
-
643
- return (await readBlockingSkillForStop(cwd, sessionId, threadId, "deep-interview")) !== null;
793
+ payloadSessionId: string,
794
+ ): Promise<string> {
795
+ const currentSession = await readUsableSessionState(cwd);
796
+ const canonicalSessionId = safeString(currentSession?.session_id).trim();
797
+ if (!canonicalSessionId) return payloadSessionId;
798
+
799
+ const nativeSessionId = safeString(currentSession?.native_session_id).trim();
800
+ if (!payloadSessionId) return canonicalSessionId;
801
+ if (payloadSessionId === canonicalSessionId) return canonicalSessionId;
802
+ if (nativeSessionId && payloadSessionId === nativeSessionId) return canonicalSessionId;
803
+ return payloadSessionId;
644
804
  }
645
805
 
646
806
  async function readStopSessionPinnedState(
@@ -648,10 +808,7 @@ async function readStopSessionPinnedState(
648
808
  cwd: string,
649
809
  sessionId: string,
650
810
  ): Promise<Record<string, unknown> | null> {
651
- const stateDir = omxStateDir(cwd);
652
- const statePath = sessionId
653
- ? join(stateDir, "sessions", sessionId, fileName)
654
- : join(stateDir, fileName);
811
+ const statePath = getStateFilePath(fileName, cwd, sessionId || undefined);
655
812
  return readJsonIfExists(statePath);
656
813
  }
657
814
 
@@ -716,6 +873,28 @@ async function readBlockingSkillForStop(
716
873
  return null;
717
874
  }
718
875
 
876
+ async function readStopAutoNudgePhase(
877
+ cwd: string,
878
+ sessionId: string,
879
+ threadId: string,
880
+ ): Promise<string> {
881
+ if (!sessionId.trim()) return "";
882
+
883
+ const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
884
+ const visibleEntries = canonicalState ? listActiveSkills(canonicalState) : [];
885
+ const deepInterview = visibleEntries.find((entry) => (
886
+ entry.skill === "deep-interview"
887
+ && matchesSkillStopContext(entry, canonicalState ?? {}, sessionId, threadId)
888
+ ));
889
+ if (!deepInterview) return "";
890
+
891
+ const modeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
892
+ if (!modeState || modeState.active !== true) return "";
893
+
894
+ const modePhase = safeString(modeState.current_phase).trim().toLowerCase();
895
+ return modePhase === "intent-first" ? "planning" : "";
896
+ }
897
+
719
898
  function buildRepeatableStopSignature(
720
899
  payload: CodexHookPayload,
721
900
  kind: string,
@@ -750,10 +929,6 @@ function buildRepeatableStopSignature(
750
929
  ].join("|");
751
930
  }
752
931
 
753
- async function readNativeStopState(stateDir: string): Promise<Record<string, unknown>> {
754
- return await readJsonIfExists(join(stateDir, NATIVE_STOP_STATE_FILE)) ?? {};
755
- }
756
-
757
932
  function readNativeStopSessionKey(payload: CodexHookPayload): string {
758
933
  return readPayloadSessionId(payload) || readPayloadThreadId(payload) || "global";
759
934
  }
@@ -773,7 +948,8 @@ async function persistNativeStopSignature(
773
948
  signature: string,
774
949
  ): Promise<void> {
775
950
  if (!signature) return;
776
- const state = await readNativeStopState(stateDir);
951
+ const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
952
+ const state = await readJsonIfExists(statePath) ?? {};
777
953
  const sessions = safeObject(state.sessions);
778
954
  const sessionKey = readNativeStopSessionKey(payload);
779
955
  sessions[sessionKey] = {
@@ -781,7 +957,8 @@ async function persistNativeStopSignature(
781
957
  last_signature: signature,
782
958
  updated_at: new Date().toISOString(),
783
959
  };
784
- await writeFile(join(stateDir, NATIVE_STOP_STATE_FILE), JSON.stringify({
960
+ await mkdir(stateDir, { recursive: true });
961
+ await writeFile(statePath, JSON.stringify({
785
962
  ...state,
786
963
  sessions,
787
964
  }, null, 2));
@@ -796,7 +973,7 @@ async function maybeReturnRepeatableStopOutput(
796
973
  if (!output) return null;
797
974
  const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
798
975
  if (stopHookActive) {
799
- const state = await readNativeStopState(stateDir);
976
+ const state = await readJsonIfExists(join(stateDir, NATIVE_STOP_STATE_FILE)) ?? {};
800
977
  const previousSignature = readPreviousNativeStopSignature(state, readNativeStopSessionKey(payload));
801
978
  if (!signature || previousSignature === signature) {
802
979
  return null;
@@ -838,6 +1015,66 @@ async function findCanonicalActiveTeamForSession(
838
1015
  return null;
839
1016
  }
840
1017
 
1018
+ async function resolveActiveTeamNameForStop(
1019
+ cwd: string,
1020
+ sessionId: string,
1021
+ ): Promise<string> {
1022
+ const directState = await readTeamModeStateForStop(cwd, sessionId);
1023
+ const directTeamName = safeString(directState?.team_name).trim();
1024
+ if (directState?.active === true && directTeamName) return directTeamName;
1025
+
1026
+ const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId);
1027
+ return canonicalTeam?.teamName ?? "";
1028
+ }
1029
+
1030
+ async function maybeBuildReleaseReadinessFinalizeStopOutput(
1031
+ payload: CodexHookPayload,
1032
+ cwd: string,
1033
+ stateDir: string,
1034
+ sessionId: string,
1035
+ ): Promise<{ matched: boolean; output: Record<string, unknown> | null }> {
1036
+ if (!sessionId) return { matched: false, output: null };
1037
+
1038
+ const teamName = await resolveActiveTeamNameForStop(cwd, sessionId);
1039
+ if (!teamName) return { matched: false, output: null };
1040
+
1041
+ const explicitReleaseReadinessContext =
1042
+ hasReleaseReadinessMode(payload)
1043
+ || await hasReleaseReadinessStopMarker(cwd, sessionId, teamName);
1044
+ if (!explicitReleaseReadinessContext) {
1045
+ return { matched: false, output: null };
1046
+ }
1047
+
1048
+ const summary = extractStableFinalRecommendationSummary(
1049
+ safeString(payload.last_assistant_message ?? payload.lastAssistantMessage),
1050
+ );
1051
+ if (!summary) return { matched: false, output: null };
1052
+
1053
+ const leaderAttention = await readTeamLeaderAttention(teamName, cwd);
1054
+ if (
1055
+ !leaderAttention
1056
+ || leaderAttention.leader_decision_state !== "done_waiting_on_leader"
1057
+ || leaderAttention.work_remaining !== false
1058
+ ) {
1059
+ return { matched: false, output: null };
1060
+ }
1061
+
1062
+ const signature = buildStableFinalRecommendationStopSignature(payload, teamName, summary);
1063
+ const output = await maybeReturnRepeatableStopOutput(
1064
+ payload,
1065
+ stateDir,
1066
+ signature,
1067
+ {
1068
+ decision: "block",
1069
+ reason:
1070
+ `Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "${summary}" with no filler or residual acknowledgements (for example "yes"), then stop.`,
1071
+ stopReason: "release_readiness_auto_finalize",
1072
+ systemMessage: RELEASE_READINESS_FINALIZE_SYSTEM_MESSAGE,
1073
+ },
1074
+ );
1075
+ return { matched: true, output };
1076
+ }
1077
+
841
1078
  async function buildSkillStopOutput(
842
1079
  cwd: string,
843
1080
  sessionId: string,
@@ -863,7 +1100,7 @@ async function findActiveTeamForTransportFailure(
863
1100
  cwd: string,
864
1101
  sessionId: string,
865
1102
  ): Promise<{ teamName: string; phase: string } | null> {
866
- const teamState = await readModeState("team", cwd);
1103
+ const teamState = await readModeStateForSession("team", sessionId, cwd);
867
1104
  if (teamState?.active === true) {
868
1105
  const teamName = safeString(teamState.team_name).trim();
869
1106
  const coarsePhase = formatPhase(teamState.current_phase);
@@ -882,8 +1119,8 @@ async function markTeamTransportFailure(
882
1119
  cwd: string,
883
1120
  payload: CodexHookPayload,
884
1121
  ): Promise<void> {
885
- const sessionId = readPayloadSessionId(payload);
886
- const activeTeam = await findActiveTeamForTransportFailure(cwd, sessionId);
1122
+ const canonicalSessionId = await resolveInternalSessionIdForPayload(cwd, readPayloadSessionId(payload));
1123
+ const activeTeam = await findActiveTeamForTransportFailure(cwd, canonicalSessionId);
887
1124
  if (!activeTeam) return;
888
1125
 
889
1126
  const nowIso = new Date().toISOString();
@@ -926,7 +1163,7 @@ async function markTeamTransportFailure(
926
1163
  ],
927
1164
  leader_stale: existingAttention?.leader_stale ?? false,
928
1165
  leader_session_active: existingAttention?.leader_session_active ?? true,
929
- leader_session_id: existingAttention?.leader_session_id ?? (sessionId || null),
1166
+ leader_session_id: existingAttention?.leader_session_id ?? (canonicalSessionId || null),
930
1167
  leader_session_stopped_at: existingAttention?.leader_session_stopped_at ?? null,
931
1168
  unread_leader_message_count: existingAttention?.unread_leader_message_count ?? 0,
932
1169
  work_remaining: existingAttention?.work_remaining ?? true,
@@ -957,6 +1194,7 @@ async function markTeamTransportFailure(
957
1194
  last_turn_at: nowIso,
958
1195
  },
959
1196
  cwd,
1197
+ canonicalSessionId || undefined,
960
1198
  );
961
1199
  } catch {
962
1200
  // Canonical team state already carries the preserved failure for coarse-state-missing sessions.
@@ -973,30 +1211,39 @@ async function buildStopHookOutput(
973
1211
  }
974
1212
 
975
1213
  const sessionId = readPayloadSessionId(payload);
1214
+ const canonicalSessionId = await resolveInternalSessionIdForPayload(cwd, sessionId);
976
1215
  const threadId = readPayloadThreadId(payload);
977
- const ralphState = await readActiveRalphState(stateDir);
1216
+ const ralphState = await readActiveRalphState(stateDir, canonicalSessionId);
978
1217
  const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
979
1218
  if (!ralphState) {
980
1219
  const teamWorkerOutput = await buildTeamWorkerStopOutput(cwd);
981
1220
  if (!stopHookActive && hasTeamWorkerContext()) return teamWorkerOutput;
982
1221
 
983
- const autopilotOutput = await buildModeBasedStopOutput("autopilot", cwd);
1222
+ const autopilotOutput = await buildModeBasedStopOutput("autopilot", cwd, canonicalSessionId);
984
1223
  if (!stopHookActive && autopilotOutput) return autopilotOutput;
985
1224
 
986
- const ultraworkOutput = await buildModeBasedStopOutput("ultrawork", cwd);
1225
+ const ultraworkOutput = await buildModeBasedStopOutput("ultrawork", cwd, canonicalSessionId);
987
1226
  if (!stopHookActive && ultraworkOutput) return ultraworkOutput;
988
1227
 
989
- const ultraqaOutput = await buildModeBasedStopOutput("ultraqa", cwd);
1228
+ const ultraqaOutput = await buildModeBasedStopOutput("ultraqa", cwd, canonicalSessionId);
990
1229
  if (!stopHookActive && ultraqaOutput) return ultraqaOutput;
991
1230
 
992
- const teamOutput = await buildTeamStopOutput(cwd);
1231
+ const releaseReadinessFinalizeResult = await maybeBuildReleaseReadinessFinalizeStopOutput(
1232
+ payload,
1233
+ cwd,
1234
+ stateDir,
1235
+ canonicalSessionId,
1236
+ );
1237
+ if (releaseReadinessFinalizeResult.matched) return releaseReadinessFinalizeResult.output;
1238
+
1239
+ const teamOutput = await buildTeamStopOutput(cwd, canonicalSessionId);
993
1240
  if (teamOutput) {
994
1241
  const teamSignature = buildRepeatableStopSignature(payload, "team-stop", safeString(teamOutput.stopReason));
995
1242
  return await maybeReturnRepeatableStopOutput(payload, stateDir, teamSignature, teamOutput);
996
1243
  }
997
1244
 
998
- if (sessionId) {
999
- const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId);
1245
+ if (canonicalSessionId) {
1246
+ const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
1000
1247
  if (canonicalTeam) {
1001
1248
  const canonicalTeamOutput = buildTeamStopOutputForPhase(
1002
1249
  canonicalTeam.teamName,
@@ -1012,28 +1259,28 @@ async function buildStopHookOutput(
1012
1259
  if (repeatedCanonicalTeamOutput) return repeatedCanonicalTeamOutput;
1013
1260
  }
1014
1261
 
1015
- const skillOutput = await buildSkillStopOutput(cwd, sessionId, threadId);
1262
+ const skillOutput = await buildSkillStopOutput(cwd, canonicalSessionId, threadId);
1016
1263
  if (!stopHookActive && skillOutput) return skillOutput;
1017
1264
  }
1018
1265
 
1019
- const deepInterviewActive = await isDeepInterviewSuppressedForStop(cwd, sessionId, threadId);
1020
1266
  const lastAssistantMessage = safeString(
1021
1267
  payload.last_assistant_message ?? payload.lastAssistantMessage,
1022
1268
  );
1023
1269
  const autoNudgeConfig = await loadAutoNudgeConfig();
1270
+ const autoNudgePhase = await readStopAutoNudgePhase(cwd, canonicalSessionId, threadId);
1024
1271
 
1025
1272
  if (
1026
- !deepInterviewActive
1027
- && autoNudgeConfig.enabled
1028
- && detectStallPattern(lastAssistantMessage, autoNudgeConfig.patterns)
1273
+ autoNudgeConfig.enabled
1274
+ && detectStallPattern(lastAssistantMessage, autoNudgeConfig.patterns, autoNudgePhase)
1029
1275
  ) {
1276
+ const effectiveResponse = resolveEffectiveAutoNudgeResponse(autoNudgeConfig.response);
1030
1277
  return await maybeReturnRepeatableStopOutput(
1031
1278
  payload,
1032
1279
  stateDir,
1033
1280
  buildRepeatableStopSignature(payload, "auto-nudge", lastAssistantMessage),
1034
1281
  {
1035
1282
  decision: "block",
1036
- reason: autoNudgeConfig.response,
1283
+ reason: effectiveResponse,
1037
1284
  stopReason: "auto_nudge",
1038
1285
  systemMessage:
1039
1286
  "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
@@ -1073,23 +1320,30 @@ export async function dispatchCodexNativeHook(
1073
1320
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
1074
1321
  let skillState: SkillActiveState | null = null;
1075
1322
 
1076
- const sessionId = safeString(payload.session_id ?? payload.sessionId).trim();
1323
+ const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
1077
1324
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
1078
1325
  const turnId = safeString(payload.turn_id ?? payload.turnId).trim();
1326
+ let canonicalSessionId = safeString((await readUsableSessionState(cwd))?.session_id).trim();
1079
1327
 
1080
- if (hookEventName === "SessionStart" && sessionId) {
1081
- await writeSessionStart(cwd, sessionId, {
1328
+ if (hookEventName === "SessionStart" && nativeSessionId) {
1329
+ const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
1082
1330
  pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
1083
1331
  });
1332
+ canonicalSessionId = safeString(sessionState.session_id).trim();
1333
+ } else if (!canonicalSessionId) {
1334
+ canonicalSessionId = safeString((await readUsableSessionState(cwd))?.session_id).trim();
1084
1335
  }
1085
1336
 
1337
+ const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
1338
+ const sessionIdForState = canonicalSessionId || nativeSessionId;
1339
+
1086
1340
  if (hookEventName === "UserPromptSubmit") {
1087
1341
  const prompt = readPromptText(payload);
1088
1342
  if (prompt) {
1089
1343
  skillState = await recordSkillActivation({
1090
1344
  stateDir,
1091
1345
  text: prompt,
1092
- sessionId,
1346
+ sessionId: sessionIdForState,
1093
1347
  threadId,
1094
1348
  turnId,
1095
1349
  });
@@ -1098,11 +1352,19 @@ export async function dispatchCodexNativeHook(
1098
1352
  }
1099
1353
 
1100
1354
  if (omxEventName) {
1355
+ const baseContext = buildBaseContext(cwd, payload, hookEventName!);
1356
+ if (nativeSessionId) {
1357
+ baseContext.native_session_id = nativeSessionId;
1358
+ baseContext.codex_session_id = nativeSessionId;
1359
+ }
1360
+ if (canonicalSessionId) {
1361
+ baseContext.omx_session_id = canonicalSessionId;
1362
+ }
1101
1363
  const event: HookEventEnvelope = buildNativeHookEvent(
1102
1364
  omxEventName,
1103
- buildBaseContext(cwd, payload, hookEventName!),
1365
+ baseContext,
1104
1366
  {
1105
- session_id: sessionId || undefined,
1367
+ session_id: eventSessionId,
1106
1368
  thread_id: threadId || undefined,
1107
1369
  turn_id: turnId || undefined,
1108
1370
  mode: safeString(payload.mode).trim() || undefined,
@@ -1114,7 +1376,7 @@ export async function dispatchCodexNativeHook(
1114
1376
  let outputJson: Record<string, unknown> | null = null;
1115
1377
  if (hookEventName === "SessionStart" || hookEventName === "UserPromptSubmit") {
1116
1378
  const additionalContext = hookEventName === "SessionStart"
1117
- ? await buildSessionStartContext(cwd, sessionId)
1379
+ ? await buildSessionStartContext(cwd, canonicalSessionId || nativeSessionId)
1118
1380
  : buildAdditionalContextMessage(readPromptText(payload), skillState);
1119
1381
  if (additionalContext) {
1120
1382
  outputJson = {
@@ -1143,17 +1405,49 @@ export async function dispatchCodexNativeHook(
1143
1405
  };
1144
1406
  }
1145
1407
 
1146
- async function readStdinJson(): Promise<CodexHookPayload> {
1408
+ interface NativeHookCliReadResult {
1409
+ payload: CodexHookPayload;
1410
+ parseError: Error | null;
1411
+ }
1412
+
1413
+ async function readStdinJson(): Promise<NativeHookCliReadResult> {
1147
1414
  const chunks: Buffer[] = [];
1148
1415
  for await (const chunk of process.stdin) {
1149
1416
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
1150
1417
  }
1151
1418
  const raw = Buffer.concat(chunks).toString("utf-8").trim();
1152
- return raw ? safeObject(JSON.parse(raw)) : {};
1419
+ if (!raw) {
1420
+ return { payload: {}, parseError: null };
1421
+ }
1422
+
1423
+ try {
1424
+ return {
1425
+ payload: safeObject(JSON.parse(raw)),
1426
+ parseError: null,
1427
+ };
1428
+ } catch (error) {
1429
+ return {
1430
+ payload: {},
1431
+ parseError: error instanceof Error ? error : new Error(String(error)),
1432
+ };
1433
+ }
1153
1434
  }
1154
1435
 
1155
1436
  export async function runCodexNativeHookCli(): Promise<void> {
1156
- const payload = await readStdinJson();
1437
+ const { payload, parseError } = await readStdinJson();
1438
+ if (parseError) {
1439
+ process.stdout.write(`${JSON.stringify({
1440
+ decision: "block",
1441
+ reason: "OMX native hook received malformed JSON input. Preserve runtime state and inspect the emitting hook payload before retrying.",
1442
+ hookSpecificOutput: {
1443
+ hookEventName: "Unknown",
1444
+ additionalContext:
1445
+ `stdin JSON parsing failed inside codex-native-hook: ${parseError.message}. Emit valid JSON from the native hook caller before retrying.`,
1446
+ },
1447
+ })}\n`);
1448
+ return;
1449
+ }
1450
+
1157
1451
  const result = await dispatchCodexNativeHook(payload);
1158
1452
  if (result.outputJson) {
1159
1453
  process.stdout.write(`${JSON.stringify(result.outputJson)}\n`);