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
package/Cargo.lock CHANGED
@@ -20,9 +20,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
20
20
 
21
21
  [[package]]
22
22
  name = "libc"
23
- version = "0.2.183"
23
+ version = "0.2.186"
24
24
  source = "registry+https://github.com/rust-lang/crates.io-index"
25
- checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
25
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
26
26
 
27
27
  [[package]]
28
28
  name = "memchr"
@@ -32,11 +32,14 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
32
32
 
33
33
  [[package]]
34
34
  name = "omx-explore-harness"
35
- version = "0.15.2"
35
+ version = "0.16.0"
36
+ dependencies = [
37
+ "libc",
38
+ ]
36
39
 
37
40
  [[package]]
38
41
  name = "omx-mux"
39
- version = "0.15.2"
42
+ version = "0.16.0"
40
43
  dependencies = [
41
44
  "serde",
42
45
  "serde_json",
@@ -44,7 +47,7 @@ dependencies = [
44
47
 
45
48
  [[package]]
46
49
  name = "omx-runtime"
47
- version = "0.15.2"
50
+ version = "0.16.0"
48
51
  dependencies = [
49
52
  "omx-mux",
50
53
  "omx-runtime-core",
@@ -53,7 +56,7 @@ dependencies = [
53
56
 
54
57
  [[package]]
55
58
  name = "omx-runtime-core"
56
- version = "0.15.2"
59
+ version = "0.16.0"
57
60
  dependencies = [
58
61
  "fs2",
59
62
  "serde",
@@ -62,7 +65,7 @@ dependencies = [
62
65
 
63
66
  [[package]]
64
67
  name = "omx-sparkshell"
65
- version = "0.15.2"
68
+ version = "0.16.0"
66
69
  dependencies = [
67
70
  "omx-mux",
68
71
  ]
package/Cargo.toml CHANGED
@@ -10,7 +10,7 @@ resolver = "2"
10
10
 
11
11
  [workspace.package]
12
12
 
13
- version = "0.15.2"
13
+ version = "0.16.0"
14
14
 
15
15
  edition = "2021"
16
16
  rust-version = "1.73"
package/README.md CHANGED
@@ -74,6 +74,7 @@ $deep-interview "clarify the authentication change"
74
74
  $ralplan "approve the auth plan and review tradeoffs"
75
75
  $ralph "carry the approved plan to completion"
76
76
  $team 3:executor "execute the approved plan in parallel"
77
+ $ultragoal "turn this launch into durable Codex goals"
77
78
  ```
78
79
 
79
80
  That is the main path.
@@ -84,6 +85,7 @@ Start OMX strongly, clarify first when needed, approve the plan, then choose `$t
84
85
 
85
86
  Use OMX if you already like Codex and want a better day-to-day runtime around it:
86
87
  - a standard workflow built around `$deep-interview`, `$ralplan`, `$team`, and `$ralph`
88
+ - durable multi-goal handoffs with `$ultragoal` and `.omx/ultragoal` artifacts when a launch needs sequential Codex goals
87
89
  - specialist roles and supporting skills when the task needs them
88
90
  - project guidance through scoped `AGENTS.md`
89
91
  - durable state under `.omx/` for plans, logs, memory, and mode tracking
@@ -323,6 +325,7 @@ If this happens, try:
323
325
  - [Agent catalog](./docs/agents.html)
324
326
  - [Skills reference](./docs/skills.html)
325
327
  - [Codex native hook mapping](./docs/codex-native-hooks.md)
328
+ - [GitHub / PR / package identity pipeline](./docs/pipeline/github-pr-package-identity.md)
326
329
  - [Integrations](./docs/integrations.html)
327
330
  - [Troubleshooting execution readiness](./docs/troubleshooting.md)
328
331
  - [OpenClaw / notification gateway guide](./docs/openclaw-integration.md)
@@ -9,3 +9,6 @@ repository.workspace = true
9
9
  [[bin]]
10
10
  name = "omx-explore-harness"
11
11
  path = "src/main.rs"
12
+
13
+ [dependencies]
14
+ libc = "0.2"
@@ -3,17 +3,25 @@ use std::ffi::OsString;
3
3
  use std::fs::{
4
4
  canonicalize, create_dir_all, read_to_string, remove_dir_all, remove_file, write, File,
5
5
  };
6
- use std::io::{self, BufRead, BufReader};
6
+ use std::io::{self, BufRead, BufReader, Read};
7
7
  use std::path::{Path, PathBuf};
8
- use std::process::Command;
9
- use std::time::{SystemTime, UNIX_EPOCH};
8
+ use std::process::{Child, Command, Output, Stdio};
9
+ use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
10
+ use std::thread;
11
+ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
12
 
11
13
  const CODEX_BIN_ENV: &str = "OMX_EXPLORE_CODEX_BIN";
12
14
  const HARNESS_ROOT_ENV: &str = "OMX_EXPLORE_ROOT";
15
+ const CODEX_TIMEOUT_MS_ENV: &str = "OMX_EXPLORE_CODEX_TIMEOUT_MS";
13
16
  const INTERNAL_DIRECT_WRAPPER_FLAG: &str = "--internal-allowlist-direct";
14
17
  const INTERNAL_SHELL_WRAPPER_FLAG: &str = "--internal-allowlist-shell";
15
18
  const TEMP_ALLOWLIST_DIR_PREFIX: &str = "omx-explore-allowlist-";
16
- const SHELL_STARTUP_ENV_VARS: &[&str] = &["BASH_ENV", "ENV", "PROMPT_COMMAND"];
19
+ const DEFAULT_CODEX_TIMEOUT_MS: u64 = 180_000;
20
+ const PROCESS_TERMINATION_GRACE_MS: u64 = 500;
21
+ const PIPE_READER_READY_GRACE_MS: u64 = 25;
22
+ const PIPE_READER_JOIN_GRACE_MS: u64 = 500;
23
+ const EXPLORE_SUBPROCESS_ENV_VARS_TO_SCRUB: &[&str] =
24
+ &["BASH_ENV", "ENV", "PROMPT_COMMAND", "NODE_OPTIONS"];
17
25
  const WINDOWS_UNSUPPORTED_ALLOWLIST_MESSAGE: &str =
18
26
  "omx explore built-in harness is not ready on Windows because its allowlist runtime relies on POSIX sh/bash wrappers. Set OMX_EXPLORE_BIN to a compatible custom harness, prefer `omx sparkshell` for shell-native read-only lookups, or run `omx doctor` for readiness details.";
19
27
 
@@ -301,15 +309,187 @@ fn invoke_codex(args: &Args, model: &str, prompt_contract: &str) -> io::Result<A
301
309
  )
302
310
  .env("SHELL", &allowlist.shell_path);
303
311
  sanitize_explore_subprocess_env(&mut command);
304
- let output = command.output()?;
312
+ let timeout = codex_timeout();
313
+ let output = run_command_with_timeout(command, timeout)?;
305
314
 
306
315
  let markdown = read_to_string(&output_path).ok();
307
316
  let _ = remove_file(&output_path);
308
- Ok(AttemptResult {
309
- status_code: output.status.code().unwrap_or(1),
310
- stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
311
- output_markdown: markdown,
312
- })
317
+ match output {
318
+ TimedCommandOutput::Completed(output) => Ok(AttemptResult {
319
+ status_code: output.status.code().unwrap_or(1),
320
+ stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
321
+ output_markdown: markdown,
322
+ }),
323
+ TimedCommandOutput::TimedOut { stderr } => Ok(AttemptResult {
324
+ status_code: 124,
325
+ stderr: format!(
326
+ "[omx explore] codex exec timed out after {}ms; terminated process tree{}{}",
327
+ timeout.as_millis(),
328
+ if stderr.trim().is_empty() {
329
+ ""
330
+ } else {
331
+ ". stderr before timeout: "
332
+ },
333
+ stderr.trim()
334
+ ),
335
+ output_markdown: None,
336
+ }),
337
+ }
338
+ }
339
+
340
+ #[derive(Debug)]
341
+ enum TimedCommandOutput {
342
+ Completed(Output),
343
+ TimedOut { stderr: String },
344
+ }
345
+
346
+ fn codex_timeout() -> Duration {
347
+ let timeout_ms = env::var(CODEX_TIMEOUT_MS_ENV)
348
+ .ok()
349
+ .and_then(|value| value.trim().parse::<u64>().ok())
350
+ .filter(|value| *value > 0)
351
+ .unwrap_or(DEFAULT_CODEX_TIMEOUT_MS);
352
+ Duration::from_millis(timeout_ms)
353
+ }
354
+
355
+ fn run_command_with_timeout(
356
+ mut command: Command,
357
+ timeout: Duration,
358
+ ) -> io::Result<TimedCommandOutput> {
359
+ command.stdout(Stdio::piped()).stderr(Stdio::piped());
360
+ configure_process_group(&mut command);
361
+ let mut child = command.spawn()?;
362
+
363
+ let stdout_reader = spawn_pipe_reader(child.stdout.take());
364
+ let stderr_reader = spawn_pipe_reader(child.stderr.take());
365
+
366
+ let deadline = Instant::now() + timeout;
367
+ loop {
368
+ if let Some(status) = child.try_wait()? {
369
+ let (stdout, stderr) = collect_completed_output(
370
+ &mut child,
371
+ &stdout_reader,
372
+ &stderr_reader,
373
+ Duration::from_millis(PIPE_READER_READY_GRACE_MS),
374
+ Duration::from_millis(PIPE_READER_JOIN_GRACE_MS),
375
+ )?;
376
+ return Ok(TimedCommandOutput::Completed(Output {
377
+ status,
378
+ stdout,
379
+ stderr,
380
+ }));
381
+ }
382
+
383
+ if Instant::now() >= deadline {
384
+ terminate_child_process_tree(&mut child);
385
+ let _ = child.wait();
386
+ let reader_timeout = Duration::from_millis(PIPE_READER_JOIN_GRACE_MS);
387
+ let _ = receive_pipe_reader(&stdout_reader, reader_timeout);
388
+ let stderr = receive_pipe_reader(&stderr_reader, reader_timeout).unwrap_or_default();
389
+ return Ok(TimedCommandOutput::TimedOut {
390
+ stderr: String::from_utf8_lossy(&stderr).into_owned(),
391
+ });
392
+ }
393
+
394
+ thread::sleep(Duration::from_millis(25));
395
+ }
396
+ }
397
+
398
+ fn spawn_pipe_reader<R: Read + Send + 'static>(pipe: Option<R>) -> Receiver<io::Result<Vec<u8>>> {
399
+ let (sender, receiver) = mpsc::channel();
400
+ thread::spawn(move || {
401
+ let _ = sender.send(read_pipe_to_end(pipe));
402
+ });
403
+ receiver
404
+ }
405
+
406
+ fn read_pipe_to_end<R: Read + Send + 'static>(pipe: Option<R>) -> io::Result<Vec<u8>> {
407
+ let mut bytes = Vec::new();
408
+ if let Some(mut pipe) = pipe {
409
+ pipe.read_to_end(&mut bytes)?;
410
+ }
411
+ Ok(bytes)
412
+ }
413
+
414
+ fn collect_completed_output(
415
+ child: &mut Child,
416
+ stdout_reader: &Receiver<io::Result<Vec<u8>>>,
417
+ stderr_reader: &Receiver<io::Result<Vec<u8>>>,
418
+ ready_timeout: Duration,
419
+ cleanup_timeout: Duration,
420
+ ) -> io::Result<(Vec<u8>, Vec<u8>)> {
421
+ let stdout = receive_pipe_reader_if_ready(stdout_reader, ready_timeout)?;
422
+ let stderr = receive_pipe_reader_if_ready(stderr_reader, ready_timeout)?;
423
+
424
+ if stdout.is_none() || stderr.is_none() {
425
+ terminate_child_process_tree(child);
426
+ }
427
+
428
+ let stdout = match stdout {
429
+ Some(stdout) => stdout,
430
+ None => receive_pipe_reader(stdout_reader, cleanup_timeout)?,
431
+ };
432
+ let stderr = match stderr {
433
+ Some(stderr) => stderr,
434
+ None => receive_pipe_reader(stderr_reader, cleanup_timeout)?,
435
+ };
436
+
437
+ Ok((stdout, stderr))
438
+ }
439
+
440
+ fn receive_pipe_reader_if_ready(
441
+ receiver: &Receiver<io::Result<Vec<u8>>>,
442
+ timeout: Duration,
443
+ ) -> io::Result<Option<Vec<u8>>> {
444
+ match receive_pipe_reader(receiver, timeout) {
445
+ Ok(bytes) => Ok(Some(bytes)),
446
+ Err(err) if err.kind() == io::ErrorKind::TimedOut => Ok(None),
447
+ Err(err) => Err(err),
448
+ }
449
+ }
450
+
451
+ fn receive_pipe_reader(
452
+ receiver: &Receiver<io::Result<Vec<u8>>>,
453
+ timeout: Duration,
454
+ ) -> io::Result<Vec<u8>> {
455
+ match receiver.recv_timeout(timeout) {
456
+ Ok(result) => result,
457
+ Err(RecvTimeoutError::Timeout) => Err(io::Error::new(
458
+ io::ErrorKind::TimedOut,
459
+ "timed out waiting for subprocess output pipe to close",
460
+ )),
461
+ Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(
462
+ io::ErrorKind::Other,
463
+ "subprocess output reader disconnected",
464
+ )),
465
+ }
466
+ }
467
+
468
+ #[cfg(unix)]
469
+ fn configure_process_group(command: &mut Command) {
470
+ use std::os::unix::process::CommandExt;
471
+ command.process_group(0);
472
+ }
473
+
474
+ #[cfg(not(unix))]
475
+ fn configure_process_group(_command: &mut Command) {}
476
+
477
+ #[cfg(unix)]
478
+ fn terminate_child_process_tree(child: &mut Child) {
479
+ let pgid = child.id() as libc::pid_t;
480
+ unsafe {
481
+ let _ = libc::kill(-pgid, libc::SIGTERM);
482
+ }
483
+ thread::sleep(Duration::from_millis(PROCESS_TERMINATION_GRACE_MS));
484
+ unsafe {
485
+ let _ = libc::kill(-pgid, libc::SIGKILL);
486
+ }
487
+ let _ = child.kill();
488
+ }
489
+
490
+ #[cfg(not(unix))]
491
+ fn terminate_child_process_tree(child: &mut Child) {
492
+ let _ = child.kill();
313
493
  }
314
494
 
315
495
  fn escape_toml_string(value: &str) -> String {
@@ -759,11 +939,18 @@ fn shell_quote(value: &str) -> String {
759
939
  }
760
940
 
761
941
  fn sanitize_explore_subprocess_env(command: &mut Command) {
762
- for key in SHELL_STARTUP_ENV_VARS {
942
+ for key in EXPLORE_SUBPROCESS_ENV_VARS_TO_SCRUB {
763
943
  command.env_remove(key);
764
944
  }
765
945
  }
766
946
 
947
+ fn shell_supports_bash_startup_suppression(shell_path: &str) -> bool {
948
+ Path::new(shell_path)
949
+ .file_name()
950
+ .and_then(|name| name.to_str())
951
+ .is_some_and(|name| name == "bash")
952
+ }
953
+
767
954
  fn run_internal_direct_wrapper<I>(mut args: I) -> Result<(), String>
768
955
  where
769
956
  I: Iterator<Item = OsString>,
@@ -796,17 +983,22 @@ where
796
983
  let forwarded: Vec<String> = args.map(|arg| arg.to_string_lossy().into_owned()).collect();
797
984
  let command = validate_shell_invocation(&forwarded)?;
798
985
 
799
- let mut child = Command::new(&real_shell);
800
- if real_shell.ends_with("bash") {
986
+ let status_code = execute_validated_shell(&real_shell, &command)?;
987
+ std::process::exit(status_code);
988
+ }
989
+
990
+ fn execute_validated_shell(real_shell: &str, command: &str) -> Result<i32, String> {
991
+ let mut child = Command::new(real_shell);
992
+ if shell_supports_bash_startup_suppression(real_shell) {
801
993
  child.arg("--noprofile").arg("--norc");
802
994
  }
803
995
  sanitize_explore_subprocess_env(&mut child);
804
996
  let status = child
805
- .arg("-lc")
806
- .arg(&command)
997
+ .arg("-c")
998
+ .arg(command)
807
999
  .status()
808
1000
  .map_err(|err| format!("failed to execute validated shell command: {err}"))?;
809
- std::process::exit(status.code().unwrap_or(1));
1001
+ Ok(status.code().unwrap_or(1))
810
1002
  }
811
1003
 
812
1004
  fn validate_shell_invocation(args: &[String]) -> Result<String, String> {
@@ -1088,6 +1280,13 @@ mod tests {
1088
1280
  .unwrap_or_else(|poisoned| poisoned.into_inner())
1089
1281
  }
1090
1282
 
1283
+ fn process_tree_lock() -> std::sync::MutexGuard<'static, ()> {
1284
+ static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
1285
+ LOCK.get_or_init(|| Mutex::new(()))
1286
+ .lock()
1287
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
1288
+ }
1289
+
1091
1290
  #[test]
1092
1291
  fn parse_args_requires_all_fields() {
1093
1292
  let result = parse_args(vec![OsString::from("--cwd")].into_iter());
@@ -1906,6 +2105,69 @@ printf '# Answer\nok\n' > "$output_path"
1906
2105
  );
1907
2106
  }
1908
2107
 
2108
+ #[test]
2109
+ fn invoke_codex_scrubs_node_options_before_node_shebang_launch() {
2110
+ let _guard = env_lock();
2111
+ if resolve_host_command("node").is_none() {
2112
+ return;
2113
+ }
2114
+
2115
+ let root = temp_allowlist_dir().expect("temp root");
2116
+ let repo = root.path.join("repo");
2117
+ create_dir_all(&repo).expect("create repo");
2118
+ let prompt_file = root.path.join("prompt.md");
2119
+ write(&prompt_file, "contract").expect("write prompt");
2120
+ let capture_path = root.path.join("node-options.txt");
2121
+ let fake_codex = root.path.join("codex-node-stub");
2122
+ write(
2123
+ &fake_codex,
2124
+ format!(
2125
+ r#"#!/usr/bin/env node
2126
+ const fs = require('fs');
2127
+ let outputPath = '';
2128
+ for (let index = 2; index < process.argv.length; index += 1) {{
2129
+ if (process.argv[index] === '-o') {{
2130
+ outputPath = process.argv[index + 1];
2131
+ index += 1;
2132
+ }}
2133
+ }}
2134
+ fs.writeFileSync({}, `NODE_OPTIONS=${{process.env.NODE_OPTIONS || ''}}\n`);
2135
+ fs.writeFileSync(outputPath, '# Answer\nok\n');
2136
+ "#,
2137
+ shell_quote(&capture_path.display().to_string())
2138
+ ),
2139
+ )
2140
+ .expect("write fake codex");
2141
+
2142
+ unsafe {
2143
+ env::set_var(CODEX_BIN_ENV, &fake_codex);
2144
+ env::set_var("NODE_OPTIONS", "--disable-warning=");
2145
+ }
2146
+ let attempt = invoke_codex(
2147
+ &Args {
2148
+ cwd: repo.clone(),
2149
+ prompt: "find tests".to_string(),
2150
+ prompt_file,
2151
+ instructions_file: repo.join("AGENTS.md"),
2152
+ spark_model: "spark-model".to_string(),
2153
+ fallback_model: "fallback-model".to_string(),
2154
+ },
2155
+ "spark-model",
2156
+ "contract",
2157
+ )
2158
+ .expect("invoke codex");
2159
+ unsafe {
2160
+ env::remove_var(CODEX_BIN_ENV);
2161
+ env::remove_var("NODE_OPTIONS");
2162
+ }
2163
+
2164
+ assert_eq!(attempt.status_code, 0);
2165
+ assert_eq!(
2166
+ read_to_string(&capture_path).expect("read capture"),
2167
+ "NODE_OPTIONS=\n"
2168
+ );
2169
+ }
2170
+
1909
2171
  #[test]
1910
2172
  fn invoke_codex_injects_model_instructions_file_override() {
1911
2173
  let _guard = env_lock();
@@ -2009,6 +2271,245 @@ printf '# Answer\nok\n' > "$output_path"
2009
2271
  assert_eq!(read_to_string(&bash_env_log).unwrap_or_default(), "");
2010
2272
  }
2011
2273
 
2274
+ #[test]
2275
+ fn execute_validated_shell_drops_login_flag_and_startup_env() {
2276
+ let _guard = env_lock();
2277
+ let root = temp_allowlist_dir().expect("temp root");
2278
+ let fake_shell = root.path.join("fake-sh");
2279
+ let argv_log = root.path.join("argv.log");
2280
+ let startup_log = root.path.join("startup.log");
2281
+ let bash_env = root.path.join("bash-env.sh");
2282
+ write(
2283
+ &bash_env,
2284
+ format!(
2285
+ "grep -qsi '^COLOR.*none' /etc/GREP_COLORS || true\nprintf startup >> {}\n",
2286
+ shell_quote(&startup_log.display().to_string())
2287
+ ),
2288
+ )
2289
+ .expect("write fake startup hook");
2290
+ write_executable(
2291
+ &fake_shell,
2292
+ &format!(
2293
+ r#"#!/bin/sh
2294
+ printf '%s
2295
+ ' "$@" > {}
2296
+ if [ "${{BASH_ENV:-}}" ]; then
2297
+ . "$BASH_ENV"
2298
+ fi
2299
+ if [ "$1" = "-lc" ]; then
2300
+ grep -qsi '^COLOR.*none' /etc/GREP_COLORS
2301
+ fi
2302
+ if [ "$1" = "-c" ]; then
2303
+ shift
2304
+ exec /bin/sh -c "$1"
2305
+ fi
2306
+ exit 64
2307
+ "#,
2308
+ shell_quote(&argv_log.display().to_string())
2309
+ ),
2310
+ )
2311
+ .expect("write fake shell");
2312
+
2313
+ unsafe {
2314
+ env::set_var("BASH_ENV", &bash_env);
2315
+ env::set_var("ENV", &bash_env);
2316
+ env::set_var(
2317
+ "PROMPT_COMMAND",
2318
+ "grep -qsi '^COLOR.*none' /etc/GREP_COLORS",
2319
+ );
2320
+ }
2321
+ let status = execute_validated_shell(
2322
+ &fake_shell.display().to_string(),
2323
+ "printf shell-ok >/dev/null",
2324
+ )
2325
+ .expect("execute fake shell");
2326
+ unsafe {
2327
+ env::remove_var("BASH_ENV");
2328
+ env::remove_var("ENV");
2329
+ env::remove_var("PROMPT_COMMAND");
2330
+ }
2331
+
2332
+ assert_eq!(status, 0);
2333
+ assert_eq!(
2334
+ read_to_string(&argv_log).expect("read argv"),
2335
+ "-c\nprintf shell-ok >/dev/null\n"
2336
+ );
2337
+ assert_eq!(read_to_string(&startup_log).unwrap_or_default(), "");
2338
+ }
2339
+
2340
+ #[test]
2341
+ fn sanitize_explore_subprocess_env_blocks_fedora_grep_colors_startup_hook() {
2342
+ let _guard = env_lock();
2343
+ let root = temp_allowlist_dir().expect("temp root");
2344
+ let repo = root.path.join("repo");
2345
+ create_dir_all(&repo).expect("create repo");
2346
+ let startup_log = root.path.join("startup.log");
2347
+ let bash_env = root.path.join("bash-env.sh");
2348
+ write(
2349
+ &bash_env,
2350
+ format!(
2351
+ "grep -qsi '^COLOR.*none' /etc/GREP_COLORS || true\nprintf 'fedora-startup-ran\n' >> {}\n",
2352
+ shell_quote(&startup_log.display().to_string())
2353
+ ),
2354
+ )
2355
+ .expect("write bash env");
2356
+ let allowlist = prepare_allowlist_environment().expect("allowlist environment");
2357
+ let bash_path = resolve_host_command("bash").expect("host bash path");
2358
+
2359
+ unsafe {
2360
+ env::set_var("BASH_ENV", &bash_env);
2361
+ env::set_var("ENV", &bash_env);
2362
+ env::set_var(
2363
+ "PROMPT_COMMAND",
2364
+ "grep -qsi '^COLOR.*none' /etc/GREP_COLORS",
2365
+ );
2366
+ }
2367
+ let mut child = Command::new(bash_path);
2368
+ child
2369
+ .arg("--noprofile")
2370
+ .arg("--norc")
2371
+ .arg("-c")
2372
+ .arg("true")
2373
+ .env(HARNESS_ROOT_ENV, &repo)
2374
+ .env(
2375
+ "PATH",
2376
+ build_codex_path(&allowlist.bin_dir, allowlist.sandbox_bin_dir.as_deref())
2377
+ .expect("restricted path"),
2378
+ );
2379
+ sanitize_explore_subprocess_env(&mut child);
2380
+ let output = child.output().expect("run bash");
2381
+ unsafe {
2382
+ env::remove_var("BASH_ENV");
2383
+ env::remove_var("ENV");
2384
+ env::remove_var("PROMPT_COMMAND");
2385
+ }
2386
+
2387
+ assert!(output.status.success());
2388
+ assert_eq!(read_to_string(&startup_log).unwrap_or_default(), "");
2389
+ assert!(
2390
+ !String::from_utf8_lossy(&output.stderr).contains("/etc/GREP_COLORS"),
2391
+ "stderr should not contain Fedora GREP_COLORS repo escape: {}",
2392
+ String::from_utf8_lossy(&output.stderr)
2393
+ );
2394
+ }
2395
+
2396
+ #[test]
2397
+ fn invoke_codex_times_out_and_returns_bounded_failure() {
2398
+ let _guard = env_lock();
2399
+ let root = temp_allowlist_dir().expect("temp root");
2400
+ let repo = root.path.join("repo");
2401
+ create_dir_all(&repo).expect("create repo");
2402
+ let prompt_file = root.path.join("prompt.md");
2403
+ write(&prompt_file, "contract").expect("write prompt");
2404
+ let fake_codex = root.path.join("codex-sleep");
2405
+ write_executable(
2406
+ &fake_codex,
2407
+ r#"#!/bin/sh
2408
+ printf 'fake codex started
2409
+ ' >&2
2410
+ /bin/sleep 5
2411
+ "#,
2412
+ )
2413
+ .expect("write fake codex");
2414
+
2415
+ unsafe {
2416
+ env::set_var(CODEX_BIN_ENV, &fake_codex);
2417
+ env::set_var(CODEX_TIMEOUT_MS_ENV, "100");
2418
+ }
2419
+ let started = Instant::now();
2420
+ let attempt = invoke_codex(
2421
+ &Args {
2422
+ cwd: repo.clone(),
2423
+ prompt: "find tests".to_string(),
2424
+ prompt_file,
2425
+ instructions_file: repo.join("AGENTS.md"),
2426
+ spark_model: "spark-model".to_string(),
2427
+ fallback_model: "fallback-model".to_string(),
2428
+ },
2429
+ "spark-model",
2430
+ "contract",
2431
+ )
2432
+ .expect("invoke codex");
2433
+ unsafe {
2434
+ env::remove_var(CODEX_BIN_ENV);
2435
+ env::remove_var(CODEX_TIMEOUT_MS_ENV);
2436
+ }
2437
+
2438
+ assert_eq!(attempt.status_code, 124);
2439
+ assert!(attempt.stderr.contains("timed out after 100ms"));
2440
+ assert!(attempt.stderr.contains("terminated process tree"));
2441
+ assert!(started.elapsed() < Duration::from_secs(3));
2442
+ assert!(attempt.output_markdown.is_none());
2443
+ }
2444
+
2445
+ #[cfg(unix)]
2446
+ #[test]
2447
+ fn run_command_with_timeout_kills_process_group_children() {
2448
+ let _env_guard = env_lock();
2449
+ let _process_guard = process_tree_lock();
2450
+ let root = temp_allowlist_dir().expect("temp root");
2451
+ let term_file = root.path.join("grandchild.term");
2452
+ let ready_file = root.path.join("grandchild.ready");
2453
+ let script = root.path.join("spawn-grandchild.sh");
2454
+ write_executable(
2455
+ &script,
2456
+ &format!(
2457
+ r#"#!/bin/sh
2458
+ (trap 'printf term > {}; exit 0' TERM; printf ready > {}; sleep 30) &
2459
+ while [ ! -f {} ]; do
2460
+ sleep 0.01
2461
+ done
2462
+ sleep 30
2463
+ "#,
2464
+ shell_quote(&term_file.display().to_string()),
2465
+ shell_quote(&ready_file.display().to_string()),
2466
+ shell_quote(&ready_file.display().to_string()),
2467
+ ),
2468
+ )
2469
+ .expect("write script");
2470
+
2471
+ let result = run_command_with_timeout(Command::new(&script), Duration::from_millis(500))
2472
+ .expect("run with timeout");
2473
+ let TimedCommandOutput::TimedOut { .. } = result else {
2474
+ panic!("expected timeout");
2475
+ };
2476
+ assert_eq!(read_to_string(&term_file).unwrap_or_default(), "term");
2477
+ }
2478
+
2479
+ #[cfg(unix)]
2480
+ #[test]
2481
+ fn run_command_with_timeout_closes_inherited_stdio_after_parent_exit() {
2482
+ let _env_guard = env_lock();
2483
+ let _process_guard = process_tree_lock();
2484
+ let root = temp_allowlist_dir().expect("temp root");
2485
+ let script = root.path.join("inherited-stdio.sh");
2486
+ write_executable(
2487
+ &script,
2488
+ r#"#!/bin/sh
2489
+ (sleep 30) &
2490
+ printf 'parent stdout\n'
2491
+ printf 'parent stderr\n' >&2
2492
+ exit 0
2493
+ "#,
2494
+ )
2495
+ .expect("write script");
2496
+
2497
+ let started = Instant::now();
2498
+ let result = run_command_with_timeout(Command::new(&script), Duration::from_secs(10))
2499
+ .expect("run with inherited stdio descendant");
2500
+
2501
+ let TimedCommandOutput::Completed(output) = result else {
2502
+ panic!("expected parent completion");
2503
+ };
2504
+ assert!(
2505
+ started.elapsed() < Duration::from_secs(3),
2506
+ "reader cleanup should not wait for the descendant sleep"
2507
+ );
2508
+ assert!(output.status.success());
2509
+ assert_eq!(String::from_utf8_lossy(&output.stdout), "parent stdout\n");
2510
+ assert_eq!(String::from_utf8_lossy(&output.stderr), "parent stderr\n");
2511
+ }
2512
+
2012
2513
  fn fallback_test_event() -> FallbackEvent {
2013
2514
  FallbackEvent {
2014
2515
  from_model: "spark-model".to_string(),