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,10 +6,11 @@ import { join } from 'path';
6
6
  import { tmpdir } from 'os';
7
7
  import { existsSync } from 'fs';
8
8
  import { HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
9
- import { initTeamState, createTask, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, transitionDispatchRequest, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, writeWorkerStatus, } from '../state.js';
9
+ import { initTeamState, createTask, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, transitionDispatchRequest, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, readWorkerStatus, writeWorkerStatus, } from '../state.js';
10
10
  import { monitorTeam, shutdownTeam, resumeTeam, startTeam, assignTask, sendWorkerMessage, applyCreatedInteractiveSessionToConfig, resolveWorkerLaunchArgsFromEnv, shouldPrekillInteractiveShutdownProcessTrees, waitForWorkerStartupEvidence, waitForClaudeStartupEvidence, } from '../runtime.js';
11
11
  import { resolveTeamLowComplexityDefaultModel } from '../model-contract.js';
12
12
  import { readTeamEvents } from '../state/events.js';
13
+ import { sanitizeTeamName } from '../tmux-session.js';
13
14
  async function initRepo() {
14
15
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-worktree-repo-'));
15
16
  execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
@@ -107,6 +108,29 @@ function withoutTeamWorkerEnv(fn) {
107
108
  }
108
109
  }
109
110
  }
111
+ function withMockPromptModeCodexAllowed(fn) {
112
+ const previous = process.env.OMX_TEST_ALLOW_NONTTY_CODEX_PROMPT;
113
+ process.env.OMX_TEST_ALLOW_NONTTY_CODEX_PROMPT = '1';
114
+ let restoreImmediately = true;
115
+ const restore = () => {
116
+ if (typeof previous === 'string')
117
+ process.env.OMX_TEST_ALLOW_NONTTY_CODEX_PROMPT = previous;
118
+ else
119
+ delete process.env.OMX_TEST_ALLOW_NONTTY_CODEX_PROMPT;
120
+ };
121
+ try {
122
+ const result = fn();
123
+ if (result instanceof Promise) {
124
+ restoreImmediately = false;
125
+ return result.finally(restore);
126
+ }
127
+ return result;
128
+ }
129
+ finally {
130
+ if (restoreImmediately)
131
+ restore();
132
+ }
133
+ }
110
134
  async function waitForFileText(filePath, matcher, timeoutMs = 3_000) {
111
135
  const deadline = Date.now() + timeoutMs;
112
136
  while (Date.now() < deadline) {
@@ -150,6 +174,7 @@ async function withPromptModeCodexEnv(binDir, extraEnv, run) {
150
174
  TMUX: undefined,
151
175
  OMX_TEAM_WORKER_LAUNCH_MODE: 'prompt',
152
176
  OMX_TEAM_WORKER_CLI: 'codex',
177
+ OMX_TEST_ALLOW_NONTTY_CODEX_PROMPT: '1',
153
178
  ...extraEnv,
154
179
  };
155
180
  for (const [key, value] of Object.entries(nextEnv)) {
@@ -488,6 +513,159 @@ describe('runtime', () => {
488
513
  await rm(cwd, { recursive: true, force: true });
489
514
  }
490
515
  });
516
+ it('waitForWorkerStartupEvidence treats blocked worker status as settled progress even without a claimed task id', async () => {
517
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-codex-blocked-startup-'));
518
+ try {
519
+ await initTeamState('codex-blocked-startup', 'blocked startup evidence test', 'executor', 1, cwd);
520
+ await writeAtomic(join(cwd, '.omx', 'state', 'team', 'codex-blocked-startup', 'workers', 'worker-1', 'status.json'), JSON.stringify({
521
+ state: 'blocked',
522
+ reason: 'waiting on shared file',
523
+ updated_at: new Date().toISOString(),
524
+ }, null, 2));
525
+ const progress = await waitForWorkerStartupEvidence({
526
+ teamName: 'codex-blocked-startup',
527
+ workerName: 'worker-1',
528
+ workerCli: 'codex',
529
+ cwd,
530
+ timeoutMs: 25,
531
+ pollMs: 5,
532
+ });
533
+ assert.equal(progress, 'worker_progress');
534
+ }
535
+ finally {
536
+ await rm(cwd, { recursive: true, force: true });
537
+ }
538
+ });
539
+ it('startTeam rejects interactive startup when tmux fallback never produces worker startup evidence', async () => {
540
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-no-evidence-'));
541
+ const prevTmux = process.env.TMUX;
542
+ const prevTmuxPane = process.env.TMUX_PANE;
543
+ const prevLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
544
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
545
+ const prevSkipReadyWait = process.env.OMX_TEAM_SKIP_READY_WAIT;
546
+ let receiptFailer = null;
547
+ try {
548
+ await withMockTmuxFixture({
549
+ dirPrefix: 'omx-runtime-startup-no-evidence-bin-',
550
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
551
+ set -eu
552
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
553
+ case "$1" in
554
+ -V)
555
+ echo "tmux 3.4"
556
+ exit 0
557
+ ;;
558
+ display-message)
559
+ case "$*" in
560
+ *"#{window_width}"*)
561
+ echo "120"
562
+ ;;
563
+ *)
564
+ echo "leader:0 %1"
565
+ ;;
566
+ esac
567
+ exit 0
568
+ ;;
569
+ list-panes)
570
+ case "$*" in
571
+ *"-F #{pane_id}"*"#{pane_current_command}"*)
572
+ printf "%%1\\tzsh\\tzsh\\n"
573
+ exit 0
574
+ ;;
575
+ *"#{pane_pid}"*)
576
+ echo "4321"
577
+ exit 0
578
+ ;;
579
+ *"#{pane_dead} #{pane_pid}"*)
580
+ echo "0 4321"
581
+ exit 0
582
+ ;;
583
+ *)
584
+ exit 0
585
+ ;;
586
+ esac
587
+ ;;
588
+ split-window)
589
+ case "$*" in
590
+ *" -h "*)
591
+ echo "%2"
592
+ ;;
593
+ *)
594
+ echo "%3"
595
+ ;;
596
+ esac
597
+ exit 0
598
+ ;;
599
+ capture-pane)
600
+ printf 'OpenAI Codex\\n> '
601
+ exit 0
602
+ ;;
603
+ send-keys|resize-pane|select-layout|set-window-option|select-pane|set-hook|run-shell|kill-pane|kill-session)
604
+ exit 0
605
+ ;;
606
+ *)
607
+ exit 0
608
+ ;;
609
+ esac
610
+ `,
611
+ binaries: [
612
+ {
613
+ name: 'codex',
614
+ content: '#!/bin/sh\nsleep 30\n',
615
+ },
616
+ ],
617
+ }, async ({ tmuxLogPath }) => {
618
+ process.env.TMUX = 'leader-session';
619
+ process.env.TMUX_PANE = '%1';
620
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
621
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
622
+ process.env.OMX_TEAM_SKIP_READY_WAIT = '1';
623
+ receiptFailer = setInterval(() => {
624
+ void (async () => {
625
+ const requests = await listDispatchRequests('team-startup-no-evidence', cwd, { kind: 'inbox' }).catch(() => []);
626
+ for (const request of requests) {
627
+ if (request.status !== 'pending')
628
+ continue;
629
+ await transitionDispatchRequest('team-startup-no-evidence', request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
630
+ }
631
+ })();
632
+ }, 20);
633
+ await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('team-startup-no-evidence', 'interactive startup must observe worker evidence', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd)), /worker_notify_failed/);
634
+ if (receiptFailer) {
635
+ clearInterval(receiptFailer);
636
+ receiptFailer = null;
637
+ }
638
+ assert.equal(await readTeamConfig('team-startup-no-evidence', cwd), null);
639
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
640
+ assert.match(tmuxLog, /send-keys -t %2 -l --/);
641
+ });
642
+ }
643
+ finally {
644
+ if (receiptFailer)
645
+ clearInterval(receiptFailer);
646
+ if (typeof prevTmux === 'string')
647
+ process.env.TMUX = prevTmux;
648
+ else
649
+ delete process.env.TMUX;
650
+ if (typeof prevTmuxPane === 'string')
651
+ process.env.TMUX_PANE = prevTmuxPane;
652
+ else
653
+ delete process.env.TMUX_PANE;
654
+ if (typeof prevLaunchMode === 'string')
655
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = prevLaunchMode;
656
+ else
657
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
658
+ if (typeof prevWorkerCli === 'string')
659
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
660
+ else
661
+ delete process.env.OMX_TEAM_WORKER_CLI;
662
+ if (typeof prevSkipReadyWait === 'string')
663
+ process.env.OMX_TEAM_SKIP_READY_WAIT = prevSkipReadyWait;
664
+ else
665
+ delete process.env.OMX_TEAM_SKIP_READY_WAIT;
666
+ await rm(cwd, { recursive: true, force: true });
667
+ }
668
+ });
491
669
  it('resolveWorkerLaunchArgsFromEnv logs source=none/default-none when thinking is not explicit', () => {
492
670
  const logs = [];
493
671
  const originalLog = console.log;
@@ -860,6 +1038,14 @@ case "\${1:-}" in
860
1038
  split-window)
861
1039
  case "$*" in
862
1040
  *" -h "*)
1041
+ mkdir -p "${cwd}/.omx/state/team/team-pane-pid/workers/worker-1"
1042
+ cat > "${cwd}/.omx/state/team/team-pane-pid/workers/worker-1/status.json" <<'EOF'
1043
+ {
1044
+ "state": "working",
1045
+ "current_task_id": "1",
1046
+ "updated_at": "2026-04-10T00:00:00.000Z"
1047
+ }
1048
+ EOF
863
1049
  echo "%2"
864
1050
  ;;
865
1051
  *)
@@ -922,15 +1108,409 @@ esac
922
1108
  });
923
1109
  it('startTeam saves interactive pane ids before readiness waits in source order', async () => {
924
1110
  const source = await readFile(join(process.cwd(), 'src', 'team', 'runtime.ts'), 'utf-8');
925
- const applyIndex = source.indexOf('applyCreatedInteractiveSessionToConfig(config, createdSession, workerPaneIds);');
926
- const saveIndex = source.indexOf('await saveTeamConfig(config, leaderCwd);', applyIndex);
927
- const readyIndex = source.indexOf('const ready = waitForWorkerReady(sessionName, i, workerReadyTimeoutMs, paneId);', saveIndex);
928
- assert.notEqual(applyIndex, -1);
929
- assert.notEqual(saveIndex, -1);
930
- assert.notEqual(readyIndex, -1);
1111
+ const applyMatch = source.match(/applyCreatedInteractiveSessionToConfig\(\s*config,\s*createdSession,\s*workerPaneIds\s*\);/m);
1112
+ const saveMatch = source.match(/await saveTeamConfig\(config, leaderCwd\);/m);
1113
+ const readyMatch = source.match(/const ready = waitForWorkerReady\(/m);
1114
+ const applyIndex = applyMatch?.index ?? -1;
1115
+ const saveIndex = saveMatch?.index ?? -1;
1116
+ const readyIndex = readyMatch?.index ?? -1;
1117
+ assert.notEqual(applyMatch, null);
1118
+ assert.notEqual(saveMatch, null);
1119
+ assert.notEqual(readyMatch, null);
931
1120
  assert.equal(applyIndex < saveIndex, true);
932
1121
  assert.equal(saveIndex < readyIndex, true);
933
1122
  });
1123
+ it('startTeam records recoverable startup issues per worker instead of failing launch early when panes stay alive', async () => {
1124
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-no-startup-evidence-'));
1125
+ const previousTmux = process.env.TMUX;
1126
+ const previousTmuxPane = process.env.TMUX_PANE;
1127
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1128
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1129
+ const previousSkipReadyWait = process.env.OMX_TEAM_SKIP_READY_WAIT;
1130
+ const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1131
+ let runtime = null;
1132
+ const teamName = 'team-no-startup-evidence';
1133
+ try {
1134
+ await withMockTmuxFixture({
1135
+ dirPrefix: 'omx-runtime-no-startup-evidence-bin-',
1136
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
1137
+ set -eu
1138
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
1139
+ case "$1" in
1140
+ -V)
1141
+ echo "tmux 3.4"
1142
+ exit 0
1143
+ ;;
1144
+ display-message)
1145
+ case "$*" in
1146
+ *"#{window_width}"*)
1147
+ echo "120"
1148
+ ;;
1149
+ *)
1150
+ echo "leader:0 %1"
1151
+ ;;
1152
+ esac
1153
+ exit 0
1154
+ ;;
1155
+ list-panes)
1156
+ case "$*" in
1157
+ *"#{pane_dead}"*)
1158
+ echo "0"
1159
+ ;;
1160
+ *"#{pane_dead} #{pane_pid}"*)
1161
+ echo "0 4242"
1162
+ ;;
1163
+ *"pane_current_command"*)
1164
+ printf "%%1\\tnode\\t'codex'\\n"
1165
+ ;;
1166
+ *"#{pane_pid}"*)
1167
+ echo "4242"
1168
+ ;;
1169
+ *)
1170
+ exit 0
1171
+ ;;
1172
+ esac
1173
+ exit 0
1174
+ ;;
1175
+ capture-pane)
1176
+ exit 0
1177
+ ;;
1178
+ split-window)
1179
+ case "$*" in
1180
+ *" -h "*)
1181
+ echo "%2"
1182
+ ;;
1183
+ *)
1184
+ echo "%3"
1185
+ ;;
1186
+ esac
1187
+ exit 0
1188
+ ;;
1189
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session|resize-pane)
1190
+ exit 0
1191
+ ;;
1192
+ *)
1193
+ exit 0
1194
+ ;;
1195
+ esac
1196
+ `,
1197
+ binaries: [
1198
+ {
1199
+ name: 'codex',
1200
+ content: `#!/usr/bin/env node
1201
+ process.stdin.resume();
1202
+ setTimeout(() => process.exit(0), 30000);
1203
+ process.on('SIGTERM', () => process.exit(0));
1204
+ `,
1205
+ },
1206
+ ],
1207
+ }, async () => {
1208
+ delete process.env.TMUX;
1209
+ process.env.TMUX_PANE = '%1';
1210
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1211
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
1212
+ process.env.OMX_TEAM_SKIP_READY_WAIT = '1';
1213
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
1214
+ runtime = await withoutTeamWorkerEnv(() => startTeam(teamName, 'interactive startup should keep evaluating per-worker startup issues while panes stay alive', 'executor', 2, [
1215
+ { subject: 'worker-1 task', description: 'd', owner: 'worker-1' },
1216
+ { subject: 'worker-2 task', description: 'd', owner: 'worker-2' },
1217
+ ], cwd));
1218
+ const worker1Status = await readWorkerStatus(teamName, 'worker-1', cwd);
1219
+ const worker2Status = await readWorkerStatus(teamName, 'worker-2', cwd);
1220
+ assert.equal(worker1Status.state, 'unknown');
1221
+ assert.equal(worker2Status.state, 'unknown');
1222
+ assert.match(worker1Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
1223
+ assert.match(worker2Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
1224
+ const task1 = await readTask(teamName, '1', cwd);
1225
+ const task2 = await readTask(teamName, '2', cwd);
1226
+ assert.equal(task1?.status, 'pending');
1227
+ assert.equal(task2?.status, 'pending');
1228
+ });
1229
+ }
1230
+ finally {
1231
+ if (runtime) {
1232
+ await shutdownTeam(teamName, cwd, { force: true }).catch(() => { });
1233
+ }
1234
+ if (typeof previousTmux === 'string')
1235
+ process.env.TMUX = previousTmux;
1236
+ else
1237
+ delete process.env.TMUX;
1238
+ if (typeof previousTmuxPane === 'string')
1239
+ process.env.TMUX_PANE = previousTmuxPane;
1240
+ else
1241
+ delete process.env.TMUX_PANE;
1242
+ if (typeof previousLaunchMode === 'string')
1243
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
1244
+ else
1245
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1246
+ if (typeof previousWorkerCli === 'string')
1247
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
1248
+ else
1249
+ delete process.env.OMX_TEAM_WORKER_CLI;
1250
+ if (typeof previousSkipReadyWait === 'string')
1251
+ process.env.OMX_TEAM_SKIP_READY_WAIT = previousSkipReadyWait;
1252
+ else
1253
+ delete process.env.OMX_TEAM_SKIP_READY_WAIT;
1254
+ if (typeof previousStartupEvidenceTimeout === 'string') {
1255
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
1256
+ }
1257
+ else {
1258
+ delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1259
+ }
1260
+ await rm(cwd, { recursive: true, force: true });
1261
+ }
1262
+ });
1263
+ it('startTeam still fails startup when the worker pane is dead/unrecoverable', async () => {
1264
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-dead-startup-pane-'));
1265
+ const previousTmux = process.env.TMUX;
1266
+ const previousTmuxPane = process.env.TMUX_PANE;
1267
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1268
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1269
+ const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1270
+ try {
1271
+ await withMockTmuxFixture({
1272
+ dirPrefix: 'omx-runtime-dead-startup-pane-bin-',
1273
+ tmuxScript: () => `#!/bin/sh
1274
+ set -eu
1275
+ case "$1" in
1276
+ -V)
1277
+ echo "tmux 3.4"
1278
+ exit 0
1279
+ ;;
1280
+ display-message)
1281
+ case "$*" in
1282
+ *"#{window_width}"*) echo "120" ;;
1283
+ *) echo "leader:0 %1" ;;
1284
+ esac
1285
+ exit 0
1286
+ ;;
1287
+ list-panes)
1288
+ case "$*" in
1289
+ *"pane_current_command"*)
1290
+ printf "%%1\\tnode\\t'codex'\\n"
1291
+ ;;
1292
+ *"#{pane_dead} #{pane_pid}"*)
1293
+ echo "1 4242"
1294
+ ;;
1295
+ *"#{pane_pid}"*)
1296
+ echo "4242"
1297
+ ;;
1298
+ *)
1299
+ exit 0
1300
+ ;;
1301
+ esac
1302
+ exit 0
1303
+ ;;
1304
+ capture-pane)
1305
+ exit 0
1306
+ ;;
1307
+ split-window)
1308
+ case "$*" in
1309
+ *" -h "*) echo "%2" ;;
1310
+ *) echo "%3" ;;
1311
+ esac
1312
+ exit 0
1313
+ ;;
1314
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session|resize-pane)
1315
+ exit 0
1316
+ ;;
1317
+ *)
1318
+ exit 0
1319
+ ;;
1320
+ esac
1321
+ `,
1322
+ binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1323
+ }, async () => {
1324
+ delete process.env.TMUX;
1325
+ process.env.TMUX_PANE = '%1';
1326
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1327
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
1328
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
1329
+ await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('team-dead-startup-pane', 'dead pane should still fail startup', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd)), /Worker worker-1 did not become ready/);
1330
+ });
1331
+ }
1332
+ finally {
1333
+ if (typeof previousTmux === 'string')
1334
+ process.env.TMUX = previousTmux;
1335
+ else
1336
+ delete process.env.TMUX;
1337
+ if (typeof previousTmuxPane === 'string')
1338
+ process.env.TMUX_PANE = previousTmuxPane;
1339
+ else
1340
+ delete process.env.TMUX_PANE;
1341
+ if (typeof previousLaunchMode === 'string')
1342
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
1343
+ else
1344
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1345
+ if (typeof previousWorkerCli === 'string')
1346
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
1347
+ else
1348
+ delete process.env.OMX_TEAM_WORKER_CLI;
1349
+ if (typeof previousStartupEvidenceTimeout === 'string') {
1350
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
1351
+ }
1352
+ else {
1353
+ delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1354
+ }
1355
+ await rm(cwd, { recursive: true, force: true });
1356
+ }
1357
+ });
1358
+ it('startTeam materializes all worker identity/inbox files before worker-1 startup evidence can block later workers', async () => {
1359
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-materialize-before-evidence-'));
1360
+ const previousTmux = process.env.TMUX;
1361
+ const previousTmuxPane = process.env.TMUX_PANE;
1362
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1363
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1364
+ const previousSkipReadyWait = process.env.OMX_TEAM_SKIP_READY_WAIT;
1365
+ const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1366
+ try {
1367
+ await withMockTmuxFixture({
1368
+ dirPrefix: 'omx-runtime-materialize-before-evidence-bin-',
1369
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
1370
+ set -eu
1371
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
1372
+ case "$1" in
1373
+ -V)
1374
+ echo "tmux 3.4"
1375
+ exit 0
1376
+ ;;
1377
+ display-message)
1378
+ case "$*" in
1379
+ *"#{window_width}"*)
1380
+ echo "120"
1381
+ ;;
1382
+ *)
1383
+ echo "leader:0 %1"
1384
+ ;;
1385
+ esac
1386
+ exit 0
1387
+ ;;
1388
+ list-panes)
1389
+ case "$*" in
1390
+ *"#{pane_dead} #{pane_pid}"*)
1391
+ echo "0 4242"
1392
+ ;;
1393
+ *"pane_current_command"*)
1394
+ printf "%%1\\tnode\\t'codex'\\n"
1395
+ ;;
1396
+ *"-t %2"*"#{pane_pid}"*)
1397
+ echo "4242"
1398
+ ;;
1399
+ *"-t %3"*"#{pane_pid}"*)
1400
+ echo "4343"
1401
+ ;;
1402
+ *"-t %4"*"#{pane_pid}"*)
1403
+ echo "4444"
1404
+ ;;
1405
+ *"#{pane_pid}"*)
1406
+ echo "4141"
1407
+ ;;
1408
+ *)
1409
+ exit 0
1410
+ ;;
1411
+ esac
1412
+ exit 0
1413
+ ;;
1414
+ capture-pane)
1415
+ exit 0
1416
+ ;;
1417
+ split-window)
1418
+ count_file="${cwd}/split-window-count"
1419
+ count=0
1420
+ if [ -f "$count_file" ]; then
1421
+ count=$(cat "$count_file")
1422
+ fi
1423
+ count=$((count + 1))
1424
+ printf '%s' "$count" > "$count_file"
1425
+ case "$count" in
1426
+ 1) echo "%2" ;;
1427
+ 2) echo "%3" ;;
1428
+ 3) echo "%4" ;;
1429
+ *) echo "%5" ;;
1430
+ esac
1431
+ exit 0
1432
+ ;;
1433
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session|resize-pane)
1434
+ exit 0
1435
+ ;;
1436
+ *)
1437
+ exit 0
1438
+ ;;
1439
+ esac
1440
+ `,
1441
+ binaries: [
1442
+ {
1443
+ name: 'codex',
1444
+ content: `#!/usr/bin/env node
1445
+ process.stdin.resume();
1446
+ setTimeout(() => process.exit(0), 30000);
1447
+ process.on('SIGTERM', () => process.exit(0));
1448
+ `,
1449
+ },
1450
+ ],
1451
+ }, async () => {
1452
+ const sanitizedTeamName = sanitizeTeamName('team-materialize-before-evidence');
1453
+ delete process.env.TMUX;
1454
+ process.env.TMUX_PANE = '%1';
1455
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1456
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
1457
+ process.env.OMX_TEAM_SKIP_READY_WAIT = '1';
1458
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
1459
+ const teamPromise = withoutTeamWorkerEnv(() => startTeam('team-materialize-before-evidence', 'later workers should materialize before startup evidence failure', 'executor', 2, [
1460
+ { subject: 'w1', description: 'worker one', owner: 'worker-1' },
1461
+ { subject: 'w2', description: 'worker two', owner: 'worker-2' },
1462
+ ], cwd));
1463
+ const observedTeamPromise = teamPromise.then((runtime) => ({ ok: true, runtime }), (error) => ({ ok: false, error }));
1464
+ const workerOneIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-1', 'identity.json');
1465
+ const workerTwoIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'identity.json');
1466
+ const workerTwoInbox = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'inbox.md');
1467
+ let materializedAllWorkers = false;
1468
+ for (let attempt = 0; attempt < 450; attempt += 1) {
1469
+ if (existsSync(workerOneIdentity)
1470
+ && existsSync(workerTwoIdentity)
1471
+ && existsSync(workerTwoInbox)) {
1472
+ materializedAllWorkers = true;
1473
+ break;
1474
+ }
1475
+ await new Promise((resolve) => setTimeout(resolve, 100));
1476
+ }
1477
+ assert.equal(materializedAllWorkers, true, 'worker-2 durable state should exist before worker-1 startup evidence failure rejects launch');
1478
+ const outcome = await observedTeamPromise;
1479
+ assert.equal(outcome.ok, false);
1480
+ assert.match(String(outcome.error), /worker_notify_failed:worker-1/);
1481
+ assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', sanitizedTeamName)), false);
1482
+ });
1483
+ }
1484
+ finally {
1485
+ if (typeof previousTmux === 'string')
1486
+ process.env.TMUX = previousTmux;
1487
+ else
1488
+ delete process.env.TMUX;
1489
+ if (typeof previousTmuxPane === 'string')
1490
+ process.env.TMUX_PANE = previousTmuxPane;
1491
+ else
1492
+ delete process.env.TMUX_PANE;
1493
+ if (typeof previousLaunchMode === 'string')
1494
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
1495
+ else
1496
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1497
+ if (typeof previousWorkerCli === 'string')
1498
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
1499
+ else
1500
+ delete process.env.OMX_TEAM_WORKER_CLI;
1501
+ if (typeof previousSkipReadyWait === 'string')
1502
+ process.env.OMX_TEAM_SKIP_READY_WAIT = previousSkipReadyWait;
1503
+ else
1504
+ delete process.env.OMX_TEAM_SKIP_READY_WAIT;
1505
+ if (typeof previousStartupEvidenceTimeout === 'string') {
1506
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
1507
+ }
1508
+ else {
1509
+ delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1510
+ }
1511
+ await rm(cwd, { recursive: true, force: true });
1512
+ }
1513
+ });
934
1514
  it('startTeam rejects dirty leader workspace before provisioning worker worktrees', async () => {
935
1515
  const repo = await initRepo();
936
1516
  const prevLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
@@ -982,8 +1562,6 @@ sleep 5
982
1562
  assert.equal(runtime.config.worker_launch_mode, 'prompt');
983
1563
  assert.equal((runtime.config.workers[0]?.pid ?? 0) > 0, true);
984
1564
  const expectedArgv = [
985
- '--approval-mode',
986
- 'yolo',
987
1565
  '-i',
988
1566
  'Read .omx/state/team/team-gemini-prompt/workers/worker-1/inbox.md, start work now, report concrete progress, then continue assigned work or next feasible task.',
989
1567
  ];
@@ -1034,59 +1612,29 @@ sleep 5
1034
1612
  await rm(cwd, { recursive: true, force: true });
1035
1613
  }
1036
1614
  });
1037
- it('startTeam preserves explicit codex launch args while forcing bypass in prompt mode', async () => {
1615
+ it('startTeam rejects codex prompt mode even when explicit launch args are provided', async () => {
1038
1616
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-codex-explicit-launch-'));
1039
1617
  const binDir = join(cwd, 'bin');
1040
1618
  const fakeCodexPath = join(binDir, 'codex');
1041
- const capturePath = join(cwd, 'codex-argv.json');
1042
1619
  await mkdir(binDir, { recursive: true });
1043
1620
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
1044
- const fs = require('fs');
1045
- fs.writeFileSync(process.env.OMX_CODEX_ARGV_CAPTURE_PATH, JSON.stringify(process.argv.slice(2), null, 2));
1046
- process.stdin.resume();
1047
- setTimeout(() => process.exit(0), 5000);
1048
- process.on('SIGTERM', () => process.exit(0));
1621
+ process.exit(0);
1049
1622
  `, { mode: 0o755 });
1050
1623
  const prevPath = process.env.PATH;
1051
1624
  const prevTmux = process.env.TMUX;
1052
1625
  const prevLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1053
1626
  const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1054
1627
  const prevLaunchArgs = process.env.OMX_TEAM_WORKER_LAUNCH_ARGS;
1055
- const prevCapture = process.env.OMX_CODEX_ARGV_CAPTURE_PATH;
1056
1628
  process.env.PATH = `${binDir}:${prevPath ?? ''}`;
1057
1629
  delete process.env.TMUX;
1058
1630
  process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'prompt';
1059
1631
  process.env.OMX_TEAM_WORKER_CLI = 'codex';
1060
1632
  process.env.OMX_TEAM_WORKER_LAUNCH_ARGS = '--model gpt-5.3-codex-spark -c model_reasoning_effort="low"';
1061
- process.env.OMX_CODEX_ARGV_CAPTURE_PATH = capturePath;
1062
- let runtime = null;
1063
1633
  try {
1064
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-codex-explicit-launch', 'codex prompt-mode team bootstrap', 'explore', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
1065
- assert.equal(runtime.config.worker_launch_mode, 'prompt');
1066
- assert.equal((runtime.config.workers[0]?.pid ?? 0) > 0, true);
1067
- let argv = null;
1068
- for (let attempt = 0; attempt < 50; attempt += 1) {
1069
- if (existsSync(capturePath)) {
1070
- argv = JSON.parse(await readFile(capturePath, 'utf-8'));
1071
- break;
1072
- }
1073
- await new Promise((resolve) => setTimeout(resolve, 100));
1074
- }
1075
- assert.ok(argv, 'codex argv capture file should be written');
1076
- assert.equal(argv.includes('--dangerously-bypass-approvals-and-sandbox'), true);
1077
- assert.equal(argv.filter((arg) => arg === '--dangerously-bypass-approvals-and-sandbox').length, 1);
1078
- assert.equal(argv.includes('--model'), true);
1079
- assert.equal(argv[argv.indexOf('--model') + 1], 'gpt-5.3-codex-spark');
1080
- assert.equal(argv.includes('-c'), true);
1081
- assert.equal(argv.includes('model_reasoning_effort="low"'), true);
1082
- assert.equal(argv.some((arg) => arg.includes('model_instructions_file=')), true);
1083
- await shutdownTeam(runtime.teamName, cwd, { force: true });
1084
- runtime = null;
1634
+ await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('team-codex-explicit-launch', 'codex prompt-mode team bootstrap', 'explore', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd)), /prompt_mode_codex_requires_tty/);
1635
+ assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', 'team-codex-explicit-launch')), false);
1085
1636
  }
1086
1637
  finally {
1087
- if (runtime) {
1088
- await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
1089
- }
1090
1638
  if (typeof prevPath === 'string')
1091
1639
  process.env.PATH = prevPath;
1092
1640
  else
@@ -1107,10 +1655,6 @@ process.on('SIGTERM', () => process.exit(0));
1107
1655
  process.env.OMX_TEAM_WORKER_LAUNCH_ARGS = prevLaunchArgs;
1108
1656
  else
1109
1657
  delete process.env.OMX_TEAM_WORKER_LAUNCH_ARGS;
1110
- if (typeof prevCapture === 'string')
1111
- process.env.OMX_CODEX_ARGV_CAPTURE_PATH = prevCapture;
1112
- else
1113
- delete process.env.OMX_CODEX_ARGV_CAPTURE_PATH;
1114
1658
  await rm(cwd, { recursive: true, force: true });
1115
1659
  }
1116
1660
  });
@@ -1147,10 +1691,10 @@ process.on('SIGTERM', () => process.exit(0));
1147
1691
  process.env.OMX_ARGV_CAPTURE_DIR = captureDir;
1148
1692
  let runtime = null;
1149
1693
  try {
1150
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
1694
+ runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
1151
1695
  { subject: 'test routing report only', description: 'test routing report only', owner: 'worker-1', role: 'test-engineer' },
1152
1696
  { subject: 'document routing report only', description: 'document routing report only', owner: 'worker-2', role: 'writer' },
1153
- ], cwd));
1697
+ ], cwd)));
1154
1698
  assert.equal(runtime.config.worker_launch_mode, 'prompt');
1155
1699
  assert.equal(runtime.config.workers[0]?.role, 'test-engineer');
1156
1700
  assert.equal(runtime.config.workers[1]?.role, 'writer');
@@ -1256,9 +1800,9 @@ process.on('SIGTERM', () => process.exit(0));
1256
1800
  process.env.OMX_TEAM_WORKER_LAUNCH_ARGS = '--model gpt-5.4-mini-tuned';
1257
1801
  let runtime = null;
1258
1802
  try {
1259
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-mini-tuned-routing', 'mini tuned routing handoff', 'executor', 1, [
1803
+ runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-mini-tuned-routing', 'mini tuned routing handoff', 'executor', 1, [
1260
1804
  { subject: 'document routing report only', description: 'document routing report only', owner: 'worker-1', role: 'writer' },
1261
- ], cwd));
1805
+ ], cwd)));
1262
1806
  const workerInstructions = await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'workers', 'worker-1', 'AGENTS.md'), 'utf-8');
1263
1807
  assert.match(workerInstructions, /You are operating as the \*\*writer\*\* role/);
1264
1808
  assert.match(workerInstructions, /You are Writer\./);
@@ -1311,7 +1855,7 @@ process.on('SIGTERM', () => process.exit(0));
1311
1855
  await rm(cwd, { recursive: true, force: true });
1312
1856
  }
1313
1857
  });
1314
- it('startTeam supports prompt launch mode without tmux and pipes trigger text via stdin', async () => {
1858
+ it('startTeam rejects codex prompt mode without tmux with an explicit non-tty error', async () => {
1315
1859
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-'));
1316
1860
  const binDir = join(cwd, 'bin');
1317
1861
  const fakeCodexPath = join(binDir, 'codex');
@@ -1329,19 +1873,11 @@ process.on('SIGTERM', () => process.exit(0));
1329
1873
  delete process.env.TMUX;
1330
1874
  process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'prompt';
1331
1875
  process.env.OMX_TEAM_WORKER_CLI = 'codex';
1332
- let runtime = null;
1333
1876
  try {
1334
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-prompt', 'prompt-mode team bootstrap', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
1335
- assert.equal(runtime.config.worker_launch_mode, 'prompt');
1336
- assert.equal(runtime.config.leader_pane_id, null);
1337
- assert.equal((runtime.config.workers[0]?.pid ?? 0) > 0, true);
1338
- await shutdownTeam(runtime.teamName, cwd, { force: true });
1339
- runtime = null;
1877
+ await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('team-prompt', 'prompt-mode team bootstrap', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd)), /prompt_mode_codex_requires_tty/);
1878
+ assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', 'team-prompt')), false);
1340
1879
  }
1341
1880
  finally {
1342
- if (runtime) {
1343
- await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
1344
- }
1345
1881
  if (typeof prevPath === 'string')
1346
1882
  process.env.PATH = prevPath;
1347
1883
  else
@@ -1520,7 +2056,7 @@ process.on('SIGTERM', () => process.exit(0));
1520
2056
  process.env.OMX_TEST_LOG_DIR = logDir;
1521
2057
  let runtime = null;
1522
2058
  try {
1523
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-paths', 'detached worktree path resolution', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], repo, { worktreeMode: { enabled: true, detached: true, name: null } }));
2059
+ runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-paths', 'detached worktree path resolution', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], repo, { worktreeMode: { enabled: true, detached: true, name: null } })));
1524
2060
  const workerPath = runtime.config.workers[0]?.worktree_path;
1525
2061
  assert.ok(workerPath, 'detached worker should have a worktree path');
1526
2062
  assert.notEqual(workerPath, repo);
@@ -1592,7 +2128,7 @@ process.on('SIGTERM', () => process.exit(0));
1592
2128
  process.env.OMX_TEAM_WORKER_CLI = 'codex';
1593
2129
  let runtime = null;
1594
2130
  try {
1595
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-shutdown', 'detached worktree shutdown cleanup', 'executor', 1, [], repo, { worktreeMode: { enabled: true, detached: true, name: null } }));
2131
+ runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-shutdown', 'detached worktree shutdown cleanup', 'executor', 1, [], repo, { worktreeMode: { enabled: true, detached: true, name: null } })));
1596
2132
  const worktreePath = runtime.config.workers[0]?.worktree_path;
1597
2133
  assert.ok(worktreePath, 'worker worktree path should be persisted');
1598
2134
  assert.equal(runtime.config.workers[0]?.worktree_created, true);
@@ -1659,7 +2195,7 @@ process.on('SIGTERM', () => process.exit(0));
1659
2195
  process.env.OMX_TEST_LOG_DIR = logDir;
1660
2196
  let runtime = null;
1661
2197
  try {
1662
- runtime = await withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-resume-metadata', 'detached worktree resume metadata', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], repo, { worktreeMode: { enabled: true, detached: true, name: null } }));
2198
+ runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-detached-worktree-resume-metadata', 'detached worktree resume metadata', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], repo, { worktreeMode: { enabled: true, detached: true, name: null } })));
1663
2199
  const originalWorker = runtime.config.workers[0];
1664
2200
  const originalWorktreePath = originalWorker?.worktree_path;
1665
2201
  assert.ok(originalWorktreePath, 'worker worktree path should be persisted before resume');
@@ -3101,6 +3637,78 @@ esac
3101
3637
  await rm(cwd, { recursive: true, force: true });
3102
3638
  }
3103
3639
  });
3640
+ it('shutdownTeam reconciles stale leader and hud pane ids before native Windows split-pane teardown', async () => {
3641
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-win32-stale-topology-'));
3642
+ const teamName = 'team-win32-stale-topo';
3643
+ try {
3644
+ await withNativeWindowsPlatform(async () => {
3645
+ await withMockTmuxFixture({
3646
+ dirPrefix: 'omx-runtime-shutdown-win32-stale-topology-bin-',
3647
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
3648
+ set -eu
3649
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
3650
+ case "$1" in
3651
+ -V)
3652
+ echo "tmux 3.4"
3653
+ exit 0
3654
+ ;;
3655
+ list-panes)
3656
+ case "$*" in
3657
+ *"-F #{pane_dead} #{pane_pid}"*)
3658
+ exit 1
3659
+ ;;
3660
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
3661
+ printf "%%21\\tpwsh\\tpwsh\\n%%22\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%23\\tcodex\\tcodex\\n%%24\\tcodex\\tcodex\\n"
3662
+ exit 0
3663
+ ;;
3664
+ *)
3665
+ exit 1
3666
+ ;;
3667
+ esac
3668
+ ;;
3669
+ split-window)
3670
+ printf '%%44\\n'
3671
+ exit 0
3672
+ ;;
3673
+ kill-pane|resize-pane|select-pane)
3674
+ exit 0
3675
+ ;;
3676
+ *)
3677
+ exit 0
3678
+ ;;
3679
+ esac
3680
+ `,
3681
+ }, async ({ tmuxLogPath }) => {
3682
+ await initTeamState(teamName, 'shutdown win32 stale topology test', 'executor', 2, cwd);
3683
+ const config = await readTeamConfig(teamName, cwd);
3684
+ assert.ok(config);
3685
+ if (!config)
3686
+ return;
3687
+ config.tmux_session = 'leader:0';
3688
+ config.leader_pane_id = '%11';
3689
+ config.hud_pane_id = '%12';
3690
+ config.workers[0].pane_id = '%23';
3691
+ config.workers[1].pane_id = '%24';
3692
+ await saveTeamConfig(config, cwd);
3693
+ await shutdownTeam(teamName, cwd, { force: true });
3694
+ const teamRoot = join(cwd, '.omx', 'state', 'team', teamName);
3695
+ assert.equal(existsSync(teamRoot), false);
3696
+ assert.equal(await readMonitorSnapshot(teamName, cwd), null);
3697
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
3698
+ assert.doesNotMatch(tmuxLog, /list-panes -t %21 -F #\{pane_pid\}/);
3699
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %21/);
3700
+ assert.match(tmuxLog, /kill-pane -t %22/);
3701
+ assert.match(tmuxLog, /kill-pane -t %23/);
3702
+ assert.match(tmuxLog, /kill-pane -t %24/);
3703
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %21 -d -P -F #\\{pane_id\\}`));
3704
+ assert.match(tmuxLog, /select-pane -t %21/);
3705
+ });
3706
+ });
3707
+ }
3708
+ finally {
3709
+ await rm(cwd, { recursive: true, force: true });
3710
+ }
3711
+ });
3104
3712
  it('shutdownTeam skips prekill and keeps the leader pane alive on shared-session shutdown', async () => {
3105
3713
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-shared-session-'));
3106
3714
  try {