oh-my-codex 0.15.3 → 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 (452) 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/autoresearch/goal.d.ts +90 -0
  7. package/dist/autoresearch/goal.d.ts.map +1 -0
  8. package/dist/autoresearch/goal.js +237 -0
  9. package/dist/autoresearch/goal.js.map +1 -0
  10. package/dist/autoresearch/skill-validation.d.ts +1 -0
  11. package/dist/autoresearch/skill-validation.d.ts.map +1 -1
  12. package/dist/autoresearch/skill-validation.js +10 -3
  13. package/dist/autoresearch/skill-validation.js.map +1 -1
  14. package/dist/catalog/__tests__/generator.test.js +9 -4
  15. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  16. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +20 -1
  17. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  18. package/dist/catalog/__tests__/schema.test.js +14 -3
  19. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  20. package/dist/catalog/schema.js +1 -1
  21. package/dist/catalog/schema.js.map +1 -1
  22. package/dist/cli/__tests__/autoresearch-goal.test.d.ts +2 -0
  23. package/dist/cli/__tests__/autoresearch-goal.test.d.ts.map +1 -0
  24. package/dist/cli/__tests__/autoresearch-goal.test.js +194 -0
  25. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -0
  26. package/dist/cli/__tests__/cleanup.test.js +82 -1
  27. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  28. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -4
  29. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  30. package/dist/cli/__tests__/doctor-warning-copy.test.js +23 -0
  31. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  32. package/dist/cli/__tests__/explore.test.js +8 -1
  33. package/dist/cli/__tests__/explore.test.js.map +1 -1
  34. package/dist/cli/__tests__/index.test.js +82 -3
  35. package/dist/cli/__tests__/index.test.js.map +1 -1
  36. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  37. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  38. package/dist/cli/__tests__/native-assets.test.js +26 -1
  39. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  40. package/dist/cli/__tests__/package-bin-contract.test.js +2 -2
  41. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  42. package/dist/cli/__tests__/performance-goal.test.d.ts +2 -0
  43. package/dist/cli/__tests__/performance-goal.test.d.ts.map +1 -0
  44. package/dist/cli/__tests__/performance-goal.test.js +144 -0
  45. package/dist/cli/__tests__/performance-goal.test.js.map +1 -0
  46. package/dist/cli/__tests__/question.test.js +8 -0
  47. package/dist/cli/__tests__/question.test.js.map +1 -1
  48. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts +2 -0
  49. package/dist/cli/__tests__/ralph-goal-mode-contract.test.d.ts.map +1 -0
  50. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +31 -0
  51. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -0
  52. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +5 -4
  53. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  54. package/dist/cli/__tests__/ralph-prd-smoke.test.js +7 -0
  55. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  56. package/dist/cli/__tests__/setup-install-mode.test.js +57 -21
  57. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  58. package/dist/cli/__tests__/setup-refresh.test.js +27 -8
  59. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  60. package/dist/cli/__tests__/setup-scope.test.js +18 -9
  61. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  62. package/dist/cli/__tests__/setup-skill-validation.test.js +11 -11
  63. package/dist/cli/__tests__/setup-skill-validation.test.js.map +1 -1
  64. package/dist/cli/__tests__/setup-skills-overwrite.test.js +12 -12
  65. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  66. package/dist/cli/__tests__/team.test.js +187 -0
  67. package/dist/cli/__tests__/team.test.js.map +1 -1
  68. package/dist/cli/__tests__/ultragoal.test.d.ts +2 -0
  69. package/dist/cli/__tests__/ultragoal.test.d.ts.map +1 -0
  70. package/dist/cli/__tests__/ultragoal.test.js +106 -0
  71. package/dist/cli/__tests__/ultragoal.test.js.map +1 -0
  72. package/dist/cli/__tests__/uninstall.test.js +11 -0
  73. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  74. package/dist/cli/autoresearch-goal.d.ts +3 -0
  75. package/dist/cli/autoresearch-goal.d.ts.map +1 -0
  76. package/dist/cli/autoresearch-goal.js +175 -0
  77. package/dist/cli/autoresearch-goal.js.map +1 -0
  78. package/dist/cli/cleanup.d.ts +3 -1
  79. package/dist/cli/cleanup.d.ts.map +1 -1
  80. package/dist/cli/cleanup.js +42 -2
  81. package/dist/cli/cleanup.js.map +1 -1
  82. package/dist/cli/doctor.d.ts.map +1 -1
  83. package/dist/cli/doctor.js +49 -0
  84. package/dist/cli/doctor.js.map +1 -1
  85. package/dist/cli/explore.d.ts.map +1 -1
  86. package/dist/cli/explore.js +10 -2
  87. package/dist/cli/explore.js.map +1 -1
  88. package/dist/cli/index.d.ts +6 -2
  89. package/dist/cli/index.d.ts.map +1 -1
  90. package/dist/cli/index.js +145 -18
  91. package/dist/cli/index.js.map +1 -1
  92. package/dist/cli/native-assets.js +1 -1
  93. package/dist/cli/native-assets.js.map +1 -1
  94. package/dist/cli/performance-goal.d.ts +3 -0
  95. package/dist/cli/performance-goal.d.ts.map +1 -0
  96. package/dist/cli/performance-goal.js +186 -0
  97. package/dist/cli/performance-goal.js.map +1 -0
  98. package/dist/cli/ralph.d.ts.map +1 -1
  99. package/dist/cli/ralph.js +8 -0
  100. package/dist/cli/ralph.js.map +1 -1
  101. package/dist/cli/setup.d.ts.map +1 -1
  102. package/dist/cli/setup.js +13 -6
  103. package/dist/cli/setup.js.map +1 -1
  104. package/dist/cli/team.d.ts +2 -0
  105. package/dist/cli/team.d.ts.map +1 -1
  106. package/dist/cli/team.js +72 -17
  107. package/dist/cli/team.js.map +1 -1
  108. package/dist/cli/tmux-hook.d.ts.map +1 -1
  109. package/dist/cli/tmux-hook.js +2 -1
  110. package/dist/cli/tmux-hook.js.map +1 -1
  111. package/dist/cli/ultragoal.d.ts +3 -0
  112. package/dist/cli/ultragoal.d.ts.map +1 -0
  113. package/dist/cli/ultragoal.js +191 -0
  114. package/dist/cli/ultragoal.js.map +1 -0
  115. package/dist/cli/uninstall.d.ts.map +1 -1
  116. package/dist/cli/uninstall.js +4 -2
  117. package/dist/cli/uninstall.js.map +1 -1
  118. package/dist/config/__tests__/generator-idempotent.test.js +12 -1
  119. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  120. package/dist/config/__tests__/generator-notify.test.js +5 -0
  121. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  122. package/dist/config/commit-lore-guard.d.ts +3 -0
  123. package/dist/config/commit-lore-guard.d.ts.map +1 -0
  124. package/dist/config/commit-lore-guard.js +9 -0
  125. package/dist/config/commit-lore-guard.js.map +1 -0
  126. package/dist/config/generator.d.ts +3 -2
  127. package/dist/config/generator.d.ts.map +1 -1
  128. package/dist/config/generator.js +52 -8
  129. package/dist/config/generator.js.map +1 -1
  130. package/dist/config/omx-first-party-mcp.d.ts +1 -0
  131. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  132. package/dist/config/omx-first-party-mcp.js +4 -1
  133. package/dist/config/omx-first-party-mcp.js.map +1 -1
  134. package/dist/goal-workflows/__tests__/artifacts.test.d.ts +2 -0
  135. package/dist/goal-workflows/__tests__/artifacts.test.d.ts.map +1 -0
  136. package/dist/goal-workflows/__tests__/artifacts.test.js +96 -0
  137. package/dist/goal-workflows/__tests__/artifacts.test.js.map +1 -0
  138. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts +2 -0
  139. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.d.ts.map +1 -0
  140. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +54 -0
  141. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -0
  142. package/dist/goal-workflows/artifacts.d.ts +62 -0
  143. package/dist/goal-workflows/artifacts.d.ts.map +1 -0
  144. package/dist/goal-workflows/artifacts.js +132 -0
  145. package/dist/goal-workflows/artifacts.js.map +1 -0
  146. package/dist/goal-workflows/codex-goal-snapshot.d.ts +28 -0
  147. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -0
  148. package/dist/goal-workflows/codex-goal-snapshot.js +110 -0
  149. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -0
  150. package/dist/goal-workflows/handoff.d.ts +10 -0
  151. package/dist/goal-workflows/handoff.d.ts.map +1 -0
  152. package/dist/goal-workflows/handoff.js +31 -0
  153. package/dist/goal-workflows/handoff.js.map +1 -0
  154. package/dist/goal-workflows/validation.d.ts +13 -0
  155. package/dist/goal-workflows/validation.d.ts.map +1 -0
  156. package/dist/goal-workflows/validation.js +36 -0
  157. package/dist/goal-workflows/validation.js.map +1 -0
  158. package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
  159. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  160. package/dist/hooks/__tests__/keyword-detector.test.js +45 -32
  161. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  162. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +3 -3
  163. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  164. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -1
  165. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  166. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +17 -24
  167. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  168. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +3 -3
  169. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  170. package/dist/hooks/__tests__/task-size-detector.test.js +1 -1
  171. package/dist/hooks/__tests__/task-size-detector.test.js.map +1 -1
  172. package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
  173. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  174. package/dist/hooks/__tests__/visual-verdict-loop.test.js +7 -11
  175. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  176. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  177. package/dist/hooks/agents-overlay.js +2 -2
  178. package/dist/hooks/agents-overlay.js.map +1 -1
  179. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  180. package/dist/hooks/keyword-detector.js +12 -13
  181. package/dist/hooks/keyword-detector.js.map +1 -1
  182. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  183. package/dist/hooks/keyword-registry.js +2 -10
  184. package/dist/hooks/keyword-registry.js.map +1 -1
  185. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  186. package/dist/hooks/prompt-guidance-contract.js +0 -4
  187. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  188. package/dist/hooks/session.js +2 -2
  189. package/dist/hooks/session.js.map +1 -1
  190. package/dist/hooks/task-size-detector.d.ts.map +1 -1
  191. package/dist/hooks/task-size-detector.js +1 -0
  192. package/dist/hooks/task-size-detector.js.map +1 -1
  193. package/dist/hud/__tests__/reconcile.test.js +29 -7
  194. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  195. package/dist/hud/reconcile.d.ts +2 -1
  196. package/dist/hud/reconcile.d.ts.map +1 -1
  197. package/dist/hud/reconcile.js +12 -0
  198. package/dist/hud/reconcile.js.map +1 -1
  199. package/dist/mcp/__tests__/bootstrap.test.js +15 -2
  200. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  201. package/dist/mcp/__tests__/state-paths.test.js +54 -0
  202. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  203. package/dist/mcp/__tests__/state-server.test.js +36 -0
  204. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  205. package/dist/mcp/bootstrap.d.ts +1 -1
  206. package/dist/mcp/bootstrap.d.ts.map +1 -1
  207. package/dist/mcp/bootstrap.js +9 -7
  208. package/dist/mcp/bootstrap.js.map +1 -1
  209. package/dist/mcp/state-paths.d.ts +17 -0
  210. package/dist/mcp/state-paths.d.ts.map +1 -1
  211. package/dist/mcp/state-paths.js +36 -2
  212. package/dist/mcp/state-paths.js.map +1 -1
  213. package/dist/modes/__tests__/base-session-scope.test.js +26 -0
  214. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  215. package/dist/modes/base.d.ts +1 -0
  216. package/dist/modes/base.d.ts.map +1 -1
  217. package/dist/modes/base.js +35 -5
  218. package/dist/modes/base.js.map +1 -1
  219. package/dist/notifications/__tests__/http-client.test.d.ts +2 -0
  220. package/dist/notifications/__tests__/http-client.test.d.ts.map +1 -0
  221. package/dist/notifications/__tests__/http-client.test.js +90 -0
  222. package/dist/notifications/__tests__/http-client.test.js.map +1 -0
  223. package/dist/notifications/__tests__/notifier.test.js +22 -60
  224. package/dist/notifications/__tests__/notifier.test.js.map +1 -1
  225. package/dist/notifications/dispatcher.d.ts.map +1 -1
  226. package/dist/notifications/dispatcher.js +35 -60
  227. package/dist/notifications/dispatcher.js.map +1 -1
  228. package/dist/notifications/http-client.d.ts +22 -0
  229. package/dist/notifications/http-client.d.ts.map +1 -0
  230. package/dist/notifications/http-client.js +298 -0
  231. package/dist/notifications/http-client.js.map +1 -0
  232. package/dist/notifications/notifier.d.ts +3 -2
  233. package/dist/notifications/notifier.d.ts.map +1 -1
  234. package/dist/notifications/notifier.js +17 -22
  235. package/dist/notifications/notifier.js.map +1 -1
  236. package/dist/openclaw/__tests__/dispatcher.test.js +62 -1
  237. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  238. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  239. package/dist/openclaw/dispatcher.js +3 -2
  240. package/dist/openclaw/dispatcher.js.map +1 -1
  241. package/dist/performance-goal/artifacts.d.ts +76 -0
  242. package/dist/performance-goal/artifacts.d.ts.map +1 -0
  243. package/dist/performance-goal/artifacts.js +221 -0
  244. package/dist/performance-goal/artifacts.js.map +1 -0
  245. package/dist/pipeline/__tests__/stages.test.js +30 -5
  246. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  247. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  248. package/dist/pipeline/stages/team-exec.js +2 -19
  249. package/dist/pipeline/stages/team-exec.js.map +1 -1
  250. package/dist/planning/__tests__/artifacts.test.js +16 -1
  251. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  252. package/dist/planning/artifacts.d.ts +1 -0
  253. package/dist/planning/artifacts.d.ts.map +1 -1
  254. package/dist/planning/artifacts.js +9 -12
  255. package/dist/planning/artifacts.js.map +1 -1
  256. package/dist/ralplan/__tests__/runtime.test.js +2 -0
  257. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  258. package/dist/ralplan/runtime.d.ts.map +1 -1
  259. package/dist/ralplan/runtime.js +6 -0
  260. package/dist/ralplan/runtime.js.map +1 -1
  261. package/dist/scripts/__tests__/codex-native-hook.test.js +1516 -205
  262. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  263. package/dist/scripts/__tests__/hook-derived-watcher.test.js +33 -1
  264. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  265. package/dist/scripts/__tests__/run-test-files.test.js +36 -0
  266. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  267. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  268. package/dist/scripts/codex-native-hook.js +497 -51
  269. package/dist/scripts/codex-native-hook.js.map +1 -1
  270. package/dist/scripts/codex-native-pre-post.d.ts +7 -0
  271. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  272. package/dist/scripts/codex-native-pre-post.js +222 -19
  273. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  274. package/dist/scripts/hook-derived-watcher.js +2 -1
  275. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  276. package/dist/scripts/notify-fallback-watcher.js +2 -1
  277. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  278. package/dist/scripts/notify-hook/orchestration-intent.d.ts +1 -2
  279. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  280. package/dist/scripts/notify-hook/orchestration-intent.js +2 -3
  281. package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -1
  282. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +0 -2
  283. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  284. package/dist/scripts/notify-hook/team-leader-nudge.js +8 -60
  285. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  286. package/dist/scripts/notify-hook/team-worker-stop.d.ts +15 -0
  287. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -0
  288. package/dist/scripts/notify-hook/team-worker-stop.js +224 -0
  289. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -0
  290. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  291. package/dist/scripts/notify-hook/team-worker.js +26 -18
  292. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  293. package/dist/scripts/run-test-files.js +17 -1
  294. package/dist/scripts/run-test-files.js.map +1 -1
  295. package/dist/scripts/sync-plugin-mirror.js +2 -2
  296. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  297. package/dist/state/__tests__/operations.test.js +26 -0
  298. package/dist/state/__tests__/operations.test.js.map +1 -1
  299. package/dist/state/__tests__/skill-active.test.js +35 -0
  300. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  301. package/dist/state/operations.d.ts +3 -1
  302. package/dist/state/operations.d.ts.map +1 -1
  303. package/dist/state/operations.js +8 -4
  304. package/dist/state/operations.js.map +1 -1
  305. package/dist/state/skill-active.d.ts +1 -0
  306. package/dist/state/skill-active.d.ts.map +1 -1
  307. package/dist/state/skill-active.js +54 -13
  308. package/dist/state/skill-active.js.map +1 -1
  309. package/dist/team/__tests__/api-interop.test.js +59 -0
  310. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  311. package/dist/team/__tests__/approved-execution.test.d.ts +2 -0
  312. package/dist/team/__tests__/approved-execution.test.d.ts.map +1 -0
  313. package/dist/team/__tests__/approved-execution.test.js +124 -0
  314. package/dist/team/__tests__/approved-execution.test.js.map +1 -0
  315. package/dist/team/__tests__/delivery-e2e-smoke.test.js +2 -4
  316. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  317. package/dist/team/__tests__/delivery-log.test.d.ts +2 -0
  318. package/dist/team/__tests__/delivery-log.test.d.ts.map +1 -0
  319. package/dist/team/__tests__/delivery-log.test.js +44 -0
  320. package/dist/team/__tests__/delivery-log.test.js.map +1 -0
  321. package/dist/team/__tests__/role-router.test.js +4 -4
  322. package/dist/team/__tests__/role-router.test.js.map +1 -1
  323. package/dist/team/__tests__/runtime-boxed-state.test.d.ts +2 -0
  324. package/dist/team/__tests__/runtime-boxed-state.test.d.ts.map +1 -0
  325. package/dist/team/__tests__/runtime-boxed-state.test.js +39 -0
  326. package/dist/team/__tests__/runtime-boxed-state.test.js.map +1 -0
  327. package/dist/team/__tests__/runtime.test.js +118 -6
  328. package/dist/team/__tests__/runtime.test.js.map +1 -1
  329. package/dist/team/__tests__/state-root.test.js +13 -0
  330. package/dist/team/__tests__/state-root.test.js.map +1 -1
  331. package/dist/team/__tests__/tmux-session.test.js +3 -0
  332. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  333. package/dist/team/__tests__/worker-bootstrap.test.js +50 -0
  334. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  335. package/dist/team/api-interop.d.ts.map +1 -1
  336. package/dist/team/api-interop.js +4 -3
  337. package/dist/team/api-interop.js.map +1 -1
  338. package/dist/team/approved-execution.d.ts +37 -0
  339. package/dist/team/approved-execution.d.ts.map +1 -0
  340. package/dist/team/approved-execution.js +136 -0
  341. package/dist/team/approved-execution.js.map +1 -0
  342. package/dist/team/delivery-log.d.ts.map +1 -1
  343. package/dist/team/delivery-log.js +2 -1
  344. package/dist/team/delivery-log.js.map +1 -1
  345. package/dist/team/followup-planner.js +2 -2
  346. package/dist/team/followup-planner.js.map +1 -1
  347. package/dist/team/goal-workflow.d.ts +20 -0
  348. package/dist/team/goal-workflow.d.ts.map +1 -0
  349. package/dist/team/goal-workflow.js +57 -0
  350. package/dist/team/goal-workflow.js.map +1 -0
  351. package/dist/team/orchestrator.js +2 -2
  352. package/dist/team/orchestrator.js.map +1 -1
  353. package/dist/team/role-router.js +5 -5
  354. package/dist/team/role-router.js.map +1 -1
  355. package/dist/team/runtime.d.ts +6 -0
  356. package/dist/team/runtime.d.ts.map +1 -1
  357. package/dist/team/runtime.js +46 -6
  358. package/dist/team/runtime.js.map +1 -1
  359. package/dist/team/scaling.d.ts.map +1 -1
  360. package/dist/team/scaling.js +2 -0
  361. package/dist/team/scaling.js.map +1 -1
  362. package/dist/team/tmux-session.d.ts.map +1 -1
  363. package/dist/team/tmux-session.js +4 -2
  364. package/dist/team/tmux-session.js.map +1 -1
  365. package/dist/team/worker-bootstrap.d.ts +2 -0
  366. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  367. package/dist/team/worker-bootstrap.js +19 -2
  368. package/dist/team/worker-bootstrap.js.map +1 -1
  369. package/dist/ultragoal/__tests__/artifacts.test.d.ts +2 -0
  370. package/dist/ultragoal/__tests__/artifacts.test.d.ts.map +1 -0
  371. package/dist/ultragoal/__tests__/artifacts.test.js +93 -0
  372. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -0
  373. package/dist/ultragoal/artifacts.d.ts +89 -0
  374. package/dist/ultragoal/artifacts.d.ts.map +1 -0
  375. package/dist/ultragoal/artifacts.js +233 -0
  376. package/dist/ultragoal/artifacts.js.map +1 -0
  377. package/dist/utils/__tests__/agents-model-table.test.js +3 -1
  378. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  379. package/dist/utils/__tests__/paths.test.js +31 -1
  380. package/dist/utils/__tests__/paths.test.js.map +1 -1
  381. package/dist/utils/agents-model-table.d.ts.map +1 -1
  382. package/dist/utils/agents-model-table.js +12 -1
  383. package/dist/utils/agents-model-table.js.map +1 -1
  384. package/dist/utils/paths.d.ts +2 -0
  385. package/dist/utils/paths.d.ts.map +1 -1
  386. package/dist/utils/paths.js +23 -7
  387. package/dist/utils/paths.js.map +1 -1
  388. package/dist/verification/__tests__/ci-rust-gates.test.js +30 -19
  389. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  390. package/package.json +5 -5
  391. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  392. package/plugins/oh-my-codex/skills/ask/SKILL.md +58 -0
  393. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +36 -0
  394. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +2 -2
  395. package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +65 -0
  396. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  397. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -3
  398. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -2
  399. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +49 -0
  400. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +9 -9
  401. package/prompts/api-reviewer.md +1 -1
  402. package/prompts/code-reviewer.md +2 -0
  403. package/prompts/performance-reviewer.md +1 -1
  404. package/prompts/quality-reviewer.md +1 -1
  405. package/prompts/quality-strategist.md +2 -2
  406. package/prompts/style-reviewer.md +1 -1
  407. package/prompts/test-engineer.md +1 -1
  408. package/skills/ask/SKILL.md +58 -0
  409. package/skills/ask-claude/SKILL.md +3 -54
  410. package/skills/ask-gemini/SKILL.md +3 -54
  411. package/skills/autoresearch-goal/SKILL.md +36 -0
  412. package/skills/build-fix/SKILL.md +4 -139
  413. package/skills/deepsearch/SKILL.md +4 -32
  414. package/skills/ecomode/SKILL.md +4 -108
  415. package/skills/help/SKILL.md +4 -196
  416. package/skills/note/SKILL.md +4 -56
  417. package/skills/omx-setup/SKILL.md +2 -2
  418. package/skills/performance-goal/SKILL.md +65 -0
  419. package/skills/plan/SKILL.md +1 -1
  420. package/skills/ralph/SKILL.md +22 -3
  421. package/skills/ralph-init/SKILL.md +4 -40
  422. package/skills/review/SKILL.md +4 -32
  423. package/skills/security-review/SKILL.md +4 -294
  424. package/skills/swarm/SKILL.md +4 -19
  425. package/skills/tdd/SKILL.md +4 -100
  426. package/skills/team/SKILL.md +6 -2
  427. package/skills/trace/SKILL.md +4 -27
  428. package/skills/ultragoal/SKILL.md +49 -0
  429. package/skills/visual-ralph/SKILL.md +9 -9
  430. package/skills/visual-verdict/SKILL.md +4 -70
  431. package/skills/web-clone/SKILL.md +4 -18
  432. package/src/scripts/__tests__/codex-native-hook.test.ts +1654 -157
  433. package/src/scripts/__tests__/hook-derived-watcher.test.ts +45 -1
  434. package/src/scripts/__tests__/run-test-files.test.ts +46 -0
  435. package/src/scripts/codex-native-hook.ts +592 -52
  436. package/src/scripts/codex-native-pre-post.ts +252 -20
  437. package/src/scripts/hook-derived-watcher.ts +2 -1
  438. package/src/scripts/notify-fallback-watcher.ts +2 -1
  439. package/src/scripts/notify-hook/orchestration-intent.ts +1 -3
  440. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -63
  441. package/src/scripts/notify-hook/team-worker-stop.ts +246 -0
  442. package/src/scripts/notify-hook/team-worker.ts +23 -14
  443. package/src/scripts/run-test-files.ts +20 -1
  444. package/src/scripts/sync-plugin-mirror.ts +2 -2
  445. package/templates/catalog-manifest.json +45 -27
  446. package/plugins/oh-my-codex/skills/ask-claude/SKILL.md +0 -61
  447. package/plugins/oh-my-codex/skills/ask-gemini/SKILL.md +0 -61
  448. package/plugins/oh-my-codex/skills/help/SKILL.md +0 -202
  449. package/plugins/oh-my-codex/skills/note/SKILL.md +0 -62
  450. package/plugins/oh-my-codex/skills/security-review/SKILL.md +0 -300
  451. package/plugins/oh-my-codex/skills/trace/SKILL.md +0 -33
  452. 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.3"
35
+ version = "0.16.0"
36
+ dependencies = [
37
+ "libc",
38
+ ]
36
39
 
37
40
  [[package]]
38
41
  name = "omx-mux"
39
- version = "0.15.3"
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.3"
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.3"
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.3"
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.3"
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(),