oh-my-codex 0.15.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/Cargo.lock +10 -7
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -0
  4. package/crates/omx-explore/Cargo.toml +3 -0
  5. package/crates/omx-explore/src/main.rs +517 -16
  6. package/dist/agents/__tests__/native-config.test.js +33 -0
  7. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  8. package/dist/autoresearch/goal.d.ts +90 -0
  9. package/dist/autoresearch/goal.d.ts.map +1 -0
  10. package/dist/autoresearch/goal.js +237 -0
  11. package/dist/autoresearch/goal.js.map +1 -0
  12. package/dist/autoresearch/skill-validation.d.ts +1 -0
  13. package/dist/autoresearch/skill-validation.d.ts.map +1 -1
  14. package/dist/autoresearch/skill-validation.js +10 -3
  15. package/dist/autoresearch/skill-validation.js.map +1 -1
  16. package/dist/catalog/__tests__/generator.test.js +9 -4
  17. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  18. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +29 -2
  19. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  20. package/dist/catalog/__tests__/schema.test.js +14 -3
  21. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  22. package/dist/catalog/schema.js +1 -1
  23. package/dist/catalog/schema.js.map +1 -1
  24. package/dist/cli/__tests__/autoresearch-goal.test.d.ts +2 -0
  25. package/dist/cli/__tests__/autoresearch-goal.test.d.ts.map +1 -0
  26. package/dist/cli/__tests__/autoresearch-goal.test.js +194 -0
  27. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -0
  28. package/dist/cli/__tests__/cleanup.test.js +82 -1
  29. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  30. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -4
  31. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  32. package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts +2 -0
  33. package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts.map +1 -0
  34. package/dist/cli/__tests__/doctor-context-window-warning.test.js +122 -0
  35. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -0
  36. package/dist/cli/__tests__/doctor-warning-copy.test.js +25 -2
  37. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  38. package/dist/cli/__tests__/exec.test.js +1 -0
  39. package/dist/cli/__tests__/exec.test.js.map +1 -1
  40. package/dist/cli/__tests__/explore.test.js +48 -18
  41. package/dist/cli/__tests__/explore.test.js.map +1 -1
  42. package/dist/cli/__tests__/index.test.js +222 -10
  43. package/dist/cli/__tests__/index.test.js.map +1 -1
  44. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  45. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  46. package/dist/cli/__tests__/mcp-serve.test.js +27 -1
  47. package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
  48. package/dist/cli/__tests__/native-assets.test.js +26 -1
  49. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  50. package/dist/cli/__tests__/package-bin-contract.test.js +2 -2
  51. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  52. package/dist/cli/__tests__/performance-goal.test.d.ts +2 -0
  53. package/dist/cli/__tests__/performance-goal.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/performance-goal.test.js +144 -0
  55. package/dist/cli/__tests__/performance-goal.test.js.map +1 -0
  56. package/dist/cli/__tests__/question.test.js +8 -0
  57. package/dist/cli/__tests__/question.test.js.map +1 -1
  58. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts +2 -0
  59. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts.map +1 -0
  60. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +31 -0
  61. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -0
  62. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +5 -4
  63. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  64. package/dist/cli/__tests__/ralph-prd-smoke.test.js +7 -0
  65. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  66. package/dist/cli/__tests__/ralph.test.js +59 -1
  67. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  68. package/dist/cli/__tests__/setup-install-mode.test.js +57 -21
  69. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  70. package/dist/cli/__tests__/setup-refresh.test.js +27 -8
  71. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  72. package/dist/cli/__tests__/setup-scope.test.js +20 -10
  73. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  74. package/dist/cli/__tests__/setup-skill-validation.test.js +11 -11
  75. package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -1
  76. package/dist/cli/__tests__/setup-skills-overwrite.test.js +12 -12
  77. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  78. package/dist/cli/__tests__/team.test.js +242 -10
  79. package/dist/cli/__tests__/team.test.js.map +1 -1
  80. package/dist/cli/__tests__/ultragoal.test.d.ts +2 -0
  81. package/dist/cli/__tests__/ultragoal.test.d.ts.map +1 -0
  82. package/dist/cli/__tests__/ultragoal.test.js +106 -0
  83. package/dist/cli/__tests__/ultragoal.test.js.map +1 -0
  84. package/dist/cli/__tests__/uninstall.test.js +11 -0
  85. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  86. package/dist/cli/autoresearch-goal.d.ts +3 -0
  87. package/dist/cli/autoresearch-goal.d.ts.map +1 -0
  88. package/dist/cli/autoresearch-goal.js +175 -0
  89. package/dist/cli/autoresearch-goal.js.map +1 -0
  90. package/dist/cli/cleanup.d.ts +3 -1
  91. package/dist/cli/cleanup.d.ts.map +1 -1
  92. package/dist/cli/cleanup.js +42 -2
  93. package/dist/cli/cleanup.js.map +1 -1
  94. package/dist/cli/doctor.d.ts.map +1 -1
  95. package/dist/cli/doctor.js +95 -3
  96. package/dist/cli/doctor.js.map +1 -1
  97. package/dist/cli/explore.d.ts.map +1 -1
  98. package/dist/cli/explore.js +10 -2
  99. package/dist/cli/explore.js.map +1 -1
  100. package/dist/cli/index.d.ts +21 -2
  101. package/dist/cli/index.d.ts.map +1 -1
  102. package/dist/cli/index.js +268 -30
  103. package/dist/cli/index.js.map +1 -1
  104. package/dist/cli/mcp-serve.d.ts +1 -0
  105. package/dist/cli/mcp-serve.d.ts.map +1 -1
  106. package/dist/cli/mcp-serve.js +8 -0
  107. package/dist/cli/mcp-serve.js.map +1 -1
  108. package/dist/cli/native-assets.js +1 -1
  109. package/dist/cli/native-assets.js.map +1 -1
  110. package/dist/cli/performance-goal.d.ts +3 -0
  111. package/dist/cli/performance-goal.d.ts.map +1 -0
  112. package/dist/cli/performance-goal.js +186 -0
  113. package/dist/cli/performance-goal.js.map +1 -0
  114. package/dist/cli/ralph.d.ts +2 -0
  115. package/dist/cli/ralph.d.ts.map +1 -1
  116. package/dist/cli/ralph.js +25 -1
  117. package/dist/cli/ralph.js.map +1 -1
  118. package/dist/cli/setup.d.ts.map +1 -1
  119. package/dist/cli/setup.js +13 -6
  120. package/dist/cli/setup.js.map +1 -1
  121. package/dist/cli/team.d.ts +6 -0
  122. package/dist/cli/team.d.ts.map +1 -1
  123. package/dist/cli/team.js +113 -33
  124. package/dist/cli/team.js.map +1 -1
  125. package/dist/cli/tmux-hook.d.ts.map +1 -1
  126. package/dist/cli/tmux-hook.js +2 -1
  127. package/dist/cli/tmux-hook.js.map +1 -1
  128. package/dist/cli/ultragoal.d.ts +3 -0
  129. package/dist/cli/ultragoal.d.ts.map +1 -0
  130. package/dist/cli/ultragoal.js +191 -0
  131. package/dist/cli/ultragoal.js.map +1 -0
  132. package/dist/cli/uninstall.d.ts.map +1 -1
  133. package/dist/cli/uninstall.js +4 -2
  134. package/dist/cli/uninstall.js.map +1 -1
  135. package/dist/config/__tests__/generator-idempotent.test.js +39 -6
  136. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  137. package/dist/config/__tests__/generator-notify.test.js +5 -0
  138. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  139. package/dist/config/commit-lore-guard.d.ts +3 -0
  140. package/dist/config/commit-lore-guard.d.ts.map +1 -0
  141. package/dist/config/commit-lore-guard.js +9 -0
  142. package/dist/config/commit-lore-guard.js.map +1 -0
  143. package/dist/config/generator.d.ts +14 -4
  144. package/dist/config/generator.d.ts.map +1 -1
  145. package/dist/config/generator.js +166 -66
  146. package/dist/config/generator.js.map +1 -1
  147. package/dist/config/omx-first-party-mcp.d.ts +1 -0
  148. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  149. package/dist/config/omx-first-party-mcp.js +4 -1
  150. package/dist/config/omx-first-party-mcp.js.map +1 -1
  151. package/dist/goal-workflows/__tests__/artifacts.test.d.ts +2 -0
  152. package/dist/goal-workflows/__tests__/artifacts.test.d.ts.map +1 -0
  153. package/dist/goal-workflows/__tests__/artifacts.test.js +96 -0
  154. package/dist/goal-workflows/__tests__/artifacts.test.js.map +1 -0
  155. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts +2 -0
  156. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts.map +1 -0
  157. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +54 -0
  158. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -0
  159. package/dist/goal-workflows/artifacts.d.ts +62 -0
  160. package/dist/goal-workflows/artifacts.d.ts.map +1 -0
  161. package/dist/goal-workflows/artifacts.js +132 -0
  162. package/dist/goal-workflows/artifacts.js.map +1 -0
  163. package/dist/goal-workflows/codex-goal-snapshot.d.ts +28 -0
  164. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -0
  165. package/dist/goal-workflows/codex-goal-snapshot.js +110 -0
  166. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -0
  167. package/dist/goal-workflows/handoff.d.ts +10 -0
  168. package/dist/goal-workflows/handoff.d.ts.map +1 -0
  169. package/dist/goal-workflows/handoff.js +31 -0
  170. package/dist/goal-workflows/handoff.js.map +1 -0
  171. package/dist/goal-workflows/validation.d.ts +13 -0
  172. package/dist/goal-workflows/validation.d.ts.map +1 -0
  173. package/dist/goal-workflows/validation.js +36 -0
  174. package/dist/goal-workflows/validation.js.map +1 -0
  175. package/dist/hooks/__tests__/agents-overlay.test.js +59 -0
  176. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  177. package/dist/hooks/__tests__/anti-slop-workflow.test.js +109 -18
  178. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  179. package/dist/hooks/__tests__/keyword-detector.test.js +45 -32
  180. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  181. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +3 -3
  182. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  183. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
  184. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  185. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +17 -24
  186. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  187. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +3 -3
  188. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  189. package/dist/hooks/__tests__/task-size-detector.test.js +1 -1
  190. package/dist/hooks/__tests__/task-size-detector.test.js.map +1 -1
  191. package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
  192. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  193. package/dist/hooks/__tests__/visual-verdict-loop.test.js +7 -11
  194. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  195. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  196. package/dist/hooks/agents-overlay.js +23 -2
  197. package/dist/hooks/agents-overlay.js.map +1 -1
  198. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  199. package/dist/hooks/keyword-detector.js +12 -13
  200. package/dist/hooks/keyword-detector.js.map +1 -1
  201. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  202. package/dist/hooks/keyword-registry.js +2 -10
  203. package/dist/hooks/keyword-registry.js.map +1 -1
  204. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  205. package/dist/hooks/prompt-guidance-contract.js +0 -4
  206. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  207. package/dist/hooks/session.js +2 -2
  208. package/dist/hooks/session.js.map +1 -1
  209. package/dist/hooks/task-size-detector.d.ts.map +1 -1
  210. package/dist/hooks/task-size-detector.js +1 -0
  211. package/dist/hooks/task-size-detector.js.map +1 -1
  212. package/dist/hud/__tests__/index.test.js +30 -14
  213. package/dist/hud/__tests__/index.test.js.map +1 -1
  214. package/dist/hud/__tests__/reconcile.test.js +29 -7
  215. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  216. package/dist/hud/reconcile.d.ts +2 -1
  217. package/dist/hud/reconcile.d.ts.map +1 -1
  218. package/dist/hud/reconcile.js +12 -0
  219. package/dist/hud/reconcile.js.map +1 -1
  220. package/dist/mcp/__tests__/bootstrap.test.js +15 -2
  221. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  222. package/dist/mcp/__tests__/state-paths.test.js +54 -0
  223. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  224. package/dist/mcp/__tests__/state-server.test.js +36 -0
  225. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  226. package/dist/mcp/bootstrap.d.ts +1 -1
  227. package/dist/mcp/bootstrap.d.ts.map +1 -1
  228. package/dist/mcp/bootstrap.js +9 -7
  229. package/dist/mcp/bootstrap.js.map +1 -1
  230. package/dist/mcp/state-paths.d.ts +17 -0
  231. package/dist/mcp/state-paths.d.ts.map +1 -1
  232. package/dist/mcp/state-paths.js +36 -2
  233. package/dist/mcp/state-paths.js.map +1 -1
  234. package/dist/modes/__tests__/base-session-scope.test.js +26 -0
  235. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  236. package/dist/modes/base.d.ts +1 -0
  237. package/dist/modes/base.d.ts.map +1 -1
  238. package/dist/modes/base.js +35 -5
  239. package/dist/modes/base.js.map +1 -1
  240. package/dist/notifications/__tests__/http-client.test.d.ts +2 -0
  241. package/dist/notifications/__tests__/http-client.test.d.ts.map +1 -0
  242. package/dist/notifications/__tests__/http-client.test.js +90 -0
  243. package/dist/notifications/__tests__/http-client.test.js.map +1 -0
  244. package/dist/notifications/__tests__/notifier.test.js +22 -60
  245. package/dist/notifications/__tests__/notifier.test.js.map +1 -1
  246. package/dist/notifications/dispatcher.d.ts.map +1 -1
  247. package/dist/notifications/dispatcher.js +35 -60
  248. package/dist/notifications/dispatcher.js.map +1 -1
  249. package/dist/notifications/http-client.d.ts +22 -0
  250. package/dist/notifications/http-client.d.ts.map +1 -0
  251. package/dist/notifications/http-client.js +298 -0
  252. package/dist/notifications/http-client.js.map +1 -0
  253. package/dist/notifications/notifier.d.ts +3 -2
  254. package/dist/notifications/notifier.d.ts.map +1 -1
  255. package/dist/notifications/notifier.js +17 -22
  256. package/dist/notifications/notifier.js.map +1 -1
  257. package/dist/openclaw/__tests__/dispatcher.test.js +63 -2
  258. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  259. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  260. package/dist/openclaw/dispatcher.js +3 -2
  261. package/dist/openclaw/dispatcher.js.map +1 -1
  262. package/dist/performance-goal/artifacts.d.ts +76 -0
  263. package/dist/performance-goal/artifacts.d.ts.map +1 -0
  264. package/dist/performance-goal/artifacts.js +221 -0
  265. package/dist/performance-goal/artifacts.js.map +1 -0
  266. package/dist/pipeline/__tests__/stages.test.js +423 -14
  267. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  268. package/dist/pipeline/stages/team-exec.d.ts +8 -4
  269. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  270. package/dist/pipeline/stages/team-exec.js +181 -13
  271. package/dist/pipeline/stages/team-exec.js.map +1 -1
  272. package/dist/planning/__tests__/artifacts.test.js +261 -1
  273. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  274. package/dist/planning/artifact-names.d.ts +13 -0
  275. package/dist/planning/artifact-names.d.ts.map +1 -0
  276. package/dist/planning/artifact-names.js +108 -0
  277. package/dist/planning/artifact-names.js.map +1 -0
  278. package/dist/planning/artifacts.d.ts +23 -1
  279. package/dist/planning/artifacts.d.ts.map +1 -1
  280. package/dist/planning/artifacts.js +171 -59
  281. package/dist/planning/artifacts.js.map +1 -1
  282. package/dist/ralph/__tests__/persistence.test.js +21 -1
  283. package/dist/ralph/__tests__/persistence.test.js.map +1 -1
  284. package/dist/ralph/persistence.d.ts.map +1 -1
  285. package/dist/ralph/persistence.js +6 -4
  286. package/dist/ralph/persistence.js.map +1 -1
  287. package/dist/ralplan/__tests__/runtime.test.js +2 -0
  288. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  289. package/dist/ralplan/runtime.d.ts.map +1 -1
  290. package/dist/ralplan/runtime.js +6 -0
  291. package/dist/ralplan/runtime.js.map +1 -1
  292. package/dist/scripts/__tests__/codex-native-hook.test.js +1749 -88
  293. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  294. package/dist/scripts/__tests__/hook-derived-watcher.test.js +33 -1
  295. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  296. package/dist/scripts/__tests__/run-test-files.test.js +36 -0
  297. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  298. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  299. package/dist/scripts/codex-native-hook.js +570 -45
  300. package/dist/scripts/codex-native-hook.js.map +1 -1
  301. package/dist/scripts/codex-native-pre-post.d.ts +7 -0
  302. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  303. package/dist/scripts/codex-native-pre-post.js +341 -15
  304. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  305. package/dist/scripts/hook-derived-watcher.js +2 -1
  306. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  307. package/dist/scripts/notify-fallback-watcher.js +2 -1
  308. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  309. package/dist/scripts/notify-hook/orchestration-intent.d.ts +1 -2
  310. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  311. package/dist/scripts/notify-hook/orchestration-intent.js +2 -3
  312. package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -1
  313. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +0 -2
  314. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  315. package/dist/scripts/notify-hook/team-leader-nudge.js +8 -60
  316. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  317. package/dist/scripts/notify-hook/team-worker-posttooluse.js +1 -1
  318. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  319. package/dist/scripts/notify-hook/team-worker-stop.d.ts +15 -0
  320. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -0
  321. package/dist/scripts/notify-hook/team-worker-stop.js +224 -0
  322. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -0
  323. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  324. package/dist/scripts/notify-hook/team-worker.js +26 -18
  325. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  326. package/dist/scripts/notify-hook.js +1 -1
  327. package/dist/scripts/notify-hook.js.map +1 -1
  328. package/dist/scripts/run-test-files.js +17 -1
  329. package/dist/scripts/run-test-files.js.map +1 -1
  330. package/dist/scripts/sync-plugin-mirror.d.ts +1 -0
  331. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  332. package/dist/scripts/sync-plugin-mirror.js +10 -4
  333. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  334. package/dist/state/__tests__/operations.test.js +26 -0
  335. package/dist/state/__tests__/operations.test.js.map +1 -1
  336. package/dist/state/__tests__/skill-active.test.js +76 -0
  337. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  338. package/dist/state/operations.d.ts +3 -1
  339. package/dist/state/operations.d.ts.map +1 -1
  340. package/dist/state/operations.js +8 -4
  341. package/dist/state/operations.js.map +1 -1
  342. package/dist/state/skill-active.d.ts +1 -0
  343. package/dist/state/skill-active.d.ts.map +1 -1
  344. package/dist/state/skill-active.js +54 -13
  345. package/dist/state/skill-active.js.map +1 -1
  346. package/dist/team/__tests__/api-interop.test.js +279 -0
  347. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  348. package/dist/team/__tests__/approved-execution.test.d.ts +2 -0
  349. package/dist/team/__tests__/approved-execution.test.d.ts.map +1 -0
  350. package/dist/team/__tests__/approved-execution.test.js +124 -0
  351. package/dist/team/__tests__/approved-execution.test.js.map +1 -0
  352. package/dist/team/__tests__/delivery-e2e-smoke.test.js +2 -4
  353. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  354. package/dist/team/__tests__/delivery-log.test.d.ts +2 -0
  355. package/dist/team/__tests__/delivery-log.test.d.ts.map +1 -0
  356. package/dist/team/__tests__/delivery-log.test.js +44 -0
  357. package/dist/team/__tests__/delivery-log.test.js.map +1 -0
  358. package/dist/team/__tests__/model-contract.test.js +40 -9
  359. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  360. package/dist/team/__tests__/repo-aware-decomposition.test.js +41 -0
  361. package/dist/team/__tests__/repo-aware-decomposition.test.js.map +1 -1
  362. package/dist/team/__tests__/role-router.test.js +4 -4
  363. package/dist/team/__tests__/role-router.test.js.map +1 -1
  364. package/dist/team/__tests__/runtime-boxed-state.test.d.ts +2 -0
  365. package/dist/team/__tests__/runtime-boxed-state.test.d.ts.map +1 -0
  366. package/dist/team/__tests__/runtime-boxed-state.test.js +39 -0
  367. package/dist/team/__tests__/runtime-boxed-state.test.js.map +1 -0
  368. package/dist/team/__tests__/runtime-cli.test.js +24 -0
  369. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  370. package/dist/team/__tests__/runtime.test.js +563 -72
  371. package/dist/team/__tests__/runtime.test.js.map +1 -1
  372. package/dist/team/__tests__/state-root.test.js +13 -0
  373. package/dist/team/__tests__/state-root.test.js.map +1 -1
  374. package/dist/team/__tests__/state.test.js +13 -0
  375. package/dist/team/__tests__/state.test.js.map +1 -1
  376. package/dist/team/__tests__/team-identity.test.d.ts +2 -0
  377. package/dist/team/__tests__/team-identity.test.d.ts.map +1 -0
  378. package/dist/team/__tests__/team-identity.test.js +166 -0
  379. package/dist/team/__tests__/team-identity.test.js.map +1 -0
  380. package/dist/team/__tests__/tmux-session.test.js +58 -1
  381. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  382. package/dist/team/__tests__/worker-bootstrap.test.js +62 -0
  383. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  384. package/dist/team/api-interop.d.ts +1 -0
  385. package/dist/team/api-interop.d.ts.map +1 -1
  386. package/dist/team/api-interop.js +163 -132
  387. package/dist/team/api-interop.js.map +1 -1
  388. package/dist/team/approved-execution.d.ts +37 -0
  389. package/dist/team/approved-execution.d.ts.map +1 -0
  390. package/dist/team/approved-execution.js +136 -0
  391. package/dist/team/approved-execution.js.map +1 -0
  392. package/dist/team/delivery-log.d.ts +1 -1
  393. package/dist/team/delivery-log.d.ts.map +1 -1
  394. package/dist/team/delivery-log.js +2 -1
  395. package/dist/team/delivery-log.js.map +1 -1
  396. package/dist/team/followup-planner.js +2 -2
  397. package/dist/team/followup-planner.js.map +1 -1
  398. package/dist/team/goal-workflow.d.ts +20 -0
  399. package/dist/team/goal-workflow.d.ts.map +1 -0
  400. package/dist/team/goal-workflow.js +57 -0
  401. package/dist/team/goal-workflow.js.map +1 -0
  402. package/dist/team/orchestrator.js +2 -2
  403. package/dist/team/orchestrator.js.map +1 -1
  404. package/dist/team/repo-aware-decomposition.d.ts +3 -0
  405. package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
  406. package/dist/team/repo-aware-decomposition.js +2 -0
  407. package/dist/team/repo-aware-decomposition.js.map +1 -1
  408. package/dist/team/role-router.js +5 -5
  409. package/dist/team/role-router.js.map +1 -1
  410. package/dist/team/runtime-cli.d.ts +32 -2
  411. package/dist/team/runtime-cli.d.ts.map +1 -1
  412. package/dist/team/runtime-cli.js +78 -26
  413. package/dist/team/runtime-cli.js.map +1 -1
  414. package/dist/team/runtime.d.ts +7 -1
  415. package/dist/team/runtime.d.ts.map +1 -1
  416. package/dist/team/runtime.js +383 -40
  417. package/dist/team/runtime.js.map +1 -1
  418. package/dist/team/scaling.d.ts.map +1 -1
  419. package/dist/team/scaling.js +2 -0
  420. package/dist/team/scaling.js.map +1 -1
  421. package/dist/team/state.d.ts +9 -0
  422. package/dist/team/state.d.ts.map +1 -1
  423. package/dist/team/state.js +21 -0
  424. package/dist/team/state.js.map +1 -1
  425. package/dist/team/team-identity.d.ts +26 -0
  426. package/dist/team/team-identity.d.ts.map +1 -0
  427. package/dist/team/team-identity.js +169 -0
  428. package/dist/team/team-identity.js.map +1 -0
  429. package/dist/team/tmux-session.d.ts +18 -0
  430. package/dist/team/tmux-session.d.ts.map +1 -1
  431. package/dist/team/tmux-session.js +65 -3
  432. package/dist/team/tmux-session.js.map +1 -1
  433. package/dist/team/worker-bootstrap.d.ts +4 -0
  434. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  435. package/dist/team/worker-bootstrap.js +28 -2
  436. package/dist/team/worker-bootstrap.js.map +1 -1
  437. package/dist/ultragoal/__tests__/artifacts.test.d.ts +2 -0
  438. package/dist/ultragoal/__tests__/artifacts.test.d.ts.map +1 -0
  439. package/dist/ultragoal/__tests__/artifacts.test.js +93 -0
  440. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -0
  441. package/dist/ultragoal/artifacts.d.ts +89 -0
  442. package/dist/ultragoal/artifacts.d.ts.map +1 -0
  443. package/dist/ultragoal/artifacts.js +233 -0
  444. package/dist/ultragoal/artifacts.js.map +1 -0
  445. package/dist/utils/__tests__/agents-model-table.test.js +3 -1
  446. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  447. package/dist/utils/__tests__/paths.test.js +31 -1
  448. package/dist/utils/__tests__/paths.test.js.map +1 -1
  449. package/dist/utils/agents-model-table.d.ts.map +1 -1
  450. package/dist/utils/agents-model-table.js +12 -1
  451. package/dist/utils/agents-model-table.js.map +1 -1
  452. package/dist/utils/paths.d.ts +2 -0
  453. package/dist/utils/paths.d.ts.map +1 -1
  454. package/dist/utils/paths.js +23 -7
  455. package/dist/utils/paths.js.map +1 -1
  456. package/dist/verification/__tests__/ci-rust-gates.test.js +30 -19
  457. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  458. package/package.json +5 -5
  459. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  460. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +30 -5
  461. package/plugins/oh-my-codex/skills/ask/SKILL.md +58 -0
  462. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +36 -0
  463. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +2 -2
  464. package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +65 -0
  465. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  466. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -3
  467. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -2
  468. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +49 -0
  469. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +9 -9
  470. package/prompts/api-reviewer.md +1 -1
  471. package/prompts/code-reviewer.md +2 -0
  472. package/prompts/performance-reviewer.md +1 -1
  473. package/prompts/quality-reviewer.md +1 -1
  474. package/prompts/quality-strategist.md +2 -2
  475. package/prompts/style-reviewer.md +1 -1
  476. package/prompts/test-engineer.md +1 -1
  477. package/skills/ai-slop-cleaner/SKILL.md +30 -5
  478. package/skills/ask/SKILL.md +58 -0
  479. package/skills/ask-claude/SKILL.md +3 -54
  480. package/skills/ask-gemini/SKILL.md +3 -54
  481. package/skills/autoresearch-goal/SKILL.md +36 -0
  482. package/skills/build-fix/SKILL.md +4 -139
  483. package/skills/deepsearch/SKILL.md +4 -32
  484. package/skills/ecomode/SKILL.md +4 -108
  485. package/skills/help/SKILL.md +4 -196
  486. package/skills/note/SKILL.md +4 -56
  487. package/skills/omx-setup/SKILL.md +2 -2
  488. package/skills/performance-goal/SKILL.md +65 -0
  489. package/skills/plan/SKILL.md +1 -1
  490. package/skills/ralph/SKILL.md +22 -3
  491. package/skills/ralph-init/SKILL.md +4 -40
  492. package/skills/review/SKILL.md +4 -32
  493. package/skills/security-review/SKILL.md +4 -294
  494. package/skills/swarm/SKILL.md +4 -19
  495. package/skills/tdd/SKILL.md +4 -100
  496. package/skills/team/SKILL.md +6 -2
  497. package/skills/trace/SKILL.md +4 -27
  498. package/skills/ultragoal/SKILL.md +49 -0
  499. package/skills/visual-ralph/SKILL.md +9 -9
  500. package/skills/visual-verdict/SKILL.md +4 -70
  501. package/skills/web-clone/SKILL.md +4 -18
  502. package/src/scripts/__tests__/codex-native-hook.test.ts +2923 -1030
  503. package/src/scripts/__tests__/hook-derived-watcher.test.ts +45 -1
  504. package/src/scripts/__tests__/run-test-files.test.ts +46 -0
  505. package/src/scripts/codex-native-hook.ts +696 -46
  506. package/src/scripts/codex-native-pre-post.ts +369 -16
  507. package/src/scripts/hook-derived-watcher.ts +2 -1
  508. package/src/scripts/notify-fallback-watcher.ts +2 -1
  509. package/src/scripts/notify-hook/orchestration-intent.ts +1 -3
  510. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -63
  511. package/src/scripts/notify-hook/team-worker-posttooluse.ts +1 -1
  512. package/src/scripts/notify-hook/team-worker-stop.ts +246 -0
  513. package/src/scripts/notify-hook/team-worker.ts +23 -14
  514. package/src/scripts/notify-hook.ts +1 -1
  515. package/src/scripts/run-test-files.ts +20 -1
  516. package/src/scripts/sync-plugin-mirror.ts +13 -4
  517. package/templates/catalog-manifest.json +45 -27
  518. package/plugins/oh-my-codex/skills/ask-claude/SKILL.md +0 -61
  519. package/plugins/oh-my-codex/skills/ask-gemini/SKILL.md +0 -61
  520. package/plugins/oh-my-codex/skills/help/SKILL.md +0 -202
  521. package/plugins/oh-my-codex/skills/note/SKILL.md +0 -62
  522. package/plugins/oh-my-codex/skills/security-review/SKILL.md +0 -300
  523. package/plugins/oh-my-codex/skills/trace/SKILL.md +0 -33
  524. package/plugins/oh-my-codex/skills/visual-verdict/SKILL.md +0 -76
@@ -1,16 +1,22 @@
1
1
  import { afterEach, beforeEach, describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { execFileSync, spawn } from 'child_process';
4
- import { mkdtemp, rm, writeFile, readFile, mkdir, chmod } from 'fs/promises';
4
+ import { mkdtemp, rm, writeFile, readFile, mkdir, chmod, readdir } from 'fs/promises';
5
5
  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, writeWorkerInbox, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, transitionDispatchRequest, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, readWorkerStatus, writeWorkerStatus, } from '../state.js';
9
+ import { DEFAULT_MAX_WORKERS, initTeamState, createTask, writeWorkerInbox, 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, cleanupTeamWorkerLaunchOrphanedMcpProcesses, settleStartupAttemptResults, } from '../runtime.js';
11
11
  import { resolveAgentReasoningEffort, resolveTeamLowComplexityDefaultModel } from '../model-contract.js';
12
12
  import { readTeamEvents } from '../state/events.js';
13
13
  import { sanitizeTeamName } from '../tmux-session.js';
14
+ import { buildInternalTeamName, resolveTeamIdentityScope } from '../team-identity.js';
15
+ import { writePersistedApprovedTeamExecutionBinding } from '../approved-execution.js';
16
+ const coverageRun = process.env.NODE_V8_COVERAGE ? true : false;
17
+ const skipSlowLifecycleUnderCoverage = coverageRun
18
+ ? 'covered by the team-state-runtime lane; skipped under c8 to keep the coverage gate bounded around slow process-lifecycle waits'
19
+ : false;
14
20
  async function initRepo() {
15
21
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-worktree-repo-'));
16
22
  execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
@@ -47,6 +53,56 @@ async function attachDirtyWorkerRepo(teamName, cwd, repoName) {
47
53
  function expectedLowComplexityModel(codexHomeOverride) {
48
54
  return resolveTeamLowComplexityDefaultModel(codexHomeOverride);
49
55
  }
56
+ function withIsolatedDefaultModelEnv(run) {
57
+ const savedEnv = new Map();
58
+ for (const key of [
59
+ 'CODEX_HOME',
60
+ 'OMX_DEFAULT_FRONTIER_MODEL',
61
+ 'OMX_DEFAULT_STANDARD_MODEL',
62
+ 'OMX_DEFAULT_SPARK_MODEL',
63
+ 'OMX_SPARK_MODEL',
64
+ ]) {
65
+ savedEnv.set(key, process.env[key]);
66
+ delete process.env[key];
67
+ }
68
+ process.env.CODEX_HOME = join(tmpdir(), `omx-runtime-defaults-${process.pid}-${Date.now()}`);
69
+ try {
70
+ return run();
71
+ }
72
+ finally {
73
+ for (const [key, value] of savedEnv.entries()) {
74
+ if (typeof value === 'string')
75
+ process.env[key] = value;
76
+ else
77
+ delete process.env[key];
78
+ }
79
+ }
80
+ }
81
+ async function withIsolatedDefaultModelEnvAsync(run) {
82
+ const savedEnv = new Map();
83
+ for (const key of [
84
+ 'CODEX_HOME',
85
+ 'OMX_DEFAULT_FRONTIER_MODEL',
86
+ 'OMX_DEFAULT_STANDARD_MODEL',
87
+ 'OMX_DEFAULT_SPARK_MODEL',
88
+ 'OMX_SPARK_MODEL',
89
+ ]) {
90
+ savedEnv.set(key, process.env[key]);
91
+ delete process.env[key];
92
+ }
93
+ process.env.CODEX_HOME = join(tmpdir(), `omx-runtime-defaults-${process.pid}-${Date.now()}`);
94
+ try {
95
+ return await run();
96
+ }
97
+ finally {
98
+ for (const [key, value] of savedEnv.entries()) {
99
+ if (typeof value === 'string')
100
+ process.env[key] = value;
101
+ else
102
+ delete process.env[key];
103
+ }
104
+ }
105
+ }
50
106
  async function readTeamDeliveryLog(cwd) {
51
107
  const path = join(cwd, '.omx', 'logs', `team-delivery-${new Date().toISOString().slice(0, 10)}.jsonl`);
52
108
  const raw = await readFile(path, 'utf-8').catch(() => '');
@@ -57,7 +113,7 @@ async function readTeamDeliveryLog(cwd) {
57
113
  .map((line) => JSON.parse(line));
58
114
  }
59
115
  async function markPendingInboxDispatchesDelivered(teamName, cwd, opts = {}) {
60
- const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
116
+ const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
61
117
  for (const request of requests) {
62
118
  if (request.status !== 'pending')
63
119
  continue;
@@ -72,7 +128,7 @@ async function markPendingInboxDispatchesDelivered(teamName, cwd, opts = {}) {
72
128
  }
73
129
  }
74
130
  async function markPendingInboxDispatchesNotified(teamName, cwd, opts = {}) {
75
- const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
131
+ const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
76
132
  for (const request of requests) {
77
133
  if (request.status !== 'pending')
78
134
  continue;
@@ -168,6 +224,16 @@ async function waitForFileText(filePath, matcher, timeoutMs = 3_000) {
168
224
  }
169
225
  throw new Error(`timed out waiting for ${filePath}`);
170
226
  }
227
+ async function resolveRuntimeTeamName(cwd, requestedName) {
228
+ const teamsRoot = join(cwd, '.omx', 'state', 'team');
229
+ const entries = await readdir(teamsRoot, { withFileTypes: true }).catch(() => []);
230
+ const prefix = requestedName.slice(0, 18);
231
+ const names = entries
232
+ .filter((entry) => entry.isDirectory() && (entry.name === requestedName || entry.name.startsWith(`${requestedName}-`) || entry.name.startsWith(prefix)))
233
+ .map((entry) => entry.name)
234
+ .sort((a, b) => a.length - b.length || a.localeCompare(b));
235
+ return names[0] ?? requestedName;
236
+ }
171
237
  async function writeFakePromptWorkerBinary(binaryPath, scriptBody, options = {}) {
172
238
  const bootstrap = options.emitStartupEvidence === false
173
239
  ? ''
@@ -175,7 +241,7 @@ async function writeFakePromptWorkerBinary(binaryPath, scriptBody, options = {})
175
241
  const fs = require('fs');
176
242
  const path = require('path');
177
243
  const stateRoot = process.env.OMX_TEAM_STATE_ROOT;
178
- const worker = String(process.env.OMX_TEAM_WORKER || '');
244
+ const worker = String(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER || '');
179
245
  const [teamName, workerName] = worker.split('/');
180
246
  if (stateRoot && teamName && workerName) {
181
247
  const workerDir = path.join(stateRoot, 'team', teamName, 'workers', workerName);
@@ -318,29 +384,35 @@ describe('runtime', () => {
318
384
  assert.deepEqual(args, ['--no-alt-screen', '--model', expectedLowComplexityModel()]);
319
385
  });
320
386
  it('resolveWorkerLaunchArgsFromEnv reads low-complexity model from config when present', async () => {
321
- const previousCodexHome = process.env.CODEX_HOME;
322
- const tempCodexHome = await mkdtemp(join(tmpdir(), 'omx-codex-home-'));
323
- await writeFile(join(tempCodexHome, '.omx-config.json'), JSON.stringify({ models: { team_low_complexity: 'gpt-4.1-mini' } }));
324
- process.env.CODEX_HOME = tempCodexHome;
325
- try {
326
- const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'explore');
327
- assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-4.1-mini']);
328
- }
329
- finally {
330
- if (typeof previousCodexHome === 'string')
331
- process.env.CODEX_HOME = previousCodexHome;
332
- else
333
- delete process.env.CODEX_HOME;
334
- await rm(tempCodexHome, { recursive: true, force: true });
335
- }
387
+ await withIsolatedDefaultModelEnvAsync(async () => {
388
+ const previousCodexHome = process.env.CODEX_HOME;
389
+ const tempCodexHome = await mkdtemp(join(tmpdir(), 'omx-codex-home-'));
390
+ await writeFile(join(tempCodexHome, '.omx-config.json'), JSON.stringify({ models: { team_low_complexity: 'gpt-4.1-mini' } }));
391
+ process.env.CODEX_HOME = tempCodexHome;
392
+ try {
393
+ const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'explore');
394
+ assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-4.1-mini']);
395
+ }
396
+ finally {
397
+ if (typeof previousCodexHome === 'string')
398
+ process.env.CODEX_HOME = previousCodexHome;
399
+ else
400
+ delete process.env.CODEX_HOME;
401
+ await rm(tempCodexHome, { recursive: true, force: true });
402
+ }
403
+ });
336
404
  });
337
405
  it('resolveWorkerLaunchArgsFromEnv injects the frontier default model for executor workers', () => {
338
- const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor');
339
- assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-5.5']);
406
+ withIsolatedDefaultModelEnv(() => {
407
+ const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor');
408
+ assert.deepEqual(args, ['--no-alt-screen', '--model', 'gpt-5.5']);
409
+ });
340
410
  });
341
411
  it('resolveWorkerLaunchArgsFromEnv uses medium reasoning for executor launch defaults', () => {
342
- const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, resolveAgentReasoningEffort('executor'), 'codex');
343
- assert.deepEqual(args, ['--no-alt-screen', '-c', 'model_reasoning_effort="medium"', '--model', 'gpt-5.5']);
412
+ withIsolatedDefaultModelEnv(() => {
413
+ const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, resolveAgentReasoningEffort('executor'), 'codex');
414
+ assert.deepEqual(args, ['--no-alt-screen', '-c', 'model_reasoning_effort="medium"', '--model', 'gpt-5.5']);
415
+ });
344
416
  });
345
417
  it('resolveWorkerLaunchArgsFromEnv treats *-low aliases as low complexity', () => {
346
418
  const args = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor-low');
@@ -366,10 +438,12 @@ describe('runtime', () => {
366
438
  const originalLog = console.log;
367
439
  console.log = (...args) => { logs.push(args.join(' ')); };
368
440
  try {
369
- const lowArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'low', 'codex');
370
- const highArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
371
- assert.deepEqual(lowArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="low"', '--model', 'gpt-5.5']);
372
- assert.deepEqual(highArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="high"', '--model', 'gpt-5.5']);
441
+ withIsolatedDefaultModelEnv(() => {
442
+ const lowArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'low', 'codex');
443
+ const highArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
444
+ assert.deepEqual(lowArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="low"', '--model', 'gpt-5.5']);
445
+ assert.deepEqual(highArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="high"', '--model', 'gpt-5.5']);
446
+ });
373
447
  }
374
448
  finally {
375
449
  console.log = originalLog;
@@ -451,7 +525,10 @@ describe('runtime', () => {
451
525
  const originalLog = console.log;
452
526
  console.log = (...args) => { logs.push(args.join(' ')); };
453
527
  try {
454
- const codexArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
528
+ let codexArgs = [];
529
+ withIsolatedDefaultModelEnv(() => {
530
+ codexArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen' }, 'executor', undefined, 'high', 'codex');
531
+ });
455
532
  const claudeArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--no-alt-screen --model claude-3-7-sonnet' }, 'executor', undefined, 'low', 'claude');
456
533
  const geminiArgs = resolveWorkerLaunchArgsFromEnv({ OMX_TEAM_WORKER_LAUNCH_ARGS: '--model gemini-2.0-pro' }, 'executor', undefined, 'low', 'gemini');
457
534
  assert.deepEqual(codexArgs, ['--no-alt-screen', '-c', 'model_reasoning_effort="high"', '--model', 'gpt-5.5']);
@@ -589,7 +666,7 @@ describe('runtime', () => {
589
666
  await rm(cwd, { recursive: true, force: true });
590
667
  }
591
668
  });
592
- it('uses a production startup evidence window that can tolerate slow Codex startup', async () => {
669
+ it('uses a production startup evidence window that can tolerate slow Codex startup', { skip: skipSlowLifecycleUnderCoverage }, async () => {
593
670
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-window-'));
594
671
  const prevTmux = process.env.TMUX;
595
672
  const prevTmuxPane = process.env.TMUX_PANE;
@@ -680,22 +757,23 @@ esac
680
757
  delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
681
758
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
682
759
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
760
+ const expectedTeamName = buildInternalTeamName('team-startup-window', resolveTeamIdentityScope(process.env));
683
761
  receiptNotifier = setInterval(() => {
684
- void markPendingInboxDispatchesNotified('team-startup-window', cwd, {
762
+ void markPendingInboxDispatchesNotified(expectedTeamName, cwd, {
685
763
  toWorker: 'worker-1',
686
764
  lastReason: 'test_notified_receipt',
687
765
  }).catch(() => { });
688
766
  }, 20);
689
767
  progressWriter = setTimeout(() => {
690
- void writeWorkerStatus('team-startup-window', 'worker-1', {
768
+ void writeWorkerStatus(expectedTeamName, 'worker-1', {
691
769
  state: 'working',
692
770
  current_task_id: '1',
693
771
  updated_at: new Date().toISOString(),
694
772
  }, cwd).catch(() => { });
695
773
  }, 6_000);
696
774
  const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-window', 'interactive startup should wait for slow Codex evidence', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
697
- assert.equal(runtime.teamName, 'team-startup-window');
698
- assert.ok(await readTeamConfig('team-startup-window', cwd));
775
+ assert.equal(runtime.teamName, expectedTeamName);
776
+ assert.ok(await readTeamConfig(runtime.teamName, cwd));
699
777
  });
700
778
  }
701
779
  finally {
@@ -744,7 +822,7 @@ esac
744
822
  await rm(cwd, { recursive: true, force: true });
745
823
  }
746
824
  });
747
- it('startTeam rejects interactive startup when tmux fallback never produces worker startup evidence', async () => {
825
+ it('startTeam records recoverable issue when tmux fallback never produces worker startup evidence', { skip: skipSlowLifecycleUnderCoverage }, async () => {
748
826
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-no-evidence-'));
749
827
  const prevTmux = process.env.TMUX;
750
828
  const prevTmuxPane = process.env.TMUX_PANE;
@@ -834,24 +912,28 @@ esac
834
912
  process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
835
913
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
836
914
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
915
+ const expectedTeamName = buildInternalTeamName('team-startup-no-evidence', resolveTeamIdentityScope(process.env));
837
916
  receiptFailer = setInterval(() => {
838
917
  void (async () => {
839
- const requests = await listDispatchRequests('team-startup-no-evidence', cwd, { kind: 'inbox' }).catch(() => []);
918
+ const requests = await listDispatchRequests(expectedTeamName, cwd, { kind: 'inbox' }).catch(() => []);
840
919
  for (const request of requests) {
841
920
  if (request.status !== 'pending')
842
921
  continue;
843
- await transitionDispatchRequest('team-startup-no-evidence', request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
922
+ await transitionDispatchRequest(expectedTeamName, request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
844
923
  }
845
924
  })();
846
925
  }, 20);
847
- 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/);
926
+ const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-no-evidence', 'interactive startup records missing worker evidence without aborting live panes', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
848
927
  if (receiptFailer) {
849
928
  clearInterval(receiptFailer);
850
929
  receiptFailer = null;
851
930
  }
852
- assert.equal(await readTeamConfig('team-startup-no-evidence', cwd), null);
931
+ assert.ok(await readTeamConfig(runtime.teamName, cwd));
932
+ const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
933
+ assert.ok(['unknown', 'idle'].includes(workerStatus.state));
853
934
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
854
935
  assert.match(tmuxLog, /send-keys -t %2 -l --/);
936
+ await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
855
937
  });
856
938
  }
857
939
  finally {
@@ -956,7 +1038,8 @@ sleep 5
956
1038
  let runtime = null;
957
1039
  try {
958
1040
  runtime = await startTeam('nested-allowed', 'nested task', 'explore', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd);
959
- assert.equal(runtime.teamName, 'nested-allowed');
1041
+ assert.match(runtime.teamName, /^nested-allowed-[a-f0-9]{8}$/);
1042
+ assert.equal(runtime.config.display_name, 'nested-allowed');
960
1043
  await shutdownTeam(runtime.teamName, cwd, { force: true });
961
1044
  runtime = null;
962
1045
  }
@@ -1008,6 +1091,54 @@ sleep 5
1008
1091
  await rm(cwd, { recursive: true, force: true });
1009
1092
  }
1010
1093
  });
1094
+ it('shutdownTeam with path-like display input cannot remove state outside the team directory', async () => {
1095
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-unsafe-'));
1096
+ try {
1097
+ const victim = join(cwd, '.omx', 'state', 'victim');
1098
+ await mkdir(victim, { recursive: true });
1099
+ await writeFile(join(victim, 'keep.txt'), 'keep');
1100
+ await shutdownTeam('../../victim', cwd, { force: true });
1101
+ assert.equal(existsSync(join(victim, 'keep.txt')), true);
1102
+ }
1103
+ finally {
1104
+ await rm(cwd, { recursive: true, force: true });
1105
+ }
1106
+ });
1107
+ it('startTeam blocks duplicate no-session/no-tmux prompt-mode starts with stable cwd leader identity', async () => {
1108
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-duplicate-nosession-'));
1109
+ const binDir = join(cwd, 'bin');
1110
+ const fakeCodexPath = join(binDir, 'codex');
1111
+ await mkdir(binDir, { recursive: true });
1112
+ await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);
1113
+ process.on('SIGTERM', () => process.exit(0));`);
1114
+ let runtime = null;
1115
+ try {
1116
+ await withPromptModeCodexEnv(binDir, {
1117
+ OMX_SESSION_ID: undefined,
1118
+ CODEX_SESSION_ID: undefined,
1119
+ SESSION_ID: undefined,
1120
+ TMUX_PANE: undefined,
1121
+ }, async () => {
1122
+ runtime = await withoutTeamWorkerEnv(() => startTeam('first-prompt-team', 'first no-session prompt team', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
1123
+ assert.equal(runtime.config.worker_launch_mode, 'prompt');
1124
+ assert.match(runtime.teamName, /^first-prompt-team-[a-f0-9]{8}$/);
1125
+ assert.equal(runtime.config.display_name, 'first-prompt-team');
1126
+ assert.equal(runtime.config.identity_source, 'run-id');
1127
+ const manifest = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'manifest.v2.json'), 'utf-8'));
1128
+ assert.equal(manifest.leader?.session_id, `cwd:${cwd}`);
1129
+ await assert.rejects(() => withoutTeamWorkerEnv(() => startTeam('second-prompt-team', 'second no-session prompt team must be blocked', 'executor', 1, [{ subject: 's2', description: 'd2', owner: 'worker-1' }], cwd)), /leader_session_conflict: active team exists \(first-prompt-team-[a-f0-9]{8}\)/);
1130
+ const teamEntries = await readdir(join(cwd, '.omx', 'state', 'team'), { withFileTypes: true });
1131
+ assert.equal(teamEntries.some((entry) => entry.isDirectory() && entry.name.startsWith('second-prompt-team-')), false, 'blocked duplicate start must not create a second prompt-mode team state directory');
1132
+ });
1133
+ }
1134
+ finally {
1135
+ const runtimeToShutdown = runtime;
1136
+ if (runtimeToShutdown) {
1137
+ await shutdownTeam(runtimeToShutdown.teamName, cwd, { force: true }).catch(() => { });
1138
+ }
1139
+ await rm(cwd, { recursive: true, force: true });
1140
+ }
1141
+ });
1011
1142
  it('startTeam rejects duplicate active same-name team state without mutating existing files', async () => {
1012
1143
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-duplicate-team-'));
1013
1144
  const prevSessionId = process.env.OMX_SESSION_ID;
@@ -1256,8 +1387,9 @@ case "\${1:-}" in
1256
1387
  split-window)
1257
1388
  case "$*" in
1258
1389
  *" -h "*)
1259
- mkdir -p "${cwd}/.omx/state/team/team-interactive-cleanup/workers/worker-1"
1260
- cat > "${cwd}/.omx/state/team/team-interactive-cleanup/workers/worker-1/status.json" <<'EOF'
1390
+ team_dir=$(find "${cwd}/.omx/state/team" -maxdepth 1 -type d -name 'team-interactive*' | head -n 1)
1391
+ mkdir -p "$team_dir/workers/worker-1"
1392
+ cat > "$team_dir/workers/worker-1/status.json" <<'EOF'
1261
1393
  {
1262
1394
  "state": "working",
1263
1395
  "current_task_id": "1",
@@ -1382,8 +1514,9 @@ case "\${1:-}" in
1382
1514
  split-window)
1383
1515
  case "$*" in
1384
1516
  *" -h "*)
1385
- mkdir -p "${cwd}/.omx/state/team/team-pane-pid/workers/worker-1"
1386
- cat > "${cwd}/.omx/state/team/team-pane-pid/workers/worker-1/status.json" <<'EOF'
1517
+ team_dir=$(find "${cwd}/.omx/state/team" -maxdepth 1 -type d -name 'team-pane-pid*' | head -n 1)
1518
+ mkdir -p "$team_dir/workers/worker-1"
1519
+ cat > "$team_dir/workers/worker-1/status.json" <<'EOF'
1387
1520
  {
1388
1521
  "state": "working",
1389
1522
  "current_task_id": "1",
@@ -1464,6 +1597,253 @@ esac
1464
1597
  assert.equal(applyIndex < saveIndex, true);
1465
1598
  assert.equal(saveIndex < readyIndex, true);
1466
1599
  });
1600
+ it('startTeam sends startup direct trigger before slow readiness wait when pane is safe', async () => {
1601
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-startup-direct-fast-'));
1602
+ const previousTmux = process.env.TMUX;
1603
+ const previousTmuxPane = process.env.TMUX_PANE;
1604
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1605
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1606
+ const previousReadyTimeout = process.env.OMX_TEAM_READY_TIMEOUT_MS;
1607
+ const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1608
+ const previousStartupDispatchRetries = process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
1609
+ let runtimeTeamName = null;
1610
+ try {
1611
+ await withMockTmuxFixture({
1612
+ dirPrefix: 'omx-runtime-startup-direct-fast-bin-',
1613
+ tmuxScript: () => `#!/bin/sh
1614
+ set -eu
1615
+ order_file="${cwd}/startup-order.log"
1616
+ case "$1" in
1617
+ -V)
1618
+ echo "tmux 3.4"
1619
+ exit 0
1620
+ ;;
1621
+ display-message)
1622
+ case "$*" in
1623
+ *"#{window_width}"*) echo "120" ;;
1624
+ *) echo "leader:0 %1" ;;
1625
+ esac
1626
+ exit 0
1627
+ ;;
1628
+ list-panes)
1629
+ case "$*" in
1630
+ *"pane_current_command"*) printf "%%1\tnode\t'codex'\n" ;;
1631
+ *"#{pane_dead} #{pane_pid}"*) echo "0 4242" ;;
1632
+ *"#{pane_dead}"*) echo "0" ;;
1633
+ *"#{pane_pid}"*) echo "4242" ;;
1634
+ *) exit 0 ;;
1635
+ esac
1636
+ exit 0
1637
+ ;;
1638
+ capture-pane)
1639
+ printf '%s\n' capture >> "$order_file"
1640
+ printf 'OpenAI Codex\nmodel: test\ndirectory: /tmp/demo\n'
1641
+ exit 0
1642
+ ;;
1643
+ send-keys)
1644
+ printf '%s\n' send-keys >> "$order_file"
1645
+ exit 0
1646
+ ;;
1647
+ split-window)
1648
+ echo "%2"
1649
+ exit 0
1650
+ ;;
1651
+ set-hook|run-shell|select-layout|set-window-option|select-pane|kill-pane|kill-session|resize-pane)
1652
+ exit 0
1653
+ ;;
1654
+ *)
1655
+ exit 0
1656
+ ;;
1657
+ esac
1658
+ `,
1659
+ binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1660
+ }, async () => {
1661
+ delete process.env.TMUX;
1662
+ process.env.TMUX_PANE = '%1';
1663
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1664
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
1665
+ process.env.OMX_TEAM_READY_TIMEOUT_MS = '5000';
1666
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '50';
1667
+ process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
1668
+ const runtime = await withoutTeamWorkerEnv(() => startTeam('team-startup-direct-fast', 'startup direct trigger falls back to evidence-gated dispatch', 'executor', 1, [{ subject: 'w1', description: 'worker one', owner: 'worker-1' }], cwd));
1669
+ runtimeTeamName = runtime.teamName;
1670
+ const order = (await readFile(join(cwd, 'startup-order.log'), 'utf-8')).trim().split('\n');
1671
+ assert.ok(order.includes('send-keys'), `expected direct send-keys, got ${order.join(',')}`);
1672
+ const timing = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'startup-timing.json'), 'utf-8'));
1673
+ assert.ok(timing.events.some((event) => event.phase === 'split_returned'));
1674
+ assert.ok(timing.events.some((event) => event.phase === 'identity_inbox_written'));
1675
+ assert.ok(timing.events.some((event) => event.phase === 'direct_fallback' && /startup_direct_trigger_sent/.test(event.reason ?? '')));
1676
+ assert.ok(timing.events.some((event) => event.phase === 'startup_evidence' && event.reason === 'none' && event.ok === false));
1677
+ assert.equal(timing.events.some((event) => event.phase === 'ready_wait_start'), false);
1678
+ const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
1679
+ assert.equal(workerStatus?.state, 'unknown');
1680
+ assert.match(workerStatus?.reason ?? '', /startup_direct_no_evidence/);
1681
+ });
1682
+ }
1683
+ finally {
1684
+ if (runtimeTeamName)
1685
+ await shutdownTeam(runtimeTeamName, cwd, { force: true }).catch(() => { });
1686
+ if (typeof previousTmux === 'string')
1687
+ process.env.TMUX = previousTmux;
1688
+ else
1689
+ delete process.env.TMUX;
1690
+ if (typeof previousTmuxPane === 'string')
1691
+ process.env.TMUX_PANE = previousTmuxPane;
1692
+ else
1693
+ delete process.env.TMUX_PANE;
1694
+ if (typeof previousLaunchMode === 'string')
1695
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
1696
+ else
1697
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1698
+ if (typeof previousWorkerCli === 'string')
1699
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
1700
+ else
1701
+ delete process.env.OMX_TEAM_WORKER_CLI;
1702
+ if (typeof previousReadyTimeout === 'string')
1703
+ process.env.OMX_TEAM_READY_TIMEOUT_MS = previousReadyTimeout;
1704
+ else
1705
+ delete process.env.OMX_TEAM_READY_TIMEOUT_MS;
1706
+ if (typeof previousStartupEvidenceTimeout === 'string')
1707
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
1708
+ else
1709
+ delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1710
+ if (typeof previousStartupDispatchRetries === 'string')
1711
+ process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = previousStartupDispatchRetries;
1712
+ else
1713
+ delete process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
1714
+ await rm(cwd, { recursive: true, force: true });
1715
+ }
1716
+ });
1717
+ it('startTeam treats a confirmed ready prompt as startup evidence after hook notification', async () => {
1718
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-ready-prompt-evidence-'));
1719
+ const previousTmux = process.env.TMUX;
1720
+ const previousTmuxPane = process.env.TMUX_PANE;
1721
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1722
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1723
+ const previousReadyTimeout = process.env.OMX_TEAM_READY_TIMEOUT_MS;
1724
+ const previousStartupEvidenceTimeout = process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1725
+ const previousStartupDispatchRetries = process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
1726
+ let receiptNotifier = null;
1727
+ let runtimeTeamName = null;
1728
+ try {
1729
+ await withMockTmuxFixture({
1730
+ dirPrefix: 'omx-runtime-ready-prompt-evidence-bin-',
1731
+ tmuxScript: () => `#!/bin/sh
1732
+ set -eu
1733
+ count_file="${cwd}/capture-count"
1734
+ case "$1" in
1735
+ -V)
1736
+ echo "tmux 3.4"
1737
+ exit 0
1738
+ ;;
1739
+ display-message)
1740
+ case "$*" in
1741
+ *"#{window_width}"*) echo "120" ;;
1742
+ *) echo "leader:0 %1" ;;
1743
+ esac
1744
+ exit 0
1745
+ ;;
1746
+ list-panes)
1747
+ case "$*" in
1748
+ *"pane_current_command"*) printf "%%1\tnode\t'codex'\n" ;;
1749
+ *"#{pane_dead} #{pane_pid}"*) echo "0 4242" ;;
1750
+ *"-t %2"*"#{pane_pid}"*) echo "4242" ;;
1751
+ *"#{pane_dead}"*) echo "0" ;;
1752
+ *"#{pane_pid}"*) echo "4242" ;;
1753
+ *) exit 0 ;;
1754
+ esac
1755
+ exit 0
1756
+ ;;
1757
+ capture-pane)
1758
+ count=0
1759
+ if [ -f "$count_file" ]; then count=$(cat "$count_file"); fi
1760
+ count=$((count + 1))
1761
+ printf '%s' "$count" > "$count_file"
1762
+ if [ "$count" -eq 1 ]; then
1763
+ printf 'OpenAI Codex\nmodel: loading\nLoading workspace...\n'
1764
+ else
1765
+ printf 'OpenAI Codex\nmodel: test\n› \n'
1766
+ fi
1767
+ exit 0
1768
+ ;;
1769
+ split-window)
1770
+ echo "%2"
1771
+ exit 0
1772
+ ;;
1773
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session|resize-pane)
1774
+ exit 0
1775
+ ;;
1776
+ *)
1777
+ exit 0
1778
+ ;;
1779
+ esac
1780
+ `,
1781
+ binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1782
+ }, async () => {
1783
+ delete process.env.TMUX;
1784
+ process.env.TMUX_PANE = '%1';
1785
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1786
+ process.env.OMX_TEAM_WORKER_CLI = 'codex';
1787
+ process.env.OMX_TEAM_READY_TIMEOUT_MS = '5000';
1788
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
1789
+ process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
1790
+ receiptNotifier = setInterval(() => {
1791
+ void markPendingInboxDispatchesNotified('team-ready-prompt-evidence', cwd);
1792
+ }, 20);
1793
+ const runtime = await withoutTeamWorkerEnv(() => startTeam('team-ready-prompt-evidence', 'interactive ready prompt should settle startup evidence after notification', 'executor', 1, [{ subject: 'w1', description: 'worker one', owner: 'worker-1' }], cwd));
1794
+ runtimeTeamName = runtime.teamName;
1795
+ const workerStatus = await readWorkerStatus(runtime.teamName, 'worker-1', cwd);
1796
+ assert.equal(workerStatus.state, 'unknown');
1797
+ assert.equal(workerStatus.reason, undefined);
1798
+ const requests = await listDispatchRequests(runtime.teamName, cwd, { kind: 'inbox' });
1799
+ assert.equal(requests.at(-1)?.status, 'notified');
1800
+ const captureCount = Number.parseInt(await readFile(join(cwd, 'capture-count'), 'utf-8'), 10);
1801
+ assert.ok(captureCount >= 2, `expected ready wait capture after bootstrapping, got ${captureCount}`);
1802
+ const timing = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'startup-timing.json'), 'utf-8'));
1803
+ assert.ok(timing.events.some((event) => event.phase === 'ready_wait_start'));
1804
+ assert.ok(timing.events.some((event) => event.phase === 'ready_wait_end' && event.ok === true));
1805
+ });
1806
+ }
1807
+ finally {
1808
+ if (receiptNotifier)
1809
+ clearInterval(receiptNotifier);
1810
+ if (runtimeTeamName)
1811
+ await shutdownTeam(runtimeTeamName, cwd, { force: true }).catch(() => { });
1812
+ if (typeof previousTmux === 'string')
1813
+ process.env.TMUX = previousTmux;
1814
+ else
1815
+ delete process.env.TMUX;
1816
+ if (typeof previousTmuxPane === 'string')
1817
+ process.env.TMUX_PANE = previousTmuxPane;
1818
+ else
1819
+ delete process.env.TMUX_PANE;
1820
+ if (typeof previousLaunchMode === 'string')
1821
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
1822
+ else
1823
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1824
+ if (typeof previousWorkerCli === 'string')
1825
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
1826
+ else
1827
+ delete process.env.OMX_TEAM_WORKER_CLI;
1828
+ if (typeof previousReadyTimeout === 'string')
1829
+ process.env.OMX_TEAM_READY_TIMEOUT_MS = previousReadyTimeout;
1830
+ else
1831
+ delete process.env.OMX_TEAM_READY_TIMEOUT_MS;
1832
+ if (typeof previousStartupEvidenceTimeout === 'string') {
1833
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = previousStartupEvidenceTimeout;
1834
+ }
1835
+ else {
1836
+ delete process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS;
1837
+ }
1838
+ if (typeof previousStartupDispatchRetries === 'string') {
1839
+ process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = previousStartupDispatchRetries;
1840
+ }
1841
+ else {
1842
+ delete process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES;
1843
+ }
1844
+ await rm(cwd, { recursive: true, force: true });
1845
+ }
1846
+ });
1467
1847
  it('startTeam starts worker-2 readiness before delayed worker-1 readiness settles', async () => {
1468
1848
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-parallel-ready-'));
1469
1849
  const previousTmux = process.env.TMUX;
@@ -1606,7 +1986,7 @@ esac
1606
1986
  await rm(cwd, { recursive: true, force: true });
1607
1987
  }
1608
1988
  });
1609
- it('startTeam records recoverable startup issues per worker instead of failing launch early when panes stay alive', async () => {
1989
+ it('startTeam records recoverable startup issues per worker instead of failing launch early when panes stay alive', { skip: skipSlowLifecycleUnderCoverage }, async () => {
1610
1990
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-no-startup-evidence-'));
1611
1991
  const previousTmux = process.env.TMUX;
1612
1992
  const previousTmuxPane = process.env.TMUX_PANE;
@@ -1704,7 +2084,7 @@ process.on('SIGTERM', () => process.exit(0));
1704
2084
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
1705
2085
  receiptFailer = setInterval(() => {
1706
2086
  void (async () => {
1707
- const requests = await listDispatchRequests(teamName, cwd, { kind: 'inbox' }).catch(() => []);
2087
+ const requests = await listDispatchRequests(await resolveRuntimeTeamName(cwd, teamName), cwd, { kind: 'inbox' }).catch(() => []);
1708
2088
  for (const request of requests) {
1709
2089
  if (request.status !== 'pending')
1710
2090
  continue;
@@ -1716,14 +2096,15 @@ process.on('SIGTERM', () => process.exit(0));
1716
2096
  { subject: 'worker-1 task', description: 'd', owner: 'worker-1' },
1717
2097
  { subject: 'worker-2 task', description: 'd', owner: 'worker-2' },
1718
2098
  ], cwd));
1719
- const worker1Status = await readWorkerStatus(teamName, 'worker-1', cwd);
1720
- const worker2Status = await readWorkerStatus(teamName, 'worker-2', cwd);
2099
+ const runtimeTeamName = runtime.teamName;
2100
+ const worker1Status = await readWorkerStatus(runtimeTeamName, 'worker-1', cwd);
2101
+ const worker2Status = await readWorkerStatus(runtimeTeamName, 'worker-2', cwd);
1721
2102
  assert.equal(worker1Status.state, 'unknown');
1722
2103
  assert.equal(worker2Status.state, 'unknown');
1723
2104
  assert.match(worker1Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
1724
2105
  assert.match(worker2Status.reason ?? '', /startup_no_evidence|fallback_attempted_but_unconfirmed/);
1725
- const task1 = await readTask(teamName, '1', cwd);
1726
- const task2 = await readTask(teamName, '2', cwd);
2106
+ const task1 = await readTask(runtimeTeamName, '1', cwd);
2107
+ const task2 = await readTask(runtimeTeamName, '2', cwd);
1727
2108
  assert.equal(task1?.status, 'pending');
1728
2109
  assert.equal(task2?.status, 'pending');
1729
2110
  });
@@ -1775,7 +2156,7 @@ process.on('SIGTERM', () => process.exit(0));
1775
2156
  await rm(cwd, { recursive: true, force: true });
1776
2157
  }
1777
2158
  });
1778
- it('startTeam attempts worker-2 before rejecting lowest-index unrecoverable startup failure', async () => {
2159
+ it('startTeam attempts worker-2 before rejecting lowest-index unrecoverable startup failure', { skip: skipSlowLifecycleUnderCoverage }, async () => {
1779
2160
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-parallel-dead-pane-'));
1780
2161
  const previousTmux = process.env.TMUX;
1781
2162
  const previousTmuxPane = process.env.TMUX_PANE;
@@ -2052,7 +2433,7 @@ esac
2052
2433
  await rm(cwd, { recursive: true, force: true });
2053
2434
  }
2054
2435
  });
2055
- it('startTeam materializes all worker identity/inbox files before worker-1 startup evidence can block later workers', async () => {
2436
+ it('startTeam materializes all worker identity/inbox files before worker-1 startup evidence can block later workers', { skip: skipSlowLifecycleUnderCoverage }, async () => {
2056
2437
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-materialize-before-evidence-'));
2057
2438
  const previousTmux = process.env.TMUX;
2058
2439
  const previousTmuxPane = process.env.TMUX_PANE;
@@ -2149,7 +2530,7 @@ process.on('SIGTERM', () => process.exit(0));
2149
2530
  },
2150
2531
  ],
2151
2532
  }, async () => {
2152
- const sanitizedTeamName = sanitizeTeamName('team-materialize-before-evidence');
2533
+ let runtimeTeamName = sanitizeTeamName('team-materialize-before-evidence');
2153
2534
  delete process.env.TMUX;
2154
2535
  process.env.TMUX_PANE = '%1';
2155
2536
  process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
@@ -2160,11 +2541,13 @@ process.on('SIGTERM', () => process.exit(0));
2160
2541
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRY_DELAY_MS = '50';
2161
2542
  receiptFailer = setInterval(() => {
2162
2543
  void (async () => {
2163
- const requests = await listDispatchRequests('team-materialize-before-evidence', cwd, { kind: 'inbox' }).catch(() => []);
2544
+ const activeTeamName = await resolveRuntimeTeamName(cwd, 'team-materialize-before-evidence');
2545
+ runtimeTeamName = activeTeamName;
2546
+ const requests = await listDispatchRequests(activeTeamName, cwd, { kind: 'inbox' }).catch(() => []);
2164
2547
  for (const request of requests) {
2165
2548
  if (request.status !== 'pending')
2166
2549
  continue;
2167
- await transitionDispatchRequest('team-materialize-before-evidence', request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
2550
+ await transitionDispatchRequest(activeTeamName, request.request_id, 'pending', 'failed', { last_reason: 'test_failed_receipt' }, cwd).catch(() => { });
2168
2551
  }
2169
2552
  })();
2170
2553
  }, 20);
@@ -2173,11 +2556,12 @@ process.on('SIGTERM', () => process.exit(0));
2173
2556
  { subject: 'w2', description: 'worker two', owner: 'worker-2' },
2174
2557
  ], cwd));
2175
2558
  const observedTeamPromise = teamPromise.then((runtime) => ({ ok: true, runtime }), (error) => ({ ok: false, error }));
2176
- const workerOneIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-1', 'identity.json');
2177
- const workerTwoIdentity = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'identity.json');
2178
- const workerTwoInbox = join(cwd, '.omx', 'state', 'team', sanitizedTeamName, 'workers', 'worker-2', 'inbox.md');
2179
2559
  let materializedAllWorkers = false;
2180
2560
  for (let attempt = 0; attempt < 200; attempt += 1) {
2561
+ runtimeTeamName = await resolveRuntimeTeamName(cwd, 'team-materialize-before-evidence');
2562
+ const workerOneIdentity = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-1', 'identity.json');
2563
+ const workerTwoIdentity = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-2', 'identity.json');
2564
+ const workerTwoInbox = join(cwd, '.omx', 'state', 'team', runtimeTeamName, 'workers', 'worker-2', 'inbox.md');
2181
2565
  if (existsSync(workerOneIdentity)
2182
2566
  && existsSync(workerTwoIdentity)
2183
2567
  && existsSync(workerTwoInbox)) {
@@ -2190,7 +2574,7 @@ process.on('SIGTERM', () => process.exit(0));
2190
2574
  const outcome = await observedTeamPromise;
2191
2575
  assert.equal(outcome.ok, false);
2192
2576
  assert.match(String(outcome.error), /worker_notify_failed:worker-1/);
2193
- assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', sanitizedTeamName)), false);
2577
+ assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', runtimeTeamName)), false);
2194
2578
  });
2195
2579
  }
2196
2580
  finally {
@@ -2361,7 +2745,7 @@ sleep 5
2361
2745
  assert.equal((runtime.config.workers[0]?.pid ?? 0) > 0, true);
2362
2746
  const expectedArgv = [
2363
2747
  '-i',
2364
- '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.',
2748
+ `Read .omx/state/team/${runtime.teamName}/workers/worker-1/inbox.md, start work now, report concrete progress, then continue assigned work or next feasible task.`,
2365
2749
  ];
2366
2750
  let argv = null;
2367
2751
  for (let attempt = 0; attempt < 50; attempt += 1) {
@@ -2491,10 +2875,10 @@ process.on('SIGTERM', () => process.exit(0));
2491
2875
  delete process.env.OMX_DEFAULT_STANDARD_MODEL;
2492
2876
  let runtime = null;
2493
2877
  try {
2494
- runtime = await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
2878
+ runtime = await withIsolatedDefaultModelEnvAsync(async () => await withMockPromptModeCodexAllowed(() => withoutTeamWorkerEnv(() => startTeam('team-role-routing', 'heuristic routing handoff', 'executor', 2, [
2495
2879
  { subject: 'test routing report only', description: 'test routing report only', owner: 'worker-1', role: 'test-engineer' },
2496
2880
  { subject: 'document routing report only', description: 'document routing report only', owner: 'worker-2', role: 'writer' },
2497
- ], cwd)));
2881
+ ], cwd))));
2498
2882
  assert.equal(runtime.config.worker_launch_mode, 'prompt');
2499
2883
  assert.equal(runtime.config.workers[0]?.role, 'test-engineer');
2500
2884
  assert.equal(runtime.config.workers[1]?.role, 'writer');
@@ -2872,20 +3256,20 @@ process.on('SIGTERM', () => process.exit(0));
2872
3256
  assert.notEqual(workerPath, repo);
2873
3257
  const workerAgents = await readFile(join(workerPath, 'AGENTS.md'), 'utf-8');
2874
3258
  assert.match(workerAgents, /Team Worker Runtime Instructions/);
2875
- assert.match(workerAgents, /team-detached-worktree-paths/);
3259
+ assert.match(workerAgents, new RegExp(runtime.teamName));
2876
3260
  const startupLog = await waitForFileText(stdinLogPath, (content) => content.includes('/workers/worker-1/inbox.md'));
2877
- assert.match(startupLog, /\$OMX_TEAM_STATE_ROOT\/team\/team-detached-worktree-paths\/workers\/worker-1\/inbox\.md/);
2878
- assert.doesNotMatch(startupLog, /Read \.omx\/state\/team\/team-detached-worktree-paths\/workers\/worker-1\/inbox\.md/);
3261
+ assert.match(startupLog, new RegExp(`\\$OMX_TEAM_STATE_ROOT/team/${runtime.teamName}/workers/worker-1/inbox\\.md`));
3262
+ assert.doesNotMatch(startupLog, new RegExp(`Read \\.omx/state/team/${runtime.teamName}/workers/worker-1/inbox\\.md`));
2879
3263
  const envLog = JSON.parse(await waitForFileText(envLogPath, (content) => content.includes('teamStateRoot')));
2880
3264
  assert.equal(envLog.cwd, workerPath);
2881
3265
  assert.equal(envLog.teamStateRoot, join(repo, '.omx', 'state'));
2882
3266
  assert.equal(envLog.worker, 'team-detached-worktree-paths/worker-1');
2883
3267
  const rootAgents = await readFile(join(workerPath, 'AGENTS.md'), 'utf-8');
2884
3268
  assert.match(rootAgents, /Team Worker Runtime Instructions/);
2885
- assert.match(rootAgents, /Inbox path: .*team-detached-worktree-paths\/workers\/worker-1\/inbox\.md/);
3269
+ assert.match(rootAgents, new RegExp(`Inbox path: .*${runtime.teamName}/workers/worker-1/inbox\\.md`));
2886
3270
  await sendWorkerMessage(runtime.teamName, 'leader-fixed', 'worker-1', 'follow-up', repo);
2887
3271
  const mailboxLog = await waitForFileText(stdinLogPath, (content) => content.includes('/mailbox/worker-1.json'));
2888
- assert.match(mailboxLog, /\$OMX_TEAM_STATE_ROOT\/team\/team-detached-worktree-paths\/mailbox\/worker-1\.json/);
3272
+ assert.match(mailboxLog, new RegExp(`\\$OMX_TEAM_STATE_ROOT/team/${runtime.teamName}/mailbox/worker-1\\.json`));
2889
3273
  await shutdownTeam(runtime.teamName, repo, { force: true });
2890
3274
  runtime = null;
2891
3275
  }
@@ -4717,6 +5101,55 @@ esac
4717
5101
  await rm(cwd, { recursive: true, force: true });
4718
5102
  }
4719
5103
  });
5104
+ it('resumeTeam fails closed when the persisted approved binding is stale', async () => {
5105
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-resume-'));
5106
+ try {
5107
+ await initTeamState('team-approved-resume', 'approved resume test', 'executor', 1, cwd);
5108
+ await writePersistedApprovedTeamExecutionBinding('team-approved-resume', cwd, {
5109
+ prd_path: join(cwd, '.omx', 'plans', 'prd-missing.md'),
5110
+ task: 'Execute missing approved plan',
5111
+ });
5112
+ await assert.rejects(() => resumeTeam('team-approved-resume', cwd), /approved_execution_binding_stale:.*Execute missing approved plan/);
5113
+ }
5114
+ finally {
5115
+ await rm(cwd, { recursive: true, force: true });
5116
+ }
5117
+ });
5118
+ it('resumeTeam resolves approved binding continuity against the persisted leader cwd', async () => {
5119
+ const teamName = 'team-approved-shared-root';
5120
+ const leaderCwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-leader-'));
5121
+ const resumeCwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-resume-alt-'));
5122
+ const sharedStateRoot = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-state-'));
5123
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
5124
+ process.env.OMX_TEAM_STATE_ROOT = sharedStateRoot;
5125
+ try {
5126
+ await initTeamState(teamName, 'approved resume shared-root test', 'executor', 1, leaderCwd, DEFAULT_MAX_WORKERS, process.env, {
5127
+ leader_cwd: leaderCwd,
5128
+ team_state_root: sharedStateRoot,
5129
+ });
5130
+ const plansDir = join(leaderCwd, '.omx', 'plans');
5131
+ await mkdir(plansDir, { recursive: true });
5132
+ const prdPath = join(plansDir, 'prd-issue-2110.md');
5133
+ await writeFile(prdPath, '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 2110 plan"\n');
5134
+ await writeFile(join(plansDir, 'test-spec-issue-2110.md'), '# Test spec\n');
5135
+ await writePersistedApprovedTeamExecutionBinding(teamName, leaderCwd, {
5136
+ prd_path: prdPath,
5137
+ task: 'Execute approved issue 2110 plan',
5138
+ command: 'omx team 1:executor "Execute approved issue 2110 plan"',
5139
+ }, sharedStateRoot);
5140
+ const resumed = await resumeTeam(teamName, resumeCwd);
5141
+ assert.equal(resumed, null);
5142
+ }
5143
+ finally {
5144
+ if (typeof previousTeamStateRoot === 'string')
5145
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
5146
+ else
5147
+ delete process.env.OMX_TEAM_STATE_ROOT;
5148
+ await rm(leaderCwd, { recursive: true, force: true });
5149
+ await rm(resumeCwd, { recursive: true, force: true });
5150
+ await rm(sharedStateRoot, { recursive: true, force: true });
5151
+ }
5152
+ });
4720
5153
  it('resumeTeam returns null for prompt teams when worker handles are missing after restart', async () => {
4721
5154
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-resume-'));
4722
5155
  const sleeper = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], {
@@ -4861,7 +5294,7 @@ esac
4861
5294
  let runtime = null;
4862
5295
  try {
4863
5296
  runtime = await withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-delegation-persist', 'delegation persistence test', 'executor', 1, [{ subject: 'Investigate runtime assignment', description: 'Search runtime and debug assignTask behavior' }], cwd)));
4864
- const task = await readTask('team-delegation-persist', '1', cwd);
5297
+ const task = await readTask(runtime.teamName, '1', cwd);
4865
5298
  assert.equal(task?.delegation?.mode, 'auto');
4866
5299
  assert.equal(task?.delegation?.child_model, 'gpt-5.4-mini');
4867
5300
  assert.equal(task?.delegation?.required_parallel_probe, true);
@@ -4873,6 +5306,64 @@ esac
4873
5306
  await rm(cwd, { recursive: true, force: true });
4874
5307
  }
4875
5308
  });
5309
+ it('startTeam persists approved execution binding under the team state root', async () => {
5310
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-binding-'));
5311
+ const binDir = join(cwd, 'bin');
5312
+ const fakeCodexPath = join(binDir, 'codex');
5313
+ await mkdir(binDir, { recursive: true });
5314
+ await mkdir(join(cwd, '.omx', 'plans'), { recursive: true });
5315
+ await writeFile(join(cwd, '.omx', 'plans', 'prd-issue-1314.md'), '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 1314 plan"\n');
5316
+ await writeFile(join(cwd, '.omx', 'plans', 'test-spec-issue-1314.md'), '# Test spec\n');
5317
+ await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);`);
5318
+ let runtime = null;
5319
+ try {
5320
+ runtime = await withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-approved-binding', 'approved binding persistence test', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd, {
5321
+ approvedExecution: {
5322
+ prd_path: join(cwd, '.omx', 'plans', 'prd-issue-1314.md'),
5323
+ task: 'Execute approved issue 1314 plan',
5324
+ command: 'omx team 1:executor "Execute approved issue 1314 plan"',
5325
+ },
5326
+ })));
5327
+ const bindingPath = join(runtime.config.team_state_root ?? join(cwd, '.omx', 'state'), 'team', runtime.teamName, 'approved-execution.json');
5328
+ const binding = JSON.parse(await readFile(bindingPath, 'utf-8'));
5329
+ assert.deepEqual(binding, {
5330
+ prd_path: join(cwd, '.omx', 'plans', 'prd-issue-1314.md'),
5331
+ task: 'Execute approved issue 1314 plan',
5332
+ command: 'omx team 1:executor "Execute approved issue 1314 plan"',
5333
+ });
5334
+ assert.deepEqual(Object.keys(binding).sort(), ['command', 'prd_path', 'task']);
5335
+ }
5336
+ finally {
5337
+ if (runtime) {
5338
+ await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
5339
+ }
5340
+ await rm(cwd, { recursive: true, force: true });
5341
+ }
5342
+ });
5343
+ it('startTeam fails closed when an explicit approved execution binding is stale', async () => {
5344
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-binding-stale-'));
5345
+ const binDir = join(cwd, 'bin');
5346
+ const fakeCodexPath = join(binDir, 'codex');
5347
+ await mkdir(binDir, { recursive: true });
5348
+ await mkdir(join(cwd, '.omx', 'plans'), { recursive: true });
5349
+ const stalePrdPath = join(cwd, '.omx', 'plans', 'prd-issue-1315.md');
5350
+ await writeFile(stalePrdPath, '# Approved plan\n\nLaunch via omx team 1:executor "Execute approved issue 1315 plan"\n');
5351
+ await writeFile(join(cwd, '.omx', 'plans', 'test-spec-issue-1315.md'), '# Test spec\n');
5352
+ await rm(stalePrdPath, { force: true });
5353
+ await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);`);
5354
+ try {
5355
+ await assert.rejects(() => withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-approved-binding-stale', 'approved binding stale start test', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd, {
5356
+ approvedExecution: {
5357
+ prd_path: stalePrdPath,
5358
+ task: 'Execute approved issue 1315 plan',
5359
+ },
5360
+ }))), /approved_execution_binding_stale:.*Execute approved issue 1315 plan/);
5361
+ assert.equal(existsSync(join(cwd, '.omx', 'state', 'team', 'team-approved-binding-stale')), false);
5362
+ }
5363
+ finally {
5364
+ await rm(cwd, { recursive: true, force: true });
5365
+ }
5366
+ });
4876
5367
  it('startTeam remaps repo-aware DAG dependencies after concrete task IDs are created', async () => {
4877
5368
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-'));
4878
5369
  const binDir = join(cwd, 'bin');
@@ -4921,17 +5412,17 @@ esac
4921
5412
  },
4922
5413
  },
4923
5414
  })));
4924
- const first = await readTask('team-dag-remap', '1', cwd);
4925
- const second = await readTask('team-dag-remap', '2', cwd);
5415
+ const first = await readTask(runtime.teamName, '1', cwd);
5416
+ const second = await readTask(runtime.teamName, '2', cwd);
4926
5417
  assert.deepEqual(first?.depends_on, []);
4927
5418
  assert.deepEqual(first?.blocked_by, undefined);
4928
5419
  assert.deepEqual(second?.depends_on, ['1']);
4929
5420
  assert.deepEqual(second?.blocked_by, ['1']);
4930
- const report = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', 'team-dag-remap', 'decomposition-report.json'), 'utf-8'));
5421
+ const report = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'decomposition-report.json'), 'utf-8'));
4931
5422
  assert.deepEqual(report.node_id_to_task_id, { impl: '1', verify: '2' });
4932
5423
  assert.deepEqual(report.task_hints?.['2']?.depends_on, ['1']);
4933
5424
  assert.deepEqual(report.task_hints?.['2']?.symbolic_depends_on, ['impl']);
4934
- const inbox = await readFile(join(cwd, '.omx', 'state', 'team', 'team-dag-remap', 'workers', 'worker-2', 'inbox.md'), 'utf-8');
5425
+ const inbox = await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'workers', 'worker-2', 'inbox.md'), 'utf-8');
4935
5426
  assert.match(inbox, /Blocked by: 1/);
4936
5427
  assert.doesNotMatch(inbox, /Blocked by: impl/);
4937
5428
  assert.doesNotMatch(inbox, /Depends on: impl/);