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
@@ -6,13 +6,41 @@ import { tmpdir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
  import { afterEach, beforeEach, describe, it } from "node:test";
8
8
  import { buildManagedCodexHooksConfig } from "../../config/codex-hooks.js";
9
- import { initTeamState, readTeamLeaderAttention, readTeamPhase, } from "../../team/state.js";
9
+ import { initTeamState, readTeamLeaderAttention, readTeamPhase, writeTeamLeaderAttention, } from "../../team/state.js";
10
10
  import { dispatchCodexNativeHook, mapCodexHookEventToOmxEvent, resolveSessionOwnerPidFromAncestry, } from "../codex-native-hook.js";
11
+ import { writeSessionStart } from "../../hooks/session.js";
11
12
  async function writeJson(path, value) {
12
13
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
13
14
  await writeFile(path, JSON.stringify(value, null, 2));
14
15
  }
16
+ async function writeReleaseReadinessLeaderAttention(teamName, sessionId, cwd, options) {
17
+ await writeTeamLeaderAttention(teamName, {
18
+ team_name: teamName,
19
+ updated_at: "2026-04-12T17:20:00.000Z",
20
+ source: "notify_hook",
21
+ leader_decision_state: "done_waiting_on_leader",
22
+ leader_attention_pending: true,
23
+ leader_attention_reason: "leader_session_stopped",
24
+ attention_reasons: ["leader_session_stopped"],
25
+ leader_stale: true,
26
+ leader_session_active: false,
27
+ leader_session_id: sessionId,
28
+ leader_session_stopped_at: "2026-04-12T17:20:00.000Z",
29
+ unread_leader_message_count: 0,
30
+ work_remaining: options.workRemaining,
31
+ stalled_for_ms: null,
32
+ }, cwd);
33
+ }
34
+ async function writeReleaseReadinessStateMarker(sessionId, teamName, cwd) {
35
+ await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "release-readiness-state.json"), {
36
+ active: true,
37
+ session_id: sessionId,
38
+ team_name: teamName,
39
+ stable_final_recommendation_emitted: true,
40
+ });
41
+ }
15
42
  const TEAM_STOP_COMMIT_GUIDANCE = " If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
43
+ const DEFAULT_AUTO_NUDGE_RESPONSE = "continue with the current task only if it is already authorized";
16
44
  const TEAM_ENV_KEYS = [
17
45
  "OMX_TEAM_WORKER",
18
46
  "OMX_TEAM_STATE_ROOT",
@@ -58,6 +86,19 @@ describe("codex native hook config", () => {
58
86
  });
59
87
  });
60
88
  describe("codex native hook dispatch", () => {
89
+ it("emits deterministic JSON stdout when CLI stdin is malformed", () => {
90
+ const stdout = execFileSync(process.execPath, [join(process.cwd(), "dist", "scripts", "codex-native-hook.js")], {
91
+ cwd: process.cwd(),
92
+ input: "{",
93
+ encoding: "utf-8",
94
+ stdio: ["pipe", "pipe", "pipe"],
95
+ });
96
+ const output = JSON.parse(stdout.trim());
97
+ assert.equal(output.decision, "block");
98
+ assert.equal(output.reason, "OMX native hook received malformed JSON input. Preserve runtime state and inspect the emitting hook payload before retrying.");
99
+ assert.equal(output.hookSpecificOutput?.hookEventName, "Unknown");
100
+ assert.match(String(output.hookSpecificOutput?.additionalContext ?? ""), /stdin JSON parsing failed inside codex-native-hook:/);
101
+ });
61
102
  it("maps Codex events onto OMX logical surfaces", () => {
62
103
  assert.equal(mapCodexHookEventToOmxEvent("SessionStart"), "session-start");
63
104
  assert.equal(mapCodexHookEventToOmxEvent("UserPromptSubmit"), "keyword-detector");
@@ -85,12 +126,55 @@ describe("codex native hook dispatch", () => {
85
126
  });
86
127
  const sessionState = JSON.parse(await readFile(join(cwd, ".omx", "state", "session.json"), "utf-8"));
87
128
  assert.equal(sessionState.session_id, "sess-start-1");
129
+ assert.equal(sessionState.native_session_id, "sess-start-1");
88
130
  assert.equal(sessionState.pid, 43210);
89
131
  }
90
132
  finally {
91
133
  await rm(cwd, { recursive: true, force: true });
92
134
  }
93
135
  });
136
+ it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => {
137
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-"));
138
+ try {
139
+ const stateDir = join(cwd, ".omx", "state");
140
+ const canonicalSessionId = "omx-launch-1";
141
+ const nativeSessionId = "codex-native-1";
142
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
143
+ await writeSessionStart(cwd, canonicalSessionId);
144
+ await writeJson(join(stateDir, "sessions", canonicalSessionId, "hud-state.json"), {
145
+ last_turn_at: "2026-04-10T00:00:00.000Z",
146
+ turn_count: 1,
147
+ });
148
+ await dispatchCodexNativeHook({
149
+ hook_event_name: "SessionStart",
150
+ cwd,
151
+ session_id: nativeSessionId,
152
+ }, {
153
+ cwd,
154
+ sessionOwnerPid: process.pid,
155
+ });
156
+ const sessionState = JSON.parse(await readFile(join(stateDir, "session.json"), "utf-8"));
157
+ assert.equal(sessionState.session_id, canonicalSessionId);
158
+ assert.equal(sessionState.native_session_id, nativeSessionId);
159
+ assert.equal(sessionState.pid, process.pid);
160
+ const promptResult = await dispatchCodexNativeHook({
161
+ hook_event_name: "UserPromptSubmit",
162
+ cwd,
163
+ session_id: nativeSessionId,
164
+ thread_id: "thread-1",
165
+ turn_id: "turn-1",
166
+ prompt: "$ralplan fix hud scope drift",
167
+ }, { cwd });
168
+ assert.equal(promptResult.omxEventName, "keyword-detector");
169
+ assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), true);
170
+ assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), true);
171
+ assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json")), false);
172
+ assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json")), false);
173
+ }
174
+ finally {
175
+ await rm(cwd, { recursive: true, force: true });
176
+ }
177
+ });
94
178
  it("appends .omx/ to repo-root .gitignore during SessionStart when missing", async () => {
95
179
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-gitignore-"));
96
180
  try {
@@ -185,6 +269,40 @@ describe("codex native hook dispatch", () => {
185
269
  await rm(cwd, { recursive: true, force: true });
186
270
  }
187
271
  });
272
+ it("does not expose submitted prompt text to keyword-detector hook plugins", async () => {
273
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-prompt-sanitized-"));
274
+ try {
275
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
276
+ await writeFile(join(cwd, ".omx", "hooks", "capture-keyword-context.mjs"), `import { mkdir, writeFile } from "node:fs/promises";
277
+ import { dirname, join } from "node:path";
278
+
279
+ export async function onHookEvent(event) {
280
+ if (event.event !== "keyword-detector") return;
281
+ const outPath = join(process.cwd(), ".omx", "captured-keyword-context.json");
282
+ await mkdir(dirname(outPath), { recursive: true });
283
+ await writeFile(outPath, JSON.stringify(event.context, null, 2));
284
+ }
285
+ `, "utf-8");
286
+ await dispatchCodexNativeHook({
287
+ hook_event_name: "UserPromptSubmit",
288
+ cwd,
289
+ session_id: "sess-sanitized-1",
290
+ thread_id: "thread-sanitized-1",
291
+ turn_id: "turn-sanitized-1",
292
+ prompt: "$ralplan approve this blocker-sensitive request",
293
+ }, { cwd });
294
+ const captured = JSON.parse(await readFile(join(cwd, ".omx", "captured-keyword-context.json"), "utf-8"));
295
+ assert.equal(captured.prompt, undefined);
296
+ assert.equal(captured.payload?.prompt, undefined);
297
+ assert.equal(captured.payload?.input, undefined);
298
+ assert.equal(captured.payload?.user_prompt, undefined);
299
+ assert.equal(captured.payload?.userPrompt, undefined);
300
+ assert.equal(captured.payload?.text, undefined);
301
+ }
302
+ finally {
303
+ await rm(cwd, { recursive: true, force: true });
304
+ }
305
+ });
188
306
  it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => {
189
307
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-"));
190
308
  try {
@@ -232,6 +350,95 @@ describe("codex native hook dispatch", () => {
232
350
  await rm(cwd, { recursive: true, force: true });
233
351
  }
234
352
  });
353
+ it("returns actionable denial guidance for unsupported workflow overlaps on prompt submit", async () => {
354
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-deny-"));
355
+ try {
356
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
357
+ await dispatchCodexNativeHook({
358
+ hook_event_name: "UserPromptSubmit",
359
+ cwd,
360
+ session_id: "sess-deny-1",
361
+ thread_id: "thread-deny-1",
362
+ turn_id: "turn-deny-1",
363
+ prompt: "$team ship this fix",
364
+ }, { cwd });
365
+ const denied = await dispatchCodexNativeHook({
366
+ hook_event_name: "UserPromptSubmit",
367
+ cwd,
368
+ session_id: "sess-deny-1",
369
+ thread_id: "thread-deny-1",
370
+ turn_id: "turn-deny-2",
371
+ prompt: "$autopilot also run this",
372
+ }, { cwd });
373
+ assert.match(JSON.stringify(denied.outputJson), /denied workflow keyword/i);
374
+ assert.match(JSON.stringify(denied.outputJson), /Unsupported workflow overlap: team \+ autopilot\./);
375
+ assert.match(JSON.stringify(denied.outputJson), /`omx state clear --mode <mode>`/);
376
+ assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` MCP tools/);
377
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-deny-1", "autopilot-state.json")), false);
378
+ }
379
+ finally {
380
+ await rm(cwd, { recursive: true, force: true });
381
+ }
382
+ });
383
+ it("surfaces transition success output for allowlisted prompt-submit handoffs", async () => {
384
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-success-"));
385
+ try {
386
+ const sessionDir = join(cwd, ".omx", "state", "sessions", "sess-handoff-1");
387
+ await mkdir(sessionDir, { recursive: true });
388
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
389
+ active: true,
390
+ mode: "deep-interview",
391
+ current_phase: "intent-first",
392
+ });
393
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
394
+ active: true,
395
+ skill: "deep-interview",
396
+ phase: "planning",
397
+ session_id: "sess-handoff-1",
398
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-handoff-1" }],
399
+ });
400
+ const result = await dispatchCodexNativeHook({
401
+ hook_event_name: "UserPromptSubmit",
402
+ cwd,
403
+ session_id: "sess-handoff-1",
404
+ thread_id: "thread-handoff-1",
405
+ turn_id: "turn-handoff-1",
406
+ prompt: "$ralplan implement the approved contract",
407
+ }, { cwd });
408
+ assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ralplan/);
409
+ const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
410
+ assert.equal(completed.active, false);
411
+ assert.equal(completed.current_phase, "completed");
412
+ }
413
+ finally {
414
+ await rm(cwd, { recursive: true, force: true });
415
+ }
416
+ });
417
+ it("keeps the planning skill active when planning and execution workflows are invoked together", async () => {
418
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-planning-precedence-"));
419
+ try {
420
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
421
+ const result = await dispatchCodexNativeHook({
422
+ hook_event_name: "UserPromptSubmit",
423
+ cwd,
424
+ session_id: "sess-multi-1",
425
+ thread_id: "thread-multi-1",
426
+ turn_id: "turn-multi-1",
427
+ prompt: "$ralplan $team $ralph ship this fix",
428
+ }, { cwd });
429
+ const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || '');
430
+ assert.match(message, /\$ralplan" -> ralplan/);
431
+ assert.match(message, /\$team" -> team/);
432
+ assert.match(message, /\$ralph" -> ralph/);
433
+ assert.doesNotMatch(message, /mode transiting:/);
434
+ assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
435
+ assert.match(message, /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-multi-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
436
+ assert.doesNotMatch(message, /Use the durable OMX team runtime via `omx team \.\.\.`/);
437
+ }
438
+ finally {
439
+ await rm(cwd, { recursive: true, force: true });
440
+ }
441
+ });
235
442
  it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => {
236
443
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-"));
237
444
  const originalTmux = process.env.TMUX;
@@ -436,6 +643,47 @@ esac
436
643
  await rm(cwd, { recursive: true, force: true });
437
644
  }
438
645
  });
646
+ it("marks canonical team state failed when native payload session ids differ during MCP transport death", async () => {
647
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-native-transport-"));
648
+ const previousCwd = process.cwd();
649
+ const canonicalSessionId = "omx-canonical-session";
650
+ const nativeSessionId = "codex-native-session";
651
+ try {
652
+ process.chdir(cwd);
653
+ await writeSessionStart(cwd, canonicalSessionId);
654
+ const sessionPath = join(cwd, ".omx", "state", "session.json");
655
+ const sessionState = JSON.parse(await readFile(sessionPath, "utf-8"));
656
+ await writeFile(sessionPath, JSON.stringify({
657
+ ...sessionState,
658
+ native_session_id: nativeSessionId,
659
+ }, null, 2));
660
+ await initTeamState("transport-team", "task", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: canonicalSessionId });
661
+ await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
662
+ active: true,
663
+ team_name: "transport-team",
664
+ current_phase: "team-exec",
665
+ });
666
+ await dispatchCodexNativeHook({
667
+ hook_event_name: "PostToolUse",
668
+ cwd,
669
+ session_id: nativeSessionId,
670
+ tool_name: "mcp__omx_state__state_write",
671
+ tool_use_id: "tool-mcp-transport-team-native",
672
+ tool_input: { mode: "team", active: true },
673
+ tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
674
+ }, { cwd });
675
+ const phase = await readTeamPhase("transport-team", cwd);
676
+ const attention = await readTeamLeaderAttention("transport-team", cwd);
677
+ assert.equal(phase?.current_phase, "failed");
678
+ assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
679
+ assert.equal(attention?.leader_attention_pending, true);
680
+ assert.equal(attention?.leader_session_id, canonicalSessionId);
681
+ }
682
+ finally {
683
+ process.chdir(previousCwd);
684
+ await rm(cwd, { recursive: true, force: true });
685
+ }
686
+ });
439
687
  it("treats stderr-only informative non-zero output as reviewable instead of a generic failure", async () => {
440
688
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-stderr-"));
441
689
  try {
@@ -792,6 +1040,58 @@ esac
792
1040
  await rm(cwd, { recursive: true, force: true });
793
1041
  }
794
1042
  });
1043
+ it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
1044
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
1045
+ try {
1046
+ await initTeamState("release-ready-team", "release readiness finalize", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" });
1047
+ await writeReleaseReadinessLeaderAttention("release-ready-team", "sess-stop-release-ready", cwd, { workRemaining: false });
1048
+ await writeReleaseReadinessStateMarker("sess-stop-release-ready", "release-ready-team", cwd);
1049
+ const result = await dispatchCodexNativeHook({
1050
+ hook_event_name: "Stop",
1051
+ cwd,
1052
+ session_id: "sess-stop-release-ready",
1053
+ thread_id: "thread-stop-release-ready",
1054
+ turn_id: "turn-stop-release-ready-1",
1055
+ mode: "release-readiness",
1056
+ last_assistant_message: "Launch-ready: yes",
1057
+ }, { cwd });
1058
+ assert.equal(result.omxEventName, "stop");
1059
+ assert.deepEqual(result.outputJson, {
1060
+ decision: "block",
1061
+ reason: 'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.',
1062
+ stopReason: "release_readiness_auto_finalize",
1063
+ systemMessage: "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.",
1064
+ });
1065
+ }
1066
+ finally {
1067
+ await rm(cwd, { recursive: true, force: true });
1068
+ }
1069
+ });
1070
+ it("does not auto-finalize non-release team stops that happen to contain a stable recommendation summary", async () => {
1071
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-non-release-readiness-control-"));
1072
+ try {
1073
+ await initTeamState("general-review-team", "general team stop control", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-general-review" });
1074
+ await writeReleaseReadinessLeaderAttention("general-review-team", "sess-stop-general-review", cwd, { workRemaining: false });
1075
+ const result = await dispatchCodexNativeHook({
1076
+ hook_event_name: "Stop",
1077
+ cwd,
1078
+ session_id: "sess-stop-general-review",
1079
+ thread_id: "thread-stop-general-review",
1080
+ turn_id: "turn-stop-general-review-1",
1081
+ last_assistant_message: "Launch-ready: yes",
1082
+ }, { cwd });
1083
+ assert.equal(result.omxEventName, "stop");
1084
+ assert.deepEqual(result.outputJson, {
1085
+ decision: "block",
1086
+ reason: `OMX team pipeline is still active (general-review-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
1087
+ stopReason: "team_team-exec",
1088
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
1089
+ });
1090
+ }
1091
+ finally {
1092
+ await rm(cwd, { recursive: true, force: true });
1093
+ }
1094
+ });
795
1095
  it("re-fires canonical-team Stop output for a later fresh Stop reply when coarse mode state is missing", async () => {
796
1096
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-refire-"));
797
1097
  try {
@@ -936,6 +1236,42 @@ esac
936
1236
  await rm(cwd, { recursive: true, force: true });
937
1237
  }
938
1238
  });
1239
+ it("blocks Stop from session-scoped team mode when session.json points to another session", async () => {
1240
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-session-mismatch-"));
1241
+ try {
1242
+ const stateDir = join(cwd, ".omx", "state");
1243
+ await mkdir(join(stateDir, "sessions", "sess-live-team"), { recursive: true });
1244
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-team" });
1245
+ await writeJson(join(stateDir, "sessions", "sess-live-team", "team-state.json"), {
1246
+ active: true,
1247
+ mode: "team",
1248
+ current_phase: "team-exec",
1249
+ team_name: "session-live-team",
1250
+ });
1251
+ await writeJson(join(stateDir, "team", "session-live-team", "phase.json"), {
1252
+ current_phase: "team-exec",
1253
+ max_fix_attempts: 3,
1254
+ current_fix_attempt: 0,
1255
+ transitions: [],
1256
+ updated_at: new Date().toISOString(),
1257
+ });
1258
+ const result = await dispatchCodexNativeHook({
1259
+ hook_event_name: "Stop",
1260
+ cwd,
1261
+ session_id: "sess-live-team",
1262
+ }, { cwd });
1263
+ assert.equal(result.omxEventName, "stop");
1264
+ assert.deepEqual(result.outputJson, {
1265
+ decision: "block",
1266
+ reason: `OMX team pipeline is still active (session-live-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
1267
+ stopReason: "team_team-exec",
1268
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
1269
+ });
1270
+ }
1271
+ finally {
1272
+ await rm(cwd, { recursive: true, force: true });
1273
+ }
1274
+ });
939
1275
  it("returns Stop continuation output for active ralplan skill with matching active mode state and without active subagents", async () => {
940
1276
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-"));
941
1277
  try {
@@ -1074,7 +1410,7 @@ esac
1074
1410
  await rm(cwd, { recursive: true, force: true });
1075
1411
  }
1076
1412
  });
1077
- it("returns Stop continuation output for active deep-interview skill with matching active mode state and without active subagents", async () => {
1413
+ it("does not block Stop solely because deep-interview is active", async () => {
1078
1414
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-"));
1079
1415
  try {
1080
1416
  const stateDir = join(cwd, ".omx", "state");
@@ -1094,12 +1430,7 @@ esac
1094
1430
  cwd,
1095
1431
  session_id: "sess-stop-deep-interview",
1096
1432
  }, { cwd });
1097
- assert.deepEqual(result.outputJson, {
1098
- decision: "block",
1099
- reason: "OMX skill deep-interview is still active (phase: planning); continue until the current deep-interview workflow reaches a terminal state.",
1100
- stopReason: "skill_deep-interview_planning",
1101
- systemMessage: "OMX skill deep-interview is still active (phase: planning).",
1102
- });
1433
+ assert.equal(result.outputJson, null);
1103
1434
  }
1104
1435
  finally {
1105
1436
  await rm(cwd, { recursive: true, force: true });
@@ -1129,7 +1460,7 @@ esac
1129
1460
  await rm(cwd, { recursive: true, force: true });
1130
1461
  }
1131
1462
  });
1132
- it("returns Stop continuation output while Ralph is active", async () => {
1463
+ it("returns Stop continuation output while Ralph is active without an explicit session pin", async () => {
1133
1464
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-"));
1134
1465
  try {
1135
1466
  const stateDir = join(cwd, ".omx", "state");
@@ -1141,7 +1472,34 @@ esac
1141
1472
  const result = await dispatchCodexNativeHook({
1142
1473
  hook_event_name: "Stop",
1143
1474
  cwd,
1144
- session_id: "sess-stop",
1475
+ }, { cwd });
1476
+ assert.equal(result.omxEventName, "stop");
1477
+ assert.deepEqual(result.outputJson, {
1478
+ decision: "block",
1479
+ reason: "OMX Ralph is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
1480
+ stopReason: "ralph_executing",
1481
+ systemMessage: "OMX Ralph is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
1482
+ });
1483
+ }
1484
+ finally {
1485
+ await rm(cwd, { recursive: true, force: true });
1486
+ }
1487
+ });
1488
+ it("blocks Stop from session-scoped Ralph state when session.json points to another session", async () => {
1489
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-session-mismatch-"));
1490
+ try {
1491
+ const stateDir = join(cwd, ".omx", "state");
1492
+ await mkdir(join(stateDir, "sessions", "sess-live-ralph"), { recursive: true });
1493
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-ralph" });
1494
+ await writeJson(join(stateDir, "sessions", "sess-live-ralph", "ralph-state.json"), {
1495
+ active: true,
1496
+ current_phase: "executing",
1497
+ session_id: "sess-live-ralph",
1498
+ });
1499
+ const result = await dispatchCodexNativeHook({
1500
+ hook_event_name: "Stop",
1501
+ cwd,
1502
+ session_id: "sess-live-ralph",
1145
1503
  }, { cwd });
1146
1504
  assert.equal(result.omxEventName, "stop");
1147
1505
  assert.deepEqual(result.outputJson, {
@@ -1179,6 +1537,103 @@ esac
1179
1537
  await rm(cwd, { recursive: true, force: true });
1180
1538
  }
1181
1539
  });
1540
+ it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => {
1541
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-"));
1542
+ try {
1543
+ const stateDir = join(cwd, ".omx", "state");
1544
+ await mkdir(join(stateDir, "sessions", "sess-other"), { recursive: true });
1545
+ await writeJson(join(stateDir, "sessions", "sess-other", "ralph-state.json"), {
1546
+ active: true,
1547
+ current_phase: "starting",
1548
+ session_id: "sess-other",
1549
+ });
1550
+ const result = await dispatchCodexNativeHook({
1551
+ hook_event_name: "Stop",
1552
+ cwd,
1553
+ session_id: "sess-current",
1554
+ }, { cwd });
1555
+ assert.equal(result.omxEventName, "stop");
1556
+ assert.equal(result.outputJson, null);
1557
+ }
1558
+ finally {
1559
+ await rm(cwd, { recursive: true, force: true });
1560
+ }
1561
+ });
1562
+ it("does not block Stop from root Ralph fallback when the current session has no scoped Ralph state", async () => {
1563
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-ralph-"));
1564
+ try {
1565
+ const stateDir = join(cwd, ".omx", "state");
1566
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
1567
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd });
1568
+ await writeJson(join(stateDir, "ralph-state.json"), {
1569
+ active: true,
1570
+ current_phase: "executing",
1571
+ });
1572
+ const result = await dispatchCodexNativeHook({
1573
+ hook_event_name: "Stop",
1574
+ cwd,
1575
+ session_id: "sess-current",
1576
+ }, { cwd });
1577
+ assert.equal(result.omxEventName, "stop");
1578
+ assert.equal(result.outputJson, null);
1579
+ }
1580
+ finally {
1581
+ await rm(cwd, { recursive: true, force: true });
1582
+ }
1583
+ });
1584
+ it("does not block Stop when the current session Ralph state is cancelled even if stale root fallback remains", async () => {
1585
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-cancelled-session-ralph-"));
1586
+ try {
1587
+ const stateDir = join(cwd, ".omx", "state");
1588
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
1589
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd });
1590
+ await writeJson(join(stateDir, "sessions", "sess-current", "ralph-state.json"), {
1591
+ active: false,
1592
+ current_phase: "cancelled",
1593
+ completed_at: "2026-04-10T23:30:38.000Z",
1594
+ session_id: "sess-current",
1595
+ });
1596
+ await writeJson(join(stateDir, "ralph-state.json"), {
1597
+ active: true,
1598
+ current_phase: "starting",
1599
+ });
1600
+ const result = await dispatchCodexNativeHook({
1601
+ hook_event_name: "Stop",
1602
+ cwd,
1603
+ session_id: "sess-current",
1604
+ }, { cwd });
1605
+ assert.equal(result.omxEventName, "stop");
1606
+ assert.equal(result.outputJson, null);
1607
+ }
1608
+ finally {
1609
+ await rm(cwd, { recursive: true, force: true });
1610
+ }
1611
+ });
1612
+ it("does not block Stop from root Ralph fallback when an explicit session_id is present and session.json points to another worktree", async () => {
1613
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-cwd-mismatch-"));
1614
+ try {
1615
+ const stateDir = join(cwd, ".omx", "state");
1616
+ await mkdir(stateDir, { recursive: true });
1617
+ await writeJson(join(stateDir, "session.json"), {
1618
+ session_id: "sess-elsewhere",
1619
+ cwd: join(cwd, "..", "different-worktree"),
1620
+ });
1621
+ await writeJson(join(stateDir, "ralph-state.json"), {
1622
+ active: true,
1623
+ current_phase: "executing",
1624
+ });
1625
+ const result = await dispatchCodexNativeHook({
1626
+ hook_event_name: "Stop",
1627
+ cwd,
1628
+ session_id: "sess-current",
1629
+ }, { cwd });
1630
+ assert.equal(result.omxEventName, "stop");
1631
+ assert.equal(result.outputJson, null);
1632
+ }
1633
+ finally {
1634
+ await rm(cwd, { recursive: true, force: true });
1635
+ }
1636
+ });
1182
1637
  it("does not re-block Ralph when Stop already continued once", async () => {
1183
1638
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-once-"));
1184
1639
  try {
@@ -1210,12 +1665,12 @@ esac
1210
1665
  hook_event_name: "Stop",
1211
1666
  cwd,
1212
1667
  session_id: "sess-stop-auto",
1213
- last_assistant_message: "Would you like me to keep going and finish the cleanup?",
1668
+ last_assistant_message: "Keep going and finish the cleanup.",
1214
1669
  }, { cwd });
1215
1670
  assert.equal(result.omxEventName, "stop");
1216
1671
  assert.deepEqual(result.outputJson, {
1217
1672
  decision: "block",
1218
- reason: "yes, proceed",
1673
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1219
1674
  stopReason: "auto_nudge",
1220
1675
  systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1221
1676
  });
@@ -1235,7 +1690,7 @@ esac
1235
1690
  session_id: "sess-stop-auto-once",
1236
1691
  thread_id: "thread-stop-auto",
1237
1692
  turn_id: "turn-stop-auto-1",
1238
- last_assistant_message: "Would you like me to continue?",
1693
+ last_assistant_message: "Keep going and finish the cleanup.",
1239
1694
  }, { cwd });
1240
1695
  const result = await dispatchCodexNativeHook({
1241
1696
  hook_event_name: "Stop",
@@ -1244,7 +1699,7 @@ esac
1244
1699
  thread_id: "thread-stop-auto",
1245
1700
  turn_id: "turn-stop-auto-1",
1246
1701
  stop_hook_active: true,
1247
- last_assistant_message: "Would you like me to continue?",
1702
+ last_assistant_message: "Keep going and finish the cleanup.",
1248
1703
  }, { cwd });
1249
1704
  assert.equal(result.omxEventName, "stop");
1250
1705
  assert.equal(result.outputJson, null);
@@ -1264,7 +1719,7 @@ esac
1264
1719
  session_id: "sess-stop-auto-refire",
1265
1720
  thread_id: "thread-stop-auto-refire",
1266
1721
  turn_id: "turn-stop-auto-refire-1",
1267
- last_assistant_message: "Would you like me to continue?",
1722
+ last_assistant_message: "Keep going and finish the cleanup.",
1268
1723
  }, { cwd });
1269
1724
  const result = await dispatchCodexNativeHook({
1270
1725
  hook_event_name: "Stop",
@@ -1273,12 +1728,12 @@ esac
1273
1728
  thread_id: "thread-stop-auto-refire",
1274
1729
  turn_id: "turn-stop-auto-refire-2",
1275
1730
  stop_hook_active: true,
1276
- last_assistant_message: "If you want, I can keep going and finish the cleanup.",
1731
+ last_assistant_message: "Continue with the cleanup from here.",
1277
1732
  }, { cwd });
1278
1733
  assert.equal(result.omxEventName, "stop");
1279
1734
  assert.deepEqual(result.outputJson, {
1280
1735
  decision: "block",
1281
- reason: "yes, proceed",
1736
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1282
1737
  stopReason: "auto_nudge",
1283
1738
  systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1284
1739
  });
@@ -1287,17 +1742,36 @@ esac
1287
1742
  await rm(cwd, { recursive: true, force: true });
1288
1743
  }
1289
1744
  });
1290
- it("suppresses native auto-nudge when only the deep-interview input lock is active", async () => {
1291
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-lock-"));
1745
+ it("does not auto-continue native Stop on permission-seeking prompts", async () => {
1746
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-permission-"));
1747
+ try {
1748
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1749
+ const result = await dispatchCodexNativeHook({
1750
+ hook_event_name: "Stop",
1751
+ cwd,
1752
+ session_id: "sess-stop-auto-permission",
1753
+ last_assistant_message: "Would you like me to continue with the cleanup?",
1754
+ }, { cwd });
1755
+ assert.equal(result.omxEventName, "stop");
1756
+ assert.equal(result.outputJson, null);
1757
+ }
1758
+ finally {
1759
+ await rm(cwd, { recursive: true, force: true });
1760
+ }
1761
+ });
1762
+ it("does not auto-continue native Stop while deep-interview is waiting on an intent-first question", async () => {
1763
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-question-"));
1292
1764
  try {
1293
1765
  const stateDir = join(cwd, ".omx", "state");
1294
- await mkdir(join(stateDir, "sessions", "sess-stop-auto-lock"), { recursive: true });
1295
- await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-lock" });
1296
- await writeJson(join(stateDir, "sessions", "sess-stop-auto-lock", "skill-active-state.json"), {
1766
+ await mkdir(join(stateDir, "sessions", "sess-stop-auto-question"), { recursive: true });
1767
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-question" });
1768
+ await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "skill-active-state.json"), {
1297
1769
  version: 1,
1298
1770
  active: true,
1299
1771
  skill: "deep-interview",
1300
1772
  phase: "planning",
1773
+ session_id: "sess-stop-auto-question",
1774
+ thread_id: "thread-stop-auto-question",
1301
1775
  input_lock: {
1302
1776
  active: true,
1303
1777
  scope: "deep-interview-auto-approval",
@@ -1305,25 +1779,26 @@ esac
1305
1779
  message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
1306
1780
  },
1307
1781
  });
1308
- await writeJson(join(stateDir, "sessions", "sess-stop-auto-lock", "deep-interview-state.json"), {
1782
+ await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "deep-interview-state.json"), {
1309
1783
  active: true,
1310
- current_phase: "planning",
1784
+ mode: "deep-interview",
1785
+ current_phase: "intent-first",
1311
1786
  });
1312
1787
  const result = await dispatchCodexNativeHook({
1313
1788
  hook_event_name: "Stop",
1314
1789
  cwd,
1315
- session_id: "sess-stop-auto-lock",
1316
- thread_id: "thread-stop-auto-lock",
1317
- turn_id: "turn-stop-auto-lock-1",
1318
- last_assistant_message: "Would you like me to continue with the next step?",
1790
+ session_id: "sess-stop-auto-question",
1791
+ thread_id: "thread-stop-auto-question",
1792
+ turn_id: "turn-stop-auto-question-1",
1793
+ last_assistant_message: [
1794
+ "Round 2 | Target: Decision boundary | Ambiguity: 24%",
1795
+ "",
1796
+ "If an existing project spider still declares session_mode = \"owned\", should ZenX fail loudly so the stale attribute is removed, or should it ignore the attribute and initialize the session pool anyway?",
1797
+ "Keep going once I have your answer.",
1798
+ ].join("\n"),
1319
1799
  }, { cwd });
1320
1800
  assert.equal(result.omxEventName, "stop");
1321
- assert.deepEqual(result.outputJson, {
1322
- decision: "block",
1323
- reason: "OMX skill deep-interview is still active (phase: planning); continue until the current deep-interview workflow reaches a terminal state.",
1324
- stopReason: "skill_deep-interview_planning",
1325
- systemMessage: "OMX skill deep-interview is still active (phase: planning).",
1326
- });
1801
+ assert.equal(result.outputJson, null);
1327
1802
  }
1328
1803
  finally {
1329
1804
  await rm(cwd, { recursive: true, force: true });
@@ -1395,12 +1870,12 @@ esac
1395
1870
  session_id: "sess-stop-auto-stale-root-mode",
1396
1871
  thread_id: "thread-stop-auto-stale-root-mode",
1397
1872
  turn_id: "turn-stop-auto-stale-root-mode-1",
1398
- last_assistant_message: "Would you like me to continue with the next step?",
1873
+ last_assistant_message: "Keep going and finish the cleanup.",
1399
1874
  }, { cwd });
1400
1875
  assert.equal(result.omxEventName, "stop");
1401
1876
  assert.deepEqual(result.outputJson, {
1402
1877
  decision: "block",
1403
- reason: "yes, proceed",
1878
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1404
1879
  stopReason: "auto_nudge",
1405
1880
  systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1406
1881
  });
@@ -1425,12 +1900,12 @@ esac
1425
1900
  session_id: "sess-stop-auto-stale-root-skill",
1426
1901
  thread_id: "thread-stop-auto-stale-root-skill",
1427
1902
  turn_id: "turn-stop-auto-stale-root-skill-1",
1428
- last_assistant_message: "Would you like me to continue with the next step?",
1903
+ last_assistant_message: "Keep going and finish the cleanup.",
1429
1904
  }, { cwd });
1430
1905
  assert.equal(result.omxEventName, "stop");
1431
1906
  assert.deepEqual(result.outputJson, {
1432
1907
  decision: "block",
1433
- reason: "yes, proceed",
1908
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1434
1909
  stopReason: "auto_nudge",
1435
1910
  systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1436
1911
  });
@@ -1461,12 +1936,48 @@ esac
1461
1936
  session_id: "sess-stop-auto-stale-root-lock",
1462
1937
  thread_id: "thread-stop-auto-stale-root-lock",
1463
1938
  turn_id: "turn-stop-auto-stale-root-lock-1",
1464
- last_assistant_message: "Would you like me to continue with the next step?",
1939
+ last_assistant_message: "Keep going and finish the cleanup.",
1465
1940
  }, { cwd });
1466
1941
  assert.equal(result.omxEventName, "stop");
1467
1942
  assert.deepEqual(result.outputJson, {
1468
1943
  decision: "block",
1469
- reason: "yes, proceed",
1944
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1945
+ stopReason: "auto_nudge",
1946
+ systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1947
+ });
1948
+ }
1949
+ finally {
1950
+ await rm(cwd, { recursive: true, force: true });
1951
+ }
1952
+ });
1953
+ it("does not suppress native auto-nudge from active root deep-interview state when the current scoped mode state is explicitly inactive", async () => {
1954
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-inactive-scoped-mode-"));
1955
+ try {
1956
+ const stateDir = join(cwd, ".omx", "state");
1957
+ await mkdir(join(stateDir, "sessions", "sess-stop-auto-inactive-mode"), { recursive: true });
1958
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-inactive-mode" });
1959
+ await writeJson(join(stateDir, "sessions", "sess-stop-auto-inactive-mode", "deep-interview-state.json"), {
1960
+ active: false,
1961
+ mode: "deep-interview",
1962
+ current_phase: "completed",
1963
+ });
1964
+ await writeJson(join(stateDir, "deep-interview-state.json"), {
1965
+ active: true,
1966
+ mode: "deep-interview",
1967
+ current_phase: "intent-first",
1968
+ });
1969
+ const result = await dispatchCodexNativeHook({
1970
+ hook_event_name: "Stop",
1971
+ cwd,
1972
+ session_id: "sess-stop-auto-inactive-mode",
1973
+ thread_id: "thread-stop-auto-inactive-mode",
1974
+ turn_id: "turn-stop-auto-inactive-mode-1",
1975
+ last_assistant_message: "Keep going and finish the cleanup.",
1976
+ }, { cwd });
1977
+ assert.equal(result.omxEventName, "stop");
1978
+ assert.deepEqual(result.outputJson, {
1979
+ decision: "block",
1980
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
1470
1981
  stopReason: "auto_nudge",
1471
1982
  systemMessage: "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
1472
1983
  });
@@ -1519,5 +2030,146 @@ esac
1519
2030
  await rm(cwd, { recursive: true, force: true });
1520
2031
  }
1521
2032
  });
2033
+ it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
2034
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
2035
+ try {
2036
+ const stateDir = join(cwd, ".omx", "state");
2037
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2038
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2039
+ await writeJson(join(stateDir, "team-state.json"), {
2040
+ active: true,
2041
+ current_phase: "starting",
2042
+ team_name: "stale-root-team",
2043
+ session_id: "sess-other",
2044
+ });
2045
+ await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), {
2046
+ current_phase: "team-exec",
2047
+ max_fix_attempts: 3,
2048
+ current_fix_attempt: 0,
2049
+ transitions: [],
2050
+ updated_at: new Date().toISOString(),
2051
+ });
2052
+ const result = await dispatchCodexNativeHook({
2053
+ hook_event_name: "Stop",
2054
+ cwd,
2055
+ session_id: "sess-current",
2056
+ }, { cwd });
2057
+ assert.equal(result.omxEventName, "stop");
2058
+ assert.equal(result.outputJson, null);
2059
+ }
2060
+ finally {
2061
+ await rm(cwd, { recursive: true, force: true });
2062
+ }
2063
+ });
2064
+ it("does not block Stop from orphaned team mode state after cleanup removed canonical team artifacts", async () => {
2065
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-orphaned-team-state-"));
2066
+ try {
2067
+ const stateDir = join(cwd, ".omx", "state");
2068
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2069
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2070
+ await writeJson(join(stateDir, "team-state.json"), {
2071
+ active: true,
2072
+ current_phase: "starting",
2073
+ team_name: "cleaned-team",
2074
+ session_id: "sess-current",
2075
+ });
2076
+ const result = await dispatchCodexNativeHook({
2077
+ hook_event_name: "Stop",
2078
+ cwd,
2079
+ session_id: "sess-current",
2080
+ }, { cwd });
2081
+ assert.equal(result.omxEventName, "stop");
2082
+ assert.equal(result.outputJson, null);
2083
+ }
2084
+ finally {
2085
+ await rm(cwd, { recursive: true, force: true });
2086
+ }
2087
+ });
2088
+ it("prefers the current session team state over a stale root team fallback during Stop", async () => {
2089
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-current-session-team-preferred-"));
2090
+ try {
2091
+ const stateDir = join(cwd, ".omx", "state");
2092
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2093
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2094
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
2095
+ active: true,
2096
+ current_phase: "starting",
2097
+ team_name: "current-team",
2098
+ session_id: "sess-current",
2099
+ });
2100
+ await writeJson(join(stateDir, "team", "current-team", "phase.json"), {
2101
+ current_phase: "team-verify",
2102
+ max_fix_attempts: 3,
2103
+ current_fix_attempt: 1,
2104
+ transitions: [],
2105
+ updated_at: new Date().toISOString(),
2106
+ });
2107
+ await writeJson(join(stateDir, "team-state.json"), {
2108
+ active: true,
2109
+ current_phase: "starting",
2110
+ team_name: "stale-root-team",
2111
+ session_id: "sess-other",
2112
+ });
2113
+ await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), {
2114
+ current_phase: "team-exec",
2115
+ max_fix_attempts: 3,
2116
+ current_fix_attempt: 0,
2117
+ transitions: [],
2118
+ updated_at: new Date().toISOString(),
2119
+ });
2120
+ const result = await dispatchCodexNativeHook({
2121
+ hook_event_name: "Stop",
2122
+ cwd,
2123
+ session_id: "sess-current",
2124
+ }, { cwd });
2125
+ assert.equal(result.omxEventName, "stop");
2126
+ assert.deepEqual(result.outputJson, {
2127
+ decision: "block",
2128
+ reason: `OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
2129
+ stopReason: "team_team-verify",
2130
+ systemMessage: "OMX team pipeline is still active at phase team-verify.",
2131
+ });
2132
+ }
2133
+ finally {
2134
+ await rm(cwd, { recursive: true, force: true });
2135
+ }
2136
+ });
2137
+ it("does not fall back to active root team state when the current scoped team state is inactive", async () => {
2138
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-inactive-scoped-team-"));
2139
+ try {
2140
+ const stateDir = join(cwd, ".omx", "state");
2141
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2142
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2143
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
2144
+ active: false,
2145
+ current_phase: "complete",
2146
+ team_name: "scoped-finished-team",
2147
+ session_id: "sess-current",
2148
+ });
2149
+ await writeJson(join(stateDir, "team-state.json"), {
2150
+ active: true,
2151
+ current_phase: "starting",
2152
+ team_name: "root-fallback-team",
2153
+ session_id: "sess-current",
2154
+ });
2155
+ await writeJson(join(stateDir, "team", "root-fallback-team", "phase.json"), {
2156
+ current_phase: "team-exec",
2157
+ max_fix_attempts: 3,
2158
+ current_fix_attempt: 0,
2159
+ transitions: [],
2160
+ updated_at: new Date().toISOString(),
2161
+ });
2162
+ const result = await dispatchCodexNativeHook({
2163
+ hook_event_name: "Stop",
2164
+ cwd,
2165
+ session_id: "sess-current",
2166
+ }, { cwd });
2167
+ assert.equal(result.omxEventName, "stop");
2168
+ assert.equal(result.outputJson, null);
2169
+ }
2170
+ finally {
2171
+ await rm(cwd, { recursive: true, force: true });
2172
+ }
2173
+ });
1522
2174
  });
1523
2175
  //# sourceMappingURL=codex-native-hook.test.js.map