oh-my-codex 0.20.3 → 0.20.5

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 (661) hide show
  1. package/Cargo.lock +35 -18
  2. package/Cargo.toml +1 -1
  3. package/README.md +2 -2
  4. package/crates/omx-runtime/Cargo.toml +3 -0
  5. package/crates/omx-runtime/src/main.rs +390 -0
  6. package/crates/omx-runtime/tests/execution.rs +264 -0
  7. package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
  8. package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
  9. package/dist/adapt/__tests__/herdr-target.test.js +72 -0
  10. package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
  11. package/dist/adapt/contracts.d.ts +1 -1
  12. package/dist/adapt/contracts.d.ts.map +1 -1
  13. package/dist/adapt/contracts.js +1 -1
  14. package/dist/adapt/contracts.js.map +1 -1
  15. package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
  16. package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
  17. package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
  18. package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
  19. package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
  20. package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
  21. package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
  22. package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
  23. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
  24. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
  25. package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
  26. package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
  27. package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
  28. package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
  29. package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
  30. package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
  31. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
  32. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
  33. package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
  34. package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
  35. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
  36. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
  37. package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
  38. package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
  39. package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
  40. package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
  41. package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
  42. package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
  43. package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
  44. package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
  45. package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
  46. package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
  47. package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
  48. package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
  49. package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
  50. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
  51. package/dist/adapt/herdr/authority.d.ts +34 -0
  52. package/dist/adapt/herdr/authority.d.ts.map +1 -0
  53. package/dist/adapt/herdr/authority.js +71 -0
  54. package/dist/adapt/herdr/authority.js.map +1 -0
  55. package/dist/adapt/herdr/bridge.d.ts +84 -0
  56. package/dist/adapt/herdr/bridge.d.ts.map +1 -0
  57. package/dist/adapt/herdr/bridge.js +222 -0
  58. package/dist/adapt/herdr/bridge.js.map +1 -0
  59. package/dist/adapt/herdr/sanitize.d.ts +20 -0
  60. package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
  61. package/dist/adapt/herdr/sanitize.js +70 -0
  62. package/dist/adapt/herdr/sanitize.js.map +1 -0
  63. package/dist/adapt/herdr/semantic.d.ts +67 -0
  64. package/dist/adapt/herdr/semantic.d.ts.map +1 -0
  65. package/dist/adapt/herdr/semantic.js +170 -0
  66. package/dist/adapt/herdr/semantic.js.map +1 -0
  67. package/dist/adapt/herdr/seq-store.d.ts +32 -0
  68. package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
  69. package/dist/adapt/herdr/seq-store.js +126 -0
  70. package/dist/adapt/herdr/seq-store.js.map +1 -0
  71. package/dist/adapt/herdr/team-map.d.ts +30 -0
  72. package/dist/adapt/herdr/team-map.d.ts.map +1 -0
  73. package/dist/adapt/herdr/team-map.js +37 -0
  74. package/dist/adapt/herdr/team-map.js.map +1 -0
  75. package/dist/adapt/herdr/transport.d.ts +115 -0
  76. package/dist/adapt/herdr/transport.d.ts.map +1 -0
  77. package/dist/adapt/herdr/transport.js +260 -0
  78. package/dist/adapt/herdr/transport.js.map +1 -0
  79. package/dist/adapt/herdr/wiring.d.ts +24 -0
  80. package/dist/adapt/herdr/wiring.d.ts.map +1 -0
  81. package/dist/adapt/herdr/wiring.js +62 -0
  82. package/dist/adapt/herdr/wiring.js.map +1 -0
  83. package/dist/adapt/herdr.d.ts +19 -0
  84. package/dist/adapt/herdr.d.ts.map +1 -0
  85. package/dist/adapt/herdr.js +115 -0
  86. package/dist/adapt/herdr.js.map +1 -0
  87. package/dist/adapt/index.d.ts +4 -0
  88. package/dist/adapt/index.d.ts.map +1 -1
  89. package/dist/adapt/index.js +48 -0
  90. package/dist/adapt/index.js.map +1 -1
  91. package/dist/adapt/registry.d.ts.map +1 -1
  92. package/dist/adapt/registry.js +11 -0
  93. package/dist/adapt/registry.js.map +1 -1
  94. package/dist/auth/__tests__/hotswap.test.js +60 -1
  95. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  96. package/dist/auth/__tests__/storage.test.js +33 -1
  97. package/dist/auth/__tests__/storage.test.js.map +1 -1
  98. package/dist/auth/hotswap.d.ts +16 -2
  99. package/dist/auth/hotswap.d.ts.map +1 -1
  100. package/dist/auth/hotswap.js +22 -7
  101. package/dist/auth/hotswap.js.map +1 -1
  102. package/dist/auth/storage.js +1 -1
  103. package/dist/auth/storage.js.map +1 -1
  104. package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
  105. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  106. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  107. package/dist/autopilot/ralplan-gate.js +7 -6
  108. package/dist/autopilot/ralplan-gate.js.map +1 -1
  109. package/dist/cli/__tests__/agents-init.test.js +4 -23
  110. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  111. package/dist/cli/__tests__/api.test.js +61 -24
  112. package/dist/cli/__tests__/api.test.js.map +1 -1
  113. package/dist/cli/__tests__/auth.test.js +3 -1
  114. package/dist/cli/__tests__/auth.test.js.map +1 -1
  115. package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
  116. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
  117. package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
  118. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  119. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
  120. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
  121. package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
  122. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
  123. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
  124. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
  125. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
  126. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
  127. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
  128. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
  129. package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
  130. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
  131. package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
  132. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  133. package/dist/cli/__tests__/exec.test.js +75 -0
  134. package/dist/cli/__tests__/exec.test.js.map +1 -1
  135. package/dist/cli/__tests__/index.test.js +621 -460
  136. package/dist/cli/__tests__/index.test.js.map +1 -1
  137. package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
  138. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  139. package/dist/cli/__tests__/mcp-parity.test.js +4 -3
  140. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  141. package/dist/cli/__tests__/native-assets.test.js +398 -16
  142. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  143. package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
  144. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  145. package/dist/cli/__tests__/ralph.test.js +2 -2
  146. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  147. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
  148. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  149. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
  150. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
  151. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
  152. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
  153. package/dist/cli/__tests__/ralplan.test.js +84 -12
  154. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  155. package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
  156. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  157. package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
  158. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  159. package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
  160. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  161. package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
  162. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  163. package/dist/cli/__tests__/team.test.js +45 -1
  164. package/dist/cli/__tests__/team.test.js.map +1 -1
  165. package/dist/cli/__tests__/ultragoal.test.js +117 -2
  166. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  167. package/dist/cli/__tests__/uninstall.test.js +50 -5
  168. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  169. package/dist/cli/__tests__/update.test.js +669 -177
  170. package/dist/cli/__tests__/update.test.js.map +1 -1
  171. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
  172. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  173. package/dist/cli/api.d.ts.map +1 -1
  174. package/dist/cli/api.js +9 -10
  175. package/dist/cli/api.js.map +1 -1
  176. package/dist/cli/codex-feature-probe.d.ts +16 -0
  177. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  178. package/dist/cli/codex-feature-probe.js +44 -0
  179. package/dist/cli/codex-feature-probe.js.map +1 -1
  180. package/dist/cli/codex-home.d.ts.map +1 -1
  181. package/dist/cli/codex-home.js +7 -4
  182. package/dist/cli/codex-home.js.map +1 -1
  183. package/dist/cli/doctor.d.ts +17 -0
  184. package/dist/cli/doctor.d.ts.map +1 -1
  185. package/dist/cli/doctor.js +277 -3
  186. package/dist/cli/doctor.js.map +1 -1
  187. package/dist/cli/index.d.ts +231 -12
  188. package/dist/cli/index.d.ts.map +1 -1
  189. package/dist/cli/index.js +2298 -735
  190. package/dist/cli/index.js.map +1 -1
  191. package/dist/cli/native-assets.d.ts +25 -0
  192. package/dist/cli/native-assets.d.ts.map +1 -1
  193. package/dist/cli/native-assets.js +448 -51
  194. package/dist/cli/native-assets.js.map +1 -1
  195. package/dist/cli/native-hook-claim-journal.d.ts +8 -4
  196. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  197. package/dist/cli/native-hook-claim-journal.js +30 -8
  198. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  199. package/dist/cli/package-manager-ownership.d.ts +63 -0
  200. package/dist/cli/package-manager-ownership.d.ts.map +1 -0
  201. package/dist/cli/package-manager-ownership.js +323 -0
  202. package/dist/cli/package-manager-ownership.js.map +1 -0
  203. package/dist/cli/ralph.js +2 -2
  204. package/dist/cli/ralph.js.map +1 -1
  205. package/dist/cli/ralplan.d.ts +4 -2
  206. package/dist/cli/ralplan.d.ts.map +1 -1
  207. package/dist/cli/ralplan.js +76 -34
  208. package/dist/cli/ralplan.js.map +1 -1
  209. package/dist/cli/setup-preferences.d.ts +15 -0
  210. package/dist/cli/setup-preferences.d.ts.map +1 -1
  211. package/dist/cli/setup-preferences.js +27 -1
  212. package/dist/cli/setup-preferences.js.map +1 -1
  213. package/dist/cli/setup.d.ts +3 -0
  214. package/dist/cli/setup.d.ts.map +1 -1
  215. package/dist/cli/setup.js +55 -32
  216. package/dist/cli/setup.js.map +1 -1
  217. package/dist/cli/sparkshell.d.ts.map +1 -1
  218. package/dist/cli/sparkshell.js +52 -9
  219. package/dist/cli/sparkshell.js.map +1 -1
  220. package/dist/cli/ultragoal.d.ts.map +1 -1
  221. package/dist/cli/ultragoal.js +2 -2
  222. package/dist/cli/ultragoal.js.map +1 -1
  223. package/dist/cli/uninstall.d.ts.map +1 -1
  224. package/dist/cli/uninstall.js +27 -28
  225. package/dist/cli/uninstall.js.map +1 -1
  226. package/dist/cli/update-worker.d.ts +2 -0
  227. package/dist/cli/update-worker.d.ts.map +1 -0
  228. package/dist/cli/update-worker.js +142 -0
  229. package/dist/cli/update-worker.js.map +1 -0
  230. package/dist/cli/update.d.ts +14 -22
  231. package/dist/cli/update.d.ts.map +1 -1
  232. package/dist/cli/update.js +192 -241
  233. package/dist/cli/update.js.map +1 -1
  234. package/dist/config/__tests__/generator-idempotent.test.js +173 -29
  235. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  236. package/dist/config/__tests__/generator-notify.test.js +3 -1
  237. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  238. package/dist/config/generator.d.ts +25 -3
  239. package/dist/config/generator.d.ts.map +1 -1
  240. package/dist/config/generator.js +113 -35
  241. package/dist/config/generator.js.map +1 -1
  242. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
  243. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  244. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
  245. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  246. package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
  247. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  248. package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
  249. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  250. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
  251. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  252. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
  255. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
  256. package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
  257. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
  259. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
  261. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
  263. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  264. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
  265. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  266. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
  267. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  268. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
  269. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  270. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
  271. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  272. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
  273. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  274. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
  275. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
  276. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
  277. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  278. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
  279. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  280. package/dist/hooks/__tests__/session.test.js +2976 -557
  281. package/dist/hooks/__tests__/session.test.js.map +1 -1
  282. package/dist/hooks/agents-overlay.js +1 -1
  283. package/dist/hooks/agents-overlay.js.map +1 -1
  284. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
  285. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  286. package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
  287. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  288. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  289. package/dist/hooks/extensibility/dispatcher.js +20 -3
  290. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  291. package/dist/hooks/extensibility/plugin-runner.js +1 -0
  292. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  293. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  294. package/dist/hooks/extensibility/runtime.js.map +1 -1
  295. package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
  296. package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
  297. package/dist/hooks/extensibility/sdk/paths.js +2 -2
  298. package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
  299. package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
  300. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  301. package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
  302. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk.d.ts +1 -0
  304. package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
  305. package/dist/hooks/extensibility/sdk.js +1 -1
  306. package/dist/hooks/extensibility/sdk.js.map +1 -1
  307. package/dist/hooks/extensibility/types.d.ts +7 -0
  308. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  309. package/dist/hooks/keyword-detector.d.ts +10 -3
  310. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  311. package/dist/hooks/keyword-detector.js +116 -20
  312. package/dist/hooks/keyword-detector.js.map +1 -1
  313. package/dist/hooks/session.d.ts +180 -14
  314. package/dist/hooks/session.d.ts.map +1 -1
  315. package/dist/hooks/session.js +1402 -281
  316. package/dist/hooks/session.js.map +1 -1
  317. package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
  318. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  319. package/dist/hud/__tests__/index.test.js +26 -16
  320. package/dist/hud/__tests__/index.test.js.map +1 -1
  321. package/dist/hud/__tests__/reconcile.test.js +83 -78
  322. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  323. package/dist/hud/__tests__/state.test.js +36 -31
  324. package/dist/hud/__tests__/state.test.js.map +1 -1
  325. package/dist/hud/__tests__/tmux.test.js +155 -198
  326. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  327. package/dist/hud/tmux.d.ts +42 -4
  328. package/dist/hud/tmux.d.ts.map +1 -1
  329. package/dist/hud/tmux.js +1017 -139
  330. package/dist/hud/tmux.js.map +1 -1
  331. package/dist/leader/__tests__/contract.test.js +71 -34
  332. package/dist/leader/__tests__/contract.test.js.map +1 -1
  333. package/dist/leader/contract.d.ts +1 -6
  334. package/dist/leader/contract.d.ts.map +1 -1
  335. package/dist/leader/contract.js +25 -24
  336. package/dist/leader/contract.js.map +1 -1
  337. package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
  338. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  339. package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
  340. package/dist/mcp/__tests__/state-paths.test.js +735 -4
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/state-server.test.js +1 -1
  343. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  344. package/dist/mcp/__tests__/trace-server.test.js +2 -2
  345. package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
  346. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  347. package/dist/mcp/hermes-bridge.js +2 -2
  348. package/dist/mcp/hermes-bridge.js.map +1 -1
  349. package/dist/mcp/state-paths.d.ts +78 -4
  350. package/dist/mcp/state-paths.d.ts.map +1 -1
  351. package/dist/mcp/state-paths.js +543 -40
  352. package/dist/mcp/state-paths.js.map +1 -1
  353. package/dist/mcp/state-server.d.ts +4 -4
  354. package/dist/mcp/state-server.d.ts.map +1 -1
  355. package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
  356. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  357. package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
  358. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
  359. package/dist/modes/__tests__/base-session-scope.test.js +17 -8
  360. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  361. package/dist/modes/base.d.ts +5 -5
  362. package/dist/modes/base.d.ts.map +1 -1
  363. package/dist/modes/base.js +76 -21
  364. package/dist/modes/base.js.map +1 -1
  365. package/dist/native-assets/archive.d.ts +17 -0
  366. package/dist/native-assets/archive.d.ts.map +1 -0
  367. package/dist/native-assets/archive.js +238 -0
  368. package/dist/native-assets/archive.js.map +1 -0
  369. package/dist/native-assets/policy.d.ts +42 -0
  370. package/dist/native-assets/policy.d.ts.map +1 -0
  371. package/dist/native-assets/policy.js +155 -0
  372. package/dist/native-assets/policy.js.map +1 -0
  373. package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
  374. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  375. package/dist/pipeline/__tests__/stages.test.js +66 -149
  376. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  377. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  378. package/dist/pipeline/orchestrator.js +124 -18
  379. package/dist/pipeline/orchestrator.js.map +1 -1
  380. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  381. package/dist/pipeline/stages/ralplan.js +36 -8
  382. package/dist/pipeline/stages/ralplan.js.map +1 -1
  383. package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
  384. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  385. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
  386. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
  387. package/dist/ralplan/__tests__/runtime.test.js +82 -102
  388. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  389. package/dist/ralplan/consensus-gate.d.ts +9 -1
  390. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  391. package/dist/ralplan/consensus-gate.js +40 -304
  392. package/dist/ralplan/consensus-gate.js.map +1 -1
  393. package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
  394. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  395. package/dist/ralplan/documented-leader-preflight.js +356 -54
  396. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  397. package/dist/ralplan/runtime.d.ts +1 -1
  398. package/dist/ralplan/runtime.d.ts.map +1 -1
  399. package/dist/ralplan/runtime.js +68 -173
  400. package/dist/ralplan/runtime.js.map +1 -1
  401. package/dist/runtime/run-state.d.ts +5 -1
  402. package/dist/runtime/run-state.d.ts.map +1 -1
  403. package/dist/runtime/run-state.js +15 -8
  404. package/dist/runtime/run-state.js.map +1 -1
  405. package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
  406. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  407. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
  408. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
  409. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
  410. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
  411. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
  414. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
  415. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
  418. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
  419. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
  422. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
  423. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
  424. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
  425. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
  426. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
  427. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
  428. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
  429. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
  430. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
  431. package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
  432. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  433. package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
  434. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
  435. package/dist/scripts/__tests__/postinstall.test.js +19 -1
  436. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  437. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
  438. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  439. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
  440. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  441. package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
  442. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  443. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
  444. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
  445. package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
  446. package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
  447. package/dist/scripts/codex-native-hook.d.ts +50 -1
  448. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  449. package/dist/scripts/codex-native-hook.js +3559 -451
  450. package/dist/scripts/codex-native-hook.js.map +1 -1
  451. package/dist/scripts/codex-native-pre-post.d.ts +9 -0
  452. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  453. package/dist/scripts/codex-native-pre-post.js +35 -12
  454. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  455. package/dist/scripts/generate-native-release-manifest.js +80 -51
  456. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  457. package/dist/scripts/notify-fallback-watcher.js +28 -39
  458. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  460. package/dist/scripts/notify-hook/state-io.js +5 -0
  461. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  462. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
  464. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  466. package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
  467. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  469. package/dist/scripts/notify-hook/team-worker.js +45 -5
  470. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  473. package/dist/scripts/notify-hook/tmux-injection.js +4 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  475. package/dist/scripts/notify-hook.js +78 -82
  476. package/dist/scripts/notify-hook.js.map +1 -1
  477. package/dist/scripts/postinstall-advisory.d.ts +14 -0
  478. package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
  479. package/dist/scripts/postinstall-advisory.js +49 -0
  480. package/dist/scripts/postinstall-advisory.js.map +1 -0
  481. package/dist/scripts/postinstall.d.ts +1 -1
  482. package/dist/scripts/postinstall.d.ts.map +1 -1
  483. package/dist/scripts/postinstall.js +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/smoke-packed-install.d.ts +32 -1
  486. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  487. package/dist/scripts/smoke-packed-install.js +413 -60
  488. package/dist/scripts/smoke-packed-install.js.map +1 -1
  489. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js +16 -1
  491. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  492. package/dist/scripts/verify-native-release-assets.js +37 -23
  493. package/dist/scripts/verify-native-release-assets.js.map +1 -1
  494. package/dist/state/__tests__/mode-state-context.test.js +12 -5
  495. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  496. package/dist/state/__tests__/operations.test.js +460 -850
  497. package/dist/state/__tests__/operations.test.js.map +1 -1
  498. package/dist/state/__tests__/skill-active.test.js +400 -2
  499. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  500. package/dist/state/mode-state-context.d.ts +3 -0
  501. package/dist/state/mode-state-context.d.ts.map +1 -1
  502. package/dist/state/mode-state-context.js +27 -13
  503. package/dist/state/mode-state-context.js.map +1 -1
  504. package/dist/state/operations.d.ts +3 -0
  505. package/dist/state/operations.d.ts.map +1 -1
  506. package/dist/state/operations.js +144 -40
  507. package/dist/state/operations.js.map +1 -1
  508. package/dist/state/skill-active.d.ts +18 -1
  509. package/dist/state/skill-active.d.ts.map +1 -1
  510. package/dist/state/skill-active.js +420 -23
  511. package/dist/state/skill-active.js.map +1 -1
  512. package/dist/state/workflow-transition-reconcile.d.ts +3 -0
  513. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  514. package/dist/state/workflow-transition-reconcile.js +10 -4
  515. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  516. package/dist/state/workflow-transition.d.ts.map +1 -1
  517. package/dist/state/workflow-transition.js +3 -1
  518. package/dist/state/workflow-transition.js.map +1 -1
  519. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
  520. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  521. package/dist/subagents/__tests__/tracker.test.js +837 -1048
  522. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  523. package/dist/subagents/tracker.d.ts +64 -60
  524. package/dist/subagents/tracker.d.ts.map +1 -1
  525. package/dist/subagents/tracker.js +809 -358
  526. package/dist/subagents/tracker.js.map +1 -1
  527. package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
  528. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  529. package/dist/team/__tests__/mcp-comm.test.js +104 -3
  530. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  531. package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
  532. package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
  533. package/dist/team/__tests__/notice-ledger.test.js +131 -0
  534. package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
  535. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
  536. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
  537. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
  538. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
  539. package/dist/team/__tests__/phase-controller.test.js +20 -0
  540. package/dist/team/__tests__/phase-controller.test.js.map +1 -1
  541. package/dist/team/__tests__/runtime.test.js +725 -59
  542. package/dist/team/__tests__/runtime.test.js.map +1 -1
  543. package/dist/team/__tests__/scaling.test.js +222 -237
  544. package/dist/team/__tests__/scaling.test.js.map +1 -1
  545. package/dist/team/__tests__/state-root.test.js +117 -2
  546. package/dist/team/__tests__/state-root.test.js.map +1 -1
  547. package/dist/team/__tests__/tmux-session.test.js +887 -92
  548. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  549. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
  550. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
  551. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
  552. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
  553. package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
  554. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  555. package/dist/team/__tests__/tmux-test-fixture.js +242 -15
  556. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  557. package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
  558. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  559. package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
  560. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  561. package/dist/team/notice-ledger.d.ts +54 -0
  562. package/dist/team/notice-ledger.d.ts.map +1 -0
  563. package/dist/team/notice-ledger.js +259 -0
  564. package/dist/team/notice-ledger.js.map +1 -0
  565. package/dist/team/phase-controller.d.ts.map +1 -1
  566. package/dist/team/phase-controller.js +2 -0
  567. package/dist/team/phase-controller.js.map +1 -1
  568. package/dist/team/runtime.d.ts +3 -1
  569. package/dist/team/runtime.d.ts.map +1 -1
  570. package/dist/team/runtime.js +314 -142
  571. package/dist/team/runtime.js.map +1 -1
  572. package/dist/team/scaling.d.ts.map +1 -1
  573. package/dist/team/scaling.js +161 -91
  574. package/dist/team/scaling.js.map +1 -1
  575. package/dist/team/state/monitor.d.ts +10 -0
  576. package/dist/team/state/monitor.d.ts.map +1 -1
  577. package/dist/team/state/monitor.js +13 -0
  578. package/dist/team/state/monitor.js.map +1 -1
  579. package/dist/team/state/types.d.ts +15 -0
  580. package/dist/team/state/types.d.ts.map +1 -1
  581. package/dist/team/state/types.js.map +1 -1
  582. package/dist/team/state-root.d.ts +12 -0
  583. package/dist/team/state-root.d.ts.map +1 -1
  584. package/dist/team/state-root.js +177 -0
  585. package/dist/team/state-root.js.map +1 -1
  586. package/dist/team/state.d.ts +18 -0
  587. package/dist/team/state.d.ts.map +1 -1
  588. package/dist/team/state.js +36 -0
  589. package/dist/team/state.js.map +1 -1
  590. package/dist/team/tmux-session.d.ts +40 -4
  591. package/dist/team/tmux-session.d.ts.map +1 -1
  592. package/dist/team/tmux-session.js +513 -166
  593. package/dist/team/tmux-session.js.map +1 -1
  594. package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
  595. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  596. package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
  597. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  598. package/dist/ultragoal/artifacts.d.ts +35 -1
  599. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  600. package/dist/ultragoal/artifacts.js +232 -46
  601. package/dist/ultragoal/artifacts.js.map +1 -1
  602. package/dist/utils/__tests__/file-durability.test.js +58 -1
  603. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  604. package/dist/utils/__tests__/paths.test.js +21 -1
  605. package/dist/utils/__tests__/paths.test.js.map +1 -1
  606. package/dist/utils/file-durability.d.ts +10 -0
  607. package/dist/utils/file-durability.d.ts.map +1 -1
  608. package/dist/utils/file-durability.js +34 -4
  609. package/dist/utils/file-durability.js.map +1 -1
  610. package/dist/utils/paths.d.ts.map +1 -1
  611. package/dist/utils/paths.js +14 -6
  612. package/dist/utils/paths.js.map +1 -1
  613. package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
  614. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  615. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
  616. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  617. package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
  618. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
  619. package/package.json +13 -5
  620. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  621. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
  622. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
  623. package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
  624. package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
  625. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
  626. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
  627. package/skills/autopilot/SKILL.md +13 -5
  628. package/skills/cancel/SKILL.md +30 -85
  629. package/skills/ralph/SKILL.md +5 -5
  630. package/skills/ralplan/SKILL.md +15 -17
  631. package/skills/ultragoal/SKILL.md +3 -2
  632. package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
  633. package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
  634. package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
  635. package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
  636. package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
  637. package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
  638. package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
  639. package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
  640. package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
  641. package/src/scripts/__tests__/postinstall.test.ts +23 -1
  642. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
  643. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
  644. package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
  645. package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
  646. package/src/scripts/codex-native-hook.ts +3638 -475
  647. package/src/scripts/codex-native-pre-post.ts +43 -12
  648. package/src/scripts/generate-native-release-manifest.ts +83 -79
  649. package/src/scripts/notify-fallback-watcher.ts +38 -38
  650. package/src/scripts/notify-hook/state-io.ts +11 -0
  651. package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
  652. package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
  653. package/src/scripts/notify-hook/team-worker.ts +43 -5
  654. package/src/scripts/notify-hook/tmux-injection.ts +5 -1
  655. package/src/scripts/notify-hook.ts +83 -82
  656. package/src/scripts/postinstall-advisory.ts +67 -0
  657. package/src/scripts/postinstall.ts +1 -1
  658. package/src/scripts/smoke-packed-install.ts +446 -59
  659. package/src/scripts/sync-plugin-mirror.ts +17 -1
  660. package/src/scripts/verify-native-release-assets.ts +39 -31
  661. package/templates/AGENTS.md +1 -0
@@ -1,14 +1,15 @@
1
1
  import { afterEach, describe, it, mock } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { existsSync, mkdirSync, readFileSync, utimesSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
4
5
  import { chmod, lstat, mkdir, mkdtemp, readFile, readdir as fsReaddir, rm, stat, symlink, writeFile } from "node:fs/promises";
5
6
  import { delimiter, dirname, join } from "node:path";
6
7
  import { tmpdir } from "node:os";
7
8
  import { fileURLToPath } from "node:url";
8
- import { once } from "node:events";
9
9
  import TOML from "@iarna/toml";
10
- import { HELP, normalizeCodexLaunchArgs, buildTmuxShellCommand, buildTmuxPaneCommand, shouldSourceTmuxPaneShellRc, buildWindowsPromptCommand, buildTmuxSessionName, resolveCliInvocation, parseResumeCodexHomeSelection, isResumeCodexLaunch, CODEX_GLOBAL_OPTIONS_WITH_SPLIT_VALUE, isCodexVersionRequest, resolveUpdateChannelArg, commandOwnsLocalHelp, resolveCodexLaunchPolicy, resolveEffectiveLeaderLaunchPolicyOverride, resolveEnvLaunchPolicyOverride, resolveLeaderLaunchPolicyOverride, classifyCodexExecFailure, resolveSignalExitCode, parseTmuxPaneSnapshot, findHudWatchPaneIds, buildHudPaneCleanupTargets, readTopLevelTomlString, upsertTopLevelTomlString, collectInheritableTeamWorkerArgs, resolveTeamWorkerLaunchArgsEnv, injectModelInstructionsBypassArgs, resolveWorkerSparkModel, resolveSetupInstallModeArg, resolveSetupMcpModeArg, resolveSetupScopeArg, resolveSetupTeamModeArg, resolveSetupAgentsMergePolicyArg, resolveLaunchConfigRepairOptions, readPersistedSetupPreferences, readPersistedSetupScope, resolveCodexConfigPathForLaunch, resolveCodexHomeForLaunch, resolveProjectLocalCodexHomeForLaunch, shouldAutoIsolateMadmaxLaunch, createMadmaxIsolatedRoot, buildMadmaxDetachedLaunchContextKey, withMadmaxDetachedContextLock, resolveOmxRootForLaunch, resolveDisposableWorktreeOmxRootForLaunch, prepareCodexHomeForLaunch, captureMadmaxWorktreeRuntimeContext, persistProjectLaunchRuntimeAuthState, persistProjectLaunchRuntimeProjectTrustState, cleanupRuntimeCodexHome, runtimeCodexHomePath, buildDetachedSessionBootstrapSteps, buildDetachedTmuxSessionName, buildDetachedSessionFinalizeSteps, shouldAttachDetachedTmuxSession, buildDetachedSessionRollbackSteps, detectDetachedSessionWindowIndex, resolveNotifyTempContract, buildNotifyTempStartupMessages, buildNotifyFallbackWatcherEnv, shouldEnableNotifyFallbackWatcher, reapStaleNotifyFallbackWatcher, cleanupLaunchOrphanedMcpProcesses, reapPostLaunchOrphanedMcpProcesses, cleanupPostLaunchModeStateFiles, resolveBackgroundHelperLaunchMode, shouldDetachBackgroundHelper, resolveNotifyFallbackWatcherScript, resolveHookDerivedWatcherScript, resolveNotifyHookScript, buildDetachedWindowsBootstrapScript, acquireTmuxExtendedKeysLease, resolveNativeSessionName, releaseTmuxExtendedKeysLease, withTmuxExtendedKeys, serializeDetachedSessionParentEnv, buildInsideTmuxHudHookEnv, registerInsideTmuxHudResizeHook, buildDetachedHudHookEnv, registerDetachedHudLayoutReconcileHook, ensureOmxRuntimeCommandShim, omxRuntimeCommandShimPath, prependOmxRuntimeCommandShimToEnv, CODEX_SQLITE_HOME_ENV, DETACHED_TMUX_HISTORY_LIMIT, isExistingTmuxWindowTooCrampedForLaunchHud, } from "../index.js";
10
+ import { HELP, normalizeCodexLaunchArgs, buildTmuxShellCommand, buildTmuxPaneCommand, shouldSourceTmuxPaneShellRc, buildWindowsPromptCommand, buildWindowsDetachedChildCommand, buildTmuxSessionName, resolveCliInvocation, parseResumeCodexHomeSelection, isResumeCodexLaunch, CODEX_GLOBAL_OPTIONS_WITH_SPLIT_VALUE, isCodexVersionRequest, resolveUpdateChannelArg, commandOwnsLocalHelp, resolveCodexLaunchPolicy, resolveEffectiveLeaderLaunchPolicyOverride, resolveEnvLaunchPolicyOverride, resolveLeaderLaunchPolicyOverride, classifyCodexExecFailure, resolveSignalExitCode, parseTmuxPaneSnapshot, findHudWatchPaneIds, buildHudPaneCleanupTargets, readTopLevelTomlString, upsertTopLevelTomlString, collectInheritableTeamWorkerArgs, resolveTeamWorkerLaunchArgsEnv, injectModelInstructionsBypassArgs, resolveWorkerSparkModel, resolveSetupInstallModeArg, resolveSetupMcpModeArg, resolveSetupScopeArg, resolveSetupTeamModeArg, resolveSetupAgentsMergePolicyArg, resolveLaunchConfigRepairOptions, readPersistedSetupPreferences, readPersistedSetupScope, resolveCodexConfigPathForLaunch, resolveCodexHomeForLaunch, resolveProjectLocalCodexHomeForLaunch, shouldAutoIsolateMadmaxLaunch, createMadmaxIsolatedRoot, buildMadmaxDetachedLaunchContextKey, withMadmaxDetachedContextLock, executeDetachedLaunchStateMachine, resolveOmxRootForLaunch, resolveDisposableWorktreeOmxRootForLaunch, prepareCodexHomeForLaunch, captureMadmaxWorktreeRuntimeContext, persistProjectLaunchRuntimeAuthState, persistProjectLaunchRuntimeProjectTrustState, cleanupRuntimeCodexHome, runtimeCodexHomePath, buildDetachedSessionBootstrapSteps, buildDetachedTmuxSessionName, buildDetachedSessionFinalizeSteps, shouldAttachDetachedTmuxSession, buildDetachedSessionRollbackSteps, detectDetachedSessionWindowIndex, resolveNotifyTempContract, buildNotifyTempStartupMessages, buildNotifyFallbackWatcherEnv, shouldEnableNotifyFallbackWatcher, reapStaleNotifyFallbackWatcher, cleanupLaunchOrphanedMcpProcesses, reapPostLaunchOrphanedMcpProcesses, cleanupPostLaunchModeStateFiles, resolveBackgroundHelperLaunchMode, shouldDetachBackgroundHelper, resolveNotifyFallbackWatcherScript, resolveHookDerivedWatcherScript, resolveNotifyHookScript, acquireTmuxExtendedKeysLease, resolveNativeSessionName, releaseTmuxExtendedKeysLease, withTmuxExtendedKeys, serializeDetachedSessionParentEnv, buildInsideTmuxHudHookEnv, registerInsideTmuxHudResizeHook, buildDetachedHudHookEnv, registerDetachedHudLayoutReconcileHook, ensureOmxRuntimeCommandShim, omxRuntimeCommandShimPath, prependOmxRuntimeCommandShimToEnv, CODEX_SQLITE_HOME_ENV, DETACHED_TMUX_HISTORY_LIMIT, isExistingTmuxWindowTooCrampedForLaunchHud, guardDetachedHudDeferredMutation, DETACHED_LAUNCH_CONTROL_PLANE_KEYS, buildDetachedLaunchControlPlane, } from "../index.js";
11
11
  import { buildResumeArgsWithPreservedFlags, stripHotswapArg } from "../../auth/hotswap.js";
12
+ import { writeSkillActiveStateCopiesForStateDir } from '../../state/skill-active.js';
12
13
  import { mergeConfig, repairConfigIfNeeded } from "../../config/generator.js";
13
14
  import { ensureReusableNodeModules } from "../../utils/repo-deps.js";
14
15
  import { readAllState } from "../../hud/state.js";
@@ -17,6 +18,7 @@ import { HUD_TMUX_HEIGHT_LINES, HUD_TMUX_MIN_LAUNCH_WINDOW_HEIGHT_LINES } from "
17
18
  import { createHudWatchPane as createSharedHudWatchPane, listCurrentWindowHudPaneIds } from "../../hud/tmux.js";
18
19
  import { DEFAULT_FRONTIER_MODEL, getTeamLowComplexityModel, } from "../../config/models.js";
19
20
  import { splitWorkerLaunchArgs } from "../../team/model-contract.js";
21
+ import { buildRegisterResizeHookArgs } from "../../team/tmux-session.js";
20
22
  const testDir = dirname(fileURLToPath(import.meta.url));
21
23
  const repoRoot = join(testDir, "..", "..", "..");
22
24
  function normalizeDarwinTmpPath(value) {
@@ -67,6 +69,7 @@ describe("madmax state isolation", () => {
67
69
  const runDir = createMadmaxIsolatedRoot(wd, ["--madmax", "--high"], env);
68
70
  env.OMX_ROOT = runDir;
69
71
  env.OMXBOX_ACTIVE = "1";
72
+ env.OMX_SOURCE_CWD = wd;
70
73
  assert.equal(shouldAutoIsolateMadmaxLaunch("launch", ["--madmax", "--high"], env, wd), false);
71
74
  assert.equal(shouldAutoIsolateMadmaxLaunch("launch", ["--madmax", "--xhigh"], env, wd), true, "changed launch semantics must not reuse an inherited boxed root");
72
75
  }
@@ -78,29 +81,40 @@ describe("madmax state isolation", () => {
78
81
  it("preserves explicit no-box behavior", () => {
79
82
  assert.equal(shouldAutoIsolateMadmaxLaunch("launch", ["--madmax"], { OMX_NO_BOX: "1" }, "/repo"), false);
80
83
  });
81
- it("captures madmax worktree context from parsed worktree state, not remaining args", () => {
84
+ it("captures madmax worktree context from parsed worktree state, not remaining args", async () => {
82
85
  const sourceCwd = "/repo/source";
83
86
  const worktreeCwd = "/repo/.worktrees/session";
84
- const runDir = "/runs/run-issue-3043";
85
- const context = captureMadmaxWorktreeRuntimeContext({
86
- originalLaunchArgs: ["--madmax", "--worktree", "--version"],
87
- worktreeEnabled: true,
88
- sourceCwd,
89
- worktreeCwd,
90
- env: {
91
- OMX_ROOT: runDir,
92
- OMXBOX_ACTIVE: "1",
93
- OMX_SOURCE_CWD: sourceCwd,
94
- OMX_MADMAX_DETACHED_CONTEXT: "ctx-3043",
95
- },
96
- });
97
- assert.deepEqual(context, {
98
- omxRoot: runDir,
99
- sourceCwd,
100
- worktreeCwd,
101
- madmaxDetachedContext: "ctx-3043",
102
- boxedActive: true,
103
- });
87
+ const runDir = await mkdtemp(join(tmpdir(), "omx-run-issue-3043-"));
88
+ try {
89
+ const detachedContext = buildMadmaxDetachedLaunchContextKey(sourceCwd, ["--madmax", "--worktree", "--version"], runDir);
90
+ await writeFile(join(runDir, ".omxbox-run.json"), JSON.stringify({
91
+ cwd: runDir,
92
+ source_cwd: sourceCwd,
93
+ detached_launch_context: detachedContext,
94
+ }));
95
+ const context = captureMadmaxWorktreeRuntimeContext({
96
+ originalLaunchArgs: ["--madmax", "--worktree", "--version"],
97
+ worktreeEnabled: true,
98
+ sourceCwd,
99
+ worktreeCwd,
100
+ env: {
101
+ OMX_ROOT: runDir,
102
+ OMXBOX_ACTIVE: "1",
103
+ OMX_SOURCE_CWD: sourceCwd,
104
+ OMX_MADMAX_DETACHED_CONTEXT: detachedContext,
105
+ },
106
+ });
107
+ assert.deepEqual(context, {
108
+ omxRoot: runDir,
109
+ sourceCwd,
110
+ worktreeCwd,
111
+ madmaxDetachedContext: detachedContext,
112
+ boxedActive: true,
113
+ });
114
+ }
115
+ finally {
116
+ await rm(runDir, { recursive: true, force: true });
117
+ }
104
118
  });
105
119
  it("does not capture ordinary worktree or unboxed madmax launches", () => {
106
120
  assert.equal(captureMadmaxWorktreeRuntimeContext({
@@ -196,7 +210,7 @@ describe("madmax state isolation", () => {
196
210
  mkdirSync(lockPath, { recursive: true });
197
211
  await writeFile(join(lockPath, "pid"), "2147483647");
198
212
  let ran = false;
199
- const result = withMadmaxDetachedContextLock(runs, contextKey, () => {
213
+ const result = await withMadmaxDetachedContextLock(runs, contextKey, () => {
200
214
  ran = true;
201
215
  return "acquired";
202
216
  }, { maxAttempts: 2, retryMs: 0 });
@@ -221,7 +235,7 @@ describe("madmax state isolation", () => {
221
235
  acquired_at: new Date().toISOString(),
222
236
  })}\n`);
223
237
  await writeFile(join(lockPath, "pid"), String(process.pid));
224
- assert.throws(() => withMadmaxDetachedContextLock(runs, contextKey, () => "should-not-run", { maxAttempts: 1, retryMs: 0 }), (err) => {
238
+ await assert.rejects(() => withMadmaxDetachedContextLock(runs, contextKey, () => "should-not-run", { maxAttempts: 1, retryMs: 0 }), (err) => {
225
239
  assert.ok(err instanceof Error);
226
240
  assert.match(err.message, /timed out waiting for madmax detached launch context lock/);
227
241
  assert.match(err.message, new RegExp(`holder pid ${process.pid} is still running`));
@@ -238,6 +252,135 @@ describe("madmax state isolation", () => {
238
252
  }
239
253
  });
240
254
  });
255
+ describe("detached launch state machine", () => {
256
+ function createDependencies(events, failAt) {
257
+ const step = async (name) => {
258
+ events.push(name);
259
+ if (failAt === name)
260
+ throw new Error(name);
261
+ };
262
+ return {
263
+ establish: async () => { await step("D1"); return "binding"; },
264
+ complete: async () => { await step("D2"); return { kind: "success" }; },
265
+ createInertSession: async () => { await step("D3"); return "inert"; },
266
+ capturePane: async () => { await step("D4"); return "%1"; },
267
+ updateNameMetadata: async () => { await step("D5"); return "committed-released"; },
268
+ updatePaneMetadata: async () => { await step("D6"); return "committed-released"; },
269
+ publishActiveRecord: async () => {
270
+ await step("D7");
271
+ return { bytes: "record", digest: "digest", nonce: "nonce" };
272
+ },
273
+ finalizeSetupFailure: async () => { await step("finalize-setup-failure"); },
274
+ releaseBarrier: async () => { await step("D9"); },
275
+ abortAndAwaitFinalization: async () => ({
276
+ acknowledged: true,
277
+ nonce: "nonce",
278
+ sessionId: "session",
279
+ sessionName: "session-name",
280
+ leaderPid: 123,
281
+ kind: "failed",
282
+ }),
283
+ attachOrReturn: async () => { await step("D10"); },
284
+ rollback: async () => { events.push("rollback"); },
285
+ };
286
+ }
287
+ it("uses the frozen D0 outcome without rediscovering transport or reuse", async () => {
288
+ const events = [];
289
+ const result = await executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, createDependencies(events));
290
+ assert.equal(result.kind, "attached");
291
+ assert.deepEqual(events, ["D1", "D2", "D3", "D4", "D5", "D6", "D7", "D9", "D10"]);
292
+ });
293
+ for (const platform of ["posix", "native-windows"]) {
294
+ it(`${platform}: releases only after atomic record and starts exactly once`, async () => {
295
+ const events = [];
296
+ const result = await executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: platform === "posix", report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, createDependencies(events));
297
+ assert.equal(result.kind, platform === "posix" ? "attached" : "returned");
298
+ assert.deepEqual(events, ["D1", "D2", "D3", "D4", "D5", "D6", "D7", "D9", "D10"]);
299
+ });
300
+ }
301
+ for (const failure of ["D1", "D2", "D3", "D4", "D5", "D6", "D7"]) {
302
+ it(`rolls back verified pre-release ownership when ${failure} fails`, async () => {
303
+ const events = [];
304
+ await assert.rejects(executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, createDependencies(events, failure)), (err) => {
305
+ assert.ok(err instanceof Error);
306
+ assert.equal(err.report?.transitions.at(-1), failure);
307
+ return true;
308
+ });
309
+ assert.ok(events.includes("rollback"));
310
+ assert.equal(events.includes("D10"), false);
311
+ });
312
+ }
313
+ it("finalizes and closes exactly once before D2 rollback without transport effects", async () => {
314
+ const events = [];
315
+ const deps = createDependencies(events);
316
+ deps.complete = async () => {
317
+ events.push("D2");
318
+ return { kind: "failure", operation: "session-instructions", error: new Error("instructions") };
319
+ };
320
+ await assert.rejects(() => executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps), /preLaunch session-instructions failed: instructions/);
321
+ assert.deepEqual(events, ["D1", "D2", "finalize-setup-failure", "rollback"]);
322
+ });
323
+ it("rolls back proven pane authority when leader parsing fails before a report PID", async () => {
324
+ const events = [];
325
+ const deps = createDependencies(events, "D2");
326
+ deps.abortAndAwaitFinalization = async () => ({ acknowledged: false, rollbackAuthorized: true });
327
+ await assert.rejects(executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps));
328
+ assert.equal(events.includes("rollback"), true);
329
+ });
330
+ it("never rolls back, deletes records, or falls back after D10 attach failure", async () => {
331
+ const events = [];
332
+ await assert.rejects(executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, createDependencies(events, "D10")), (err) => {
333
+ assert.ok(err instanceof Error);
334
+ assert.equal(err.report?.transitions.at(-1), "D10");
335
+ return true;
336
+ });
337
+ assert.equal(events.includes("rollback"), false);
338
+ assert.deepEqual(events.slice(-2), ["D9", "D10"]);
339
+ });
340
+ it("preserves leader authority when D9 publication fails without an authenticated finalization acknowledgement", async () => {
341
+ const events = [];
342
+ const deps = createDependencies(events, "D9");
343
+ deps.abortAndAwaitFinalization = async () => {
344
+ events.push("abort-request");
345
+ return { acknowledged: false };
346
+ };
347
+ await assert.rejects(() => executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps));
348
+ assert.deepEqual(events.slice(-2), ["D9", "abort-request"]);
349
+ assert.equal(events.includes("rollback"), false);
350
+ });
351
+ it("permits rollback only after an authenticated D9 abort acknowledgement", async () => {
352
+ const events = [];
353
+ const deps = createDependencies(events, "D9");
354
+ deps.abortAndAwaitFinalization = async () => ({
355
+ acknowledged: true,
356
+ nonce: "nonce",
357
+ sessionId: "session",
358
+ sessionName: "session-name",
359
+ leaderPid: 123,
360
+ kind: "failed",
361
+ });
362
+ await assert.rejects(() => executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps));
363
+ assert.equal(events.includes("rollback"), true);
364
+ });
365
+ it("fails closed when a purported D9 acknowledgement omits its finalized authority binding", async () => {
366
+ const events = [];
367
+ const deps = createDependencies(events, "D9");
368
+ deps.abortAndAwaitFinalization = async () => ({ acknowledged: true });
369
+ await assert.rejects(() => executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps));
370
+ assert.equal(events.includes("rollback"), false);
371
+ });
372
+ it("preserves the authority-owning leader on a pre-D9 failure without finalized acknowledgement", async () => {
373
+ const events = [];
374
+ const deps = createDependencies(events, "D4");
375
+ deps.abortAndAwaitFinalization = async () => {
376
+ events.push("abort-request");
377
+ return { acknowledged: false };
378
+ };
379
+ await assert.rejects(() => executeDetachedLaunchStateMachine({ preflight: { kind: "available", shouldAttach: true, report: { transitions: ["D0"], rollback: { attempted: [], failures: [] } } } }, deps));
380
+ assert.deepEqual(events.slice(-2), ["D4", "abort-request"]);
381
+ assert.equal(events.includes("rollback"), false);
382
+ });
383
+ });
241
384
  describe("resolveOmxRootForLaunch", () => {
242
385
  it("preserves POSIX absolute OMX_ROOT", () => {
243
386
  assert.equal(resolveOmxRootForLaunch("/repo", { OMX_ROOT: "/var/tmp/omx" }), "/var/tmp/omx");
@@ -850,6 +993,81 @@ describe("cleanupPostLaunchModeStateFiles", () => {
850
993
  }
851
994
  assert.deepEqual(warnings, []);
852
995
  });
996
+ it('routes root skill-active cleanup through the locked root writer', async () => {
997
+ const wd = await mkdtemp(join(tmpdir(), 'omx-postlaunch-root-writer-'));
998
+ const sessionId = 'sess-root-writer';
999
+ const stateDir = join(wd, '.omx', 'state');
1000
+ const rootState = {
1001
+ version: 1,
1002
+ active: true,
1003
+ skill: 'ralph',
1004
+ session_id: sessionId,
1005
+ active_skills: [{ skill: 'ralph', phase: 'executing', active: true, session_id: sessionId }],
1006
+ };
1007
+ await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
1008
+ await writeFile(join(stateDir, 'skill-active-state.json'), `${JSON.stringify(rootState, null, 2)}\n`, 'utf8');
1009
+ let writerCalls = 0;
1010
+ await cleanupPostLaunchModeStateFiles(wd, sessionId, {
1011
+ writeRootState: async (rootDir, update) => {
1012
+ writerCalls += 1;
1013
+ const concurrentRoot = {
1014
+ ...rootState,
1015
+ active_skills: [
1016
+ ...rootState.active_skills,
1017
+ { skill: 'team', phase: 'running', active: true, session_id: 'sess-concurrent' },
1018
+ ],
1019
+ };
1020
+ const nextState = update(concurrentRoot);
1021
+ assert.ok(nextState);
1022
+ await writeSkillActiveStateCopiesForStateDir(rootDir, nextState);
1023
+ },
1024
+ });
1025
+ assert.equal(writerCalls, 1);
1026
+ const persisted = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf8'));
1027
+ assert.equal(persisted.active, true);
1028
+ assert.deepEqual(persisted.active_skills, [{ skill: 'team', phase: 'running', active: true, session_id: 'sess-concurrent' }]);
1029
+ });
1030
+ it('preserves a process-level session update before root scrub cleanup', async () => {
1031
+ const wd = await mkdtemp(join(tmpdir(), 'omx-postlaunch-process-scrub-'));
1032
+ const sessionId = 'sess-process-scrub';
1033
+ const stateDir = join(wd, '.omx', 'state');
1034
+ const rootPath = join(stateDir, 'skill-active-state.json');
1035
+ const rootState = {
1036
+ version: 1,
1037
+ active: true,
1038
+ skill: 'ralph',
1039
+ session_id: sessionId,
1040
+ active_skills: [{ skill: 'ralph', phase: 'executing', active: true, session_id: sessionId }],
1041
+ };
1042
+ try {
1043
+ await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
1044
+ await writeFile(rootPath, `${JSON.stringify(rootState, null, 2)}\n`, 'utf8');
1045
+ const updateScript = `
1046
+ import { readFile, writeFile } from 'node:fs/promises';
1047
+ import { writeSkillActiveStateCopiesForStateDir } from './dist/state/skill-active.js';
1048
+ const stateDir = process.env.STATE_DIR;
1049
+ const current = JSON.parse(await readFile(process.env.ROOT_PATH, 'utf8'));
1050
+ current.active_skills.push({ skill: 'team', phase: 'running', active: true, session_id: 'sess-process-concurrent' });
1051
+ await writeSkillActiveStateCopiesForStateDir(stateDir, current, undefined, current);
1052
+ `;
1053
+ const update = spawnSync(process.execPath, ['--input-type=module', '-e', updateScript], {
1054
+ cwd: repoRoot,
1055
+ env: { ...process.env, STATE_DIR: stateDir, ROOT_PATH: rootPath },
1056
+ encoding: 'utf8',
1057
+ });
1058
+ assert.equal(update.status, 0, update.stderr);
1059
+ const scrub = spawnSync(process.execPath, ['--input-type=module', '-e', `
1060
+ import { cleanupPostLaunchModeStateFiles } from './dist/cli/index.js';
1061
+ await cleanupPostLaunchModeStateFiles(${JSON.stringify(wd)}, ${JSON.stringify(sessionId)});
1062
+ `], { cwd: repoRoot, env: { ...process.env }, encoding: 'utf8' });
1063
+ assert.equal(scrub.status, 0, scrub.stderr);
1064
+ const persisted = JSON.parse(await readFile(rootPath, 'utf8'));
1065
+ assert.deepEqual(persisted.active_skills, [{ skill: 'team', phase: 'running', active: true, session_id: 'sess-process-concurrent' }]);
1066
+ }
1067
+ finally {
1068
+ await rm(wd, { recursive: true, force: true });
1069
+ }
1070
+ });
853
1071
  it("normalizes stale terminal deep-interview locks during postLaunch cleanup", async () => {
854
1072
  const wd = await mkdtemp(join(tmpdir(), "omx-postlaunch-di-terminal-locks-"));
855
1073
  const sessionId = "sess-postlaunch-di-terminal-locks";
@@ -1532,6 +1750,10 @@ describe("resolveCliInvocation", () => {
1532
1750
  assert.match(HELP, /omx update --stable\s+Install\/rollback to npm stable \(oh-my-codex@latest\), then refresh setup/);
1533
1751
  assert.match(HELP, /omx update --dev\s+Install the upstream dev branch, then refresh setup/);
1534
1752
  });
1753
+ it("scopes Ralplan authority preflight in top-level help", () => {
1754
+ assert.match(HELP, /omx ralplan\s+Adapted Ralplan authority support; preflight applies only when native role routing is unavailable\s+and adapted Ralplan authority is requested/);
1755
+ assert.doesNotMatch(HELP, /Record validated role intents/);
1756
+ });
1535
1757
  it("advertises only the four supported root reasoning modes", () => {
1536
1758
  assert.match(HELP, /omx reasoning Show or set model reasoning effort \(low\|medium\|high\|xhigh\)/);
1537
1759
  assert.match(HELP, /--high\s+Launch Codex with high reasoning effort/);
@@ -2231,6 +2453,47 @@ describe("project launch scope helpers", () => {
2231
2453
  await rm(wd, { recursive: true, force: true });
2232
2454
  }
2233
2455
  });
2456
+ it("walks upward to find a project-scoped setup for launch resolution", async () => {
2457
+ const wd = await mkdtemp(join(tmpdir(), "omx-launch-scope-"));
2458
+ try {
2459
+ await mkdir(join(wd, ".omx"), { recursive: true });
2460
+ await writeFile(join(wd, ".omx", "setup-scope.json"), JSON.stringify({ scope: "project" }));
2461
+ const nested = join(wd, "packages", "app", "src");
2462
+ await mkdir(nested, { recursive: true });
2463
+ assert.equal(resolveCodexHomeForLaunch(nested, {}), join(wd, ".codex"));
2464
+ assert.equal(resolveCodexConfigPathForLaunch(nested, {}), join(wd, ".codex", "config.toml"));
2465
+ }
2466
+ finally {
2467
+ await rm(wd, { recursive: true, force: true });
2468
+ }
2469
+ });
2470
+ it("does not redirect a launch outside any setup root into project CODEX_HOME", async () => {
2471
+ const wd = await mkdtemp(join(tmpdir(), "omx-launch-scope-"));
2472
+ try {
2473
+ await mkdir(join(wd, ".omx"), { recursive: true });
2474
+ await writeFile(join(wd, ".omx", "setup-scope.json"), JSON.stringify({ scope: "project" }));
2475
+ const outside = join(wd, "..", `outside-${Date.now()}`);
2476
+ await mkdir(outside, { recursive: true });
2477
+ assert.equal(resolveCodexHomeForLaunch(outside, {}), undefined);
2478
+ }
2479
+ finally {
2480
+ await rm(wd, { recursive: true, force: true });
2481
+ }
2482
+ });
2483
+ it("keeps a user-scope setup on the user config when launching from a subdirectory", async () => {
2484
+ const wd = await mkdtemp(join(tmpdir(), "omx-launch-scope-"));
2485
+ try {
2486
+ await mkdir(join(wd, ".omx"), { recursive: true });
2487
+ await writeFile(join(wd, ".omx", "setup-scope.json"), JSON.stringify({ scope: "user" }));
2488
+ const nested = join(wd, "sub");
2489
+ await mkdir(nested, { recursive: true });
2490
+ assert.equal(resolveCodexHomeForLaunch(nested, {}), undefined);
2491
+ assert.equal(resolveProjectLocalCodexHomeForLaunch(nested, {}), undefined);
2492
+ }
2493
+ finally {
2494
+ await rm(wd, { recursive: true, force: true });
2495
+ }
2496
+ });
2234
2497
  });
2235
2498
  describe("pointer launch aborts", () => {
2236
2499
  it("does not launch, tag, or retain a runtime home when a pointer lock is held", async () => {
@@ -2320,7 +2583,7 @@ describe("pointer launch aborts", () => {
2320
2583
  },
2321
2584
  });
2322
2585
  assert.equal(result.status, 1, result.stderr || result.stdout);
2323
- assert.match(result.stderr, /session_pointer_context_failure/);
2586
+ assert.match(result.stderr, /Unable to establish launch directory capability/);
2324
2587
  assert.equal(existsSync(codexLog), false);
2325
2588
  assert.equal(existsSync(tmuxLog), false);
2326
2589
  assert.equal(existsSync(join(wd, ".omx", "state", "sessions")), false);
@@ -2455,20 +2718,23 @@ describe("tmux HUD pane helpers", () => {
2455
2718
  it("buildHudPaneCleanupTargets is a no-op guard when leaderPaneId is absent", () => {
2456
2719
  assert.deepEqual(buildHudPaneCleanupTargets(["%3"], "%4"), ["%3", "%4"]);
2457
2720
  });
2458
- it("listCurrentWindowHudPaneIds scopes tmux pane listing to the emitting pane", () => {
2721
+ it("listCurrentWindowHudPaneIds excludes snapshots without complete current ownership", () => {
2459
2722
  const calls = [];
2460
- const panes = listCurrentWindowHudPaneIds("%leader", (args) => {
2723
+ const panes = listCurrentWindowHudPaneIds("%1", (args) => {
2461
2724
  calls.push(args);
2725
+ if (args.at(-1) === "#{pane_id}")
2726
+ return "%1\n%2\n";
2462
2727
  return [
2463
- "%leader\tcodex\tcodex",
2464
- "%hud\tnode\tnode /tmp/bin/omx.js hud --watch",
2728
+ "%1\x1fcodex\x1f0\x1f0\x1f100\x1f40\x1f39\x1f100\x1f40\x1fcodex\x1f/repo\x1f0\x1f101",
2729
+ "%2\x1fnode\x1f0\x1f40\x1f100\x1f3\x1f42\x1f100\x1f43\x1fexec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%1' node /tmp/bin/dist/cli/omx.js hud --watch\x1f/repo\x1f0\x1f202",
2465
2730
  ].join("\n");
2466
2731
  });
2467
- assert.deepEqual(panes, ["%hud"]);
2468
- assert.deepEqual(calls[0], [
2732
+ assert.deepEqual(panes, []);
2733
+ assert.deepEqual(calls[0], ["list-panes", "-t", "%1", "-F", "#{pane_id}"]);
2734
+ assert.deepEqual(calls[1], [
2469
2735
  "list-panes",
2470
2736
  "-t",
2471
- "%leader",
2737
+ "%1",
2472
2738
  "-F",
2473
2739
  [
2474
2740
  "#{pane_id}",
@@ -2482,31 +2748,67 @@ describe("tmux HUD pane helpers", () => {
2482
2748
  "#{window_height}",
2483
2749
  "#{pane_start_command}",
2484
2750
  "#{pane_current_path}",
2751
+ "#{pane_dead}",
2752
+ "#{pane_pid}",
2485
2753
  ].join("\x1f"),
2486
2754
  ]);
2487
2755
  });
2488
- it("createHudWatchPane splits from the emitting pane target when provided", () => {
2756
+ it("createHudWatchPane rejects an incomplete synthetic source incarnation", () => {
2489
2757
  const calls = [];
2490
- const paneId = createSharedHudWatchPane("/repo", "node /repo/dist/cli/omx.js hud --watch", { heightLines: 3, targetPaneId: "%leader" }, (args) => {
2758
+ const options = new Map();
2759
+ let splitCreated = false;
2760
+ let splitMarker = "";
2761
+ const paneId = createSharedHudWatchPane("/repo", "node /repo/dist/cli/omx.js hud --watch", { heightLines: 3, targetPaneId: "%1" }, (args) => {
2491
2762
  calls.push(args);
2492
- return "%hud\n";
2763
+ if (args[0] === "display-message") {
2764
+ const format = args.at(-1);
2765
+ if (format === "#{pane_id}\t#{pane_dead}\t#{pane_pid}\t#{session_id}\t#{window_id}") {
2766
+ return "%1\t0\t101\t$7\t@1";
2767
+ }
2768
+ if (format === "#{session_id}\t#{window_id}")
2769
+ return "$7\t@1";
2770
+ }
2771
+ if (args[0] === "list-panes") {
2772
+ const format = args.at(-1);
2773
+ const panes = splitCreated ? ["%1", "%2"] : ["%1"];
2774
+ if (format === "#{pane_id}")
2775
+ return `${panes.join("\n")}\n`;
2776
+ if (format === "#{pane_id}\t#{pane_start_command}") {
2777
+ return [
2778
+ "%1\tcodex",
2779
+ `%2\tOMX_TMUX_SPLIT_OPERATION_MARKER='${splitMarker}'; export OMX_TMUX_SPLIT_OPERATION_MARKER; exec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%1' node /repo/dist/cli/omx.js hud --watch`,
2780
+ ].join("\n");
2781
+ }
2782
+ if (format === "#{pane_id}\t#{pane_dead}\t#{pane_pid}") {
2783
+ return "%1\t0\t101\n%2\t0\t202\n";
2784
+ }
2785
+ }
2786
+ if (args[0] === "set-option") {
2787
+ options.set(args[2], args[3]);
2788
+ return "";
2789
+ }
2790
+ if (args[0] === "show-options")
2791
+ return `${options.get(args.at(-1)) ?? ""}\n`;
2792
+ if (args[0] === "if-shell") {
2793
+ assert.equal(args[1], "-F");
2794
+ assert.equal(args[2], "-t");
2795
+ assert.equal(args[3], "%1");
2796
+ assert.match(args[4], /#\{pane_id\},%1/);
2797
+ assert.match(args[4], /#\{pane_pid\},101/);
2798
+ assert.match(args[4], /#\{session_id\},\$1/);
2799
+ assert.match(args[4], /#\{window_id\},@1/);
2800
+ splitCreated = true;
2801
+ const marker = /OMX_TMUX_SPLIT_OPERATION_MARKER='([\w-]+)'/.exec(args[5]);
2802
+ const receipt = /display-message -p (__omx_hud_split_[\w-]+)/.exec(args[5]);
2803
+ assert.ok(marker, "guarded split must bind an operation marker");
2804
+ assert.ok(receipt, "guarded split must emit an exact receipt");
2805
+ splitMarker = marker[1];
2806
+ return `${receipt[1]}\n`;
2807
+ }
2808
+ throw new Error(`unexpected tmux command: ${args.join(" ")}`);
2493
2809
  });
2494
- assert.equal(paneId, "%hud");
2495
- assert.deepEqual(calls[0], [
2496
- "split-window",
2497
- "-v",
2498
- "-l",
2499
- "3",
2500
- "-d",
2501
- "-t",
2502
- "%leader",
2503
- "-c",
2504
- "/repo",
2505
- "-P",
2506
- "-F",
2507
- "#{pane_id}",
2508
- "node /repo/dist/cli/omx.js hud --watch",
2509
- ]);
2810
+ assert.equal(paneId, null);
2811
+ assert.equal(calls.some((args) => args[0] === "if-shell"), false);
2510
2812
  });
2511
2813
  });
2512
2814
  describe("detached tmux new-session sequencing", () => {
@@ -2575,7 +2877,7 @@ describe("detached tmux new-session sequencing", () => {
2575
2877
  assert.equal(newSession.args.includes("-e") &&
2576
2878
  newSession.args.some((arg) => arg === "OMX_ROOT=/tmp/omx-root"), true);
2577
2879
  });
2578
- it("buildDetachedSessionBootstrapSteps forwards boxed env to detached tmux session", () => {
2880
+ it("clears unverified boxed env from detached tmux sessions", () => {
2579
2881
  const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/boxed-runtime", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null, undefined, null, false, "sess-detached-managed", undefined, undefined, "/tmp/boxed-runtime", {
2580
2882
  OMXBOX_ACTIVE: "1",
2581
2883
  OMX_SOURCE_CWD: "/tmp/source-project",
@@ -2583,11 +2885,11 @@ describe("detached tmux new-session sequencing", () => {
2583
2885
  });
2584
2886
  const newSession = steps.find((step) => step.name === "new-session");
2585
2887
  assert.ok(newSession);
2586
- assert.equal(newSession.args.some((arg) => arg === "OMX_ROOT=/tmp/boxed-runtime"), true);
2888
+ assert.equal(newSession.args.some((arg) => arg === "OMX_ROOT=/tmp/boxed-runtime"), false);
2587
2889
  assert.equal(newSession.args.some((arg) => arg === "OMX_STATE_ROOT=/tmp/boxed-state-root"), false);
2588
2890
  assert.equal(newSession.args.some((arg) => arg === "OMX_TMUX_HUD_OWNER=1"), true);
2589
- assert.equal(newSession.args.some((arg) => arg === "OMXBOX_ACTIVE=1"), true);
2590
- assert.equal(newSession.args.some((arg) => arg === "OMX_SOURCE_CWD=/tmp/source-project"), true);
2891
+ assert.equal(newSession.args.some((arg) => arg === "OMXBOX_ACTIVE=1"), false);
2892
+ assert.equal(newSession.args.some((arg) => arg === "OMX_SOURCE_CWD=/tmp/source-project"), false);
2591
2893
  });
2592
2894
  it("buildDetachedSessionBootstrapSteps preserves OMX_STATE_ROOT identity when no root override is explicit", () => {
2593
2895
  const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null, undefined, null, false, "sess-detached-managed", undefined, undefined, "/tmp/state-root", { OMX_STATE_ROOT: "/tmp/state-root" });
@@ -2879,13 +3181,15 @@ exit 0
2879
3181
  it("runCodex builds inside-tmux HUD command through explicit runtime-root resolver", async () => {
2880
3182
  const source = await readFile(join(repoRoot, 'src', 'cli', 'index.ts'), 'utf-8');
2881
3183
  assert.match(source, /const hudRuntimeRoot: HudRuntimeRootForLaunch = runtimeContext\s*\? \{ omxRoot: runtimeContext\.omxRoot, rootSource: 'omx-root-env' \}\s*: resolveHudRuntimeRootForLaunch\(cwd, process\.env\);/);
2882
- assert.match(source, /const hudRuntimeEnv = \{\s*\.\.\.buildHudRuntimeEnv\(\{\s*sessionId,\s*leaderPaneId: currentPaneId,\s*\.\.\.hudRuntimeRoot,\s*\}\)\.env,\s*\.\.\.runtimeEnvOverlay,\s*\};\s*const hudEnvArgs = Object\.entries\(hudRuntimeEnv\)\.map\(\(\[key, value\]\) => `\$\{key\}=\$\{value\}`\)/);
2883
- assert.match(source, /if \(env\.OMX_TEAM_STATE_ROOT\?\.trim\(\)\) return 'team-env';\s*if \(env\.OMX_ROOT\?\.trim\(\) \|\| omxRootOverride\) return 'omx-root-env';\s*if \(env\.OMX_STATE_ROOT\?\.trim\(\)\) return 'omx-state-root-env';/);
3184
+ assert.match(source, /const hudRuntimeEnv = launchOwnedControlPlane\s*\? Object\.fromEntries\([\s\S]*?\)\s*:\s*\{\s*\.\.\.buildHudRuntimeEnv\(/);
3185
+ assert.match(source, /const restoreInsideTmuxControlPlane = insideTmuxControlPlane\s*\? applyLaunchOwnedControlPlane\(insideTmuxControlPlane\)/);
3186
+ assert.match(source, /const restoreInsideTmuxControlPlane[\s\S]*?launch = await preLaunch/);
3187
+ assert.match(source, /\.\.\.runtimeEnvOverlay,\s*\.\.\.launchOwnedControlPlaneEnv/);
2884
3188
  assert.match(source, /buildTmuxPaneCommand\("env",\s*\[\.\.\.hudEnvArgs,\s*"node",\s*omxBin,\s*"hud",\s*"--watch"\]\)/);
2885
3189
  });
2886
3190
  it("runCodex registers a HUD resize hook immediately for inside-tmux launches", async () => {
2887
3191
  const source = await readFile(join(repoRoot, 'src', 'cli', 'index.ts'), 'utf-8');
2888
- assert.match(source, /registerInsideTmuxHudResizeHook\(\{\s*hudPaneId,\s*currentPaneId,\s*cwd,\s*sessionId,\s*omxRootOverride,\s*baseEnv: runtimeHookEnv,\s*\}\)/);
3192
+ assert.match(source, /registerInsideTmuxHudResizeHook\(\{\s*hudPaneId,\s*currentPaneId,\s*cwd,\s*sessionId,\s*omxRootOverride: selectedOmxRootOverride,\s*baseEnv: runtimeHookEnv,\s*\}\)/);
2889
3193
  assert.match(source, /if \(currentPaneId\) \{\s*unregisterHudResizeHook\(currentPaneId\);\s*\}/);
2890
3194
  });
2891
3195
  it("buildInsideTmuxHudHookEnv tags hook commands with session, owner, leader, and local root", () => {
@@ -2944,6 +3248,59 @@ exit 0
2944
3248
  assert.equal(env.OMX_ROOT, "/repo");
2945
3249
  assert.equal(env.OMX_ENTRY_PATH, "/repo/dist/cli/omx.js");
2946
3250
  });
3251
+ describe("detached HUD deferred mutation guard", () => {
3252
+ const leader = {
3253
+ paneId: "%11",
3254
+ panePid: 1100,
3255
+ sessionName: "omx-detached",
3256
+ sessionId: "$1",
3257
+ sessionCreated: "1700000000",
3258
+ windowId: "@1",
3259
+ windowIndex: "0",
3260
+ ownerId: "instance-1",
3261
+ };
3262
+ const hud = {
3263
+ paneId: "%77",
3264
+ panePid: 7700,
3265
+ sessionId: "$1",
3266
+ windowId: "@1",
3267
+ operationMarker: "operation-1",
3268
+ };
3269
+ it("throws fail-closed when a deferred HUD payload has format escapes but no resize sink", () => {
3270
+ const sinkFree = ["run-shell", "-b",
3271
+ "tmux list-panes -a -F '#{pane_id}\t#{pane_dead}\t#{pane_pid}' >/dev/null 2>&1 || true"];
3272
+ assert.throws(() => guardDetachedHudDeferredMutation(leader, hud, sinkFree), /detached deferred HUD mutation lacks a recognized resize sink/);
3273
+ });
3274
+ it("guards immediate and delayed resize sinks as bare tmux command-list commands", () => {
3275
+ const args = buildRegisterResizeHookArgs("omx-detached:0", "omx_resize_detached", "%77", 2, 7700, "instance-1");
3276
+ const guarded = guardDetachedHudDeferredMutation(leader, hud, args);
3277
+ const hookCommand = guarded.at(-1);
3278
+ assert.match(hookCommand, /resize-pane -t %77 -y 2 ; display-message/);
3279
+ assert.doesNotMatch(hookCommand, /tmux resize-pane -t %77 -y 2 ; display-message/);
3280
+ assert.match(hookCommand, /tmux if-shell -F/);
3281
+ assert.match(hookCommand, /##\{pane_id\}/);
3282
+ assert.equal(countMatches(hookCommand, /tmux if-shell -F/g), 2);
3283
+ assert.equal(countMatches(hookCommand, /run-shell -b/g), 1);
3284
+ });
3285
+ it("escapes injected authority conditions so they survive the outer run-shell format pass", () => {
3286
+ const args = buildRegisterResizeHookArgs("omx-detached:0", "omx_resize_detached", "%77", 2, 7700, "instance-1");
3287
+ const hookCommand = guardDetachedHudDeferredMutation(leader, hud, args).at(-1);
3288
+ // run-shell applies exactly one outer tmux format pass to the stored payload.
3289
+ // Unescaped, tmux pre-expands these conditions against the hook's own pane and
3290
+ // collapses them to constants, denying the resize before the sink is parsed.
3291
+ for (const condition of [
3292
+ "##{==:##{pane_id},%11}",
3293
+ "##{==:##{pane_pid},1100}",
3294
+ "##{==:##{@omx_instance_id},instance-1}",
3295
+ "##{==:##{pane_id},%77}",
3296
+ "##{m:*OMX_DETACHED_HUD_OPERATION=operation-1*,##{pane_start_command}}",
3297
+ ]) {
3298
+ assert.ok(hookCommand.includes(condition), `stored payload must escape ${condition}`);
3299
+ }
3300
+ assert.doesNotMatch(hookCommand, /(^|[^#])#\{==:#\{pane_id\},%77\}/);
3301
+ assert.doesNotMatch(hookCommand, /(^|[^#])#\{&&:/);
3302
+ });
3303
+ });
2947
3304
  it("registerDetachedHudLayoutReconcileHook reads TMUX from the detached leader pane before registering", () => {
2948
3305
  const calls = [];
2949
3306
  const readTargets = [];
@@ -2993,412 +3350,28 @@ exit 0
2993
3350
  },
2994
3351
  }), false);
2995
3352
  });
2996
- it("buildDetachedSessionBootstrapSteps starts native Windows detached sessions with powershell", () => {
2997
- const hudCmd = buildWindowsPromptCommand("node", [
2998
- "omx.js",
2999
- "hud",
3000
- "--watch",
3001
- ]);
3002
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", "C:/project", "'codex' '--dangerously-bypass-approvals-and-sandbox'", hudCmd, "--model gpt-5", "C:/codex-home", null, true);
3003
- assert.equal(steps[0]?.name, "new-session");
3004
- assert.equal(steps[0]?.args.at(-1), "powershell.exe");
3005
- assert.equal(steps[1]?.name, "split-and-capture-hud-pane");
3006
- assert.equal(steps[1]?.args.at(-1), hudCmd);
3007
- });
3008
- it("buildDetachedWindowsBootstrapScript targets the resolved tmux-compatible command", () => {
3009
- const script = buildDetachedWindowsBootstrapScript("omx-demo", "powershell.exe -NoLogo -NoExit -EncodedCommand abc", 2500, "C:\\Program Files\\psmux\\psmux.exe");
3010
- assert.match(script, /const tmuxCommand = "C:\\\\Program Files\\\\psmux\\\\psmux\.exe";/);
3011
- assert.match(script, /execFileSync\(tmuxCommand, \['send-keys'/);
3012
- assert.doesNotMatch(script, /execFileSync\('tmux'/);
3013
- });
3014
- it("buildDetachedSessionBootstrapSteps kills detached tmux session on normal shell exit", () => {
3015
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3353
+ it("buildDetachedSessionBootstrapSteps starts native Windows with the persistent internal leader", () => {
3354
+ const steps = buildDetachedSessionBootstrapSteps("omx-demo", "C:/project", buildWindowsDetachedChildCommand("codex", ["--model", "gpt-5"]), "'node' 'omx.js' 'hud' '--watch'", null, undefined, null, true, "omx-session-123", "C:/project/.codex", "C:/project/.omx/runtime/codex-home/omx-session-123", undefined, process.env, undefined, undefined, undefined, "C:/project/.omx/runtime/detached-release/omx-session-123.release", "C:/project/dist/cli/omx.js");
3016
3355
  const leaderCmd = steps[0]?.args.at(-1);
3017
3356
  assert.equal(typeof leaderCmd, "string");
3018
- assert.match(leaderCmd, /^\/bin\/sh -c '/);
3019
- assert.doesNotMatch(leaderCmd, /^\/bin\/sh -lc '/);
3020
- assert.match(leaderCmd, /acquireTmuxExtendedKeysLease/);
3021
- assert.match(leaderCmd, /omx_detached_session_cleanup\(\)/);
3022
- assert.match(leaderCmd, /trap omx_detached_session_cleanup 0 INT TERM HUP;/);
3023
- assert.match(leaderCmd, /exec 3<&0;/);
3024
- assert.match(leaderCmd, /omx_codex_pid=\$!;/);
3025
- assert.match(leaderCmd, /<\&3 &/);
3026
- assert.match(leaderCmd, /wait "\$omx_codex_pid";/);
3027
- assert.match(leaderCmd, /kill -TERM "\$omx_codex_pid"/);
3028
- assert.match(leaderCmd, /releaseTmuxExtendedKeysLease/);
3029
- assert.match(leaderCmd, /if \[ "\$status" -eq 0 \]; then/);
3030
- assert.match(leaderCmd, /tmux kill-session -t/);
3031
- assert.match(leaderCmd, /"omx-demo"/);
3032
- assert.match(leaderCmd, /codex exited immediately with code 0/);
3033
- assert.match(leaderCmd, /codex exited with code/);
3034
- assert.match(leaderCmd, /detached tmux session is being kept open/);
3035
- assert.match(leaderCmd, /exit \$status/);
3036
- });
3037
- it("buildDetachedSessionBootstrapSteps finalizes postLaunch inside the detached leader when a session id is available", () => {
3038
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null, "/tmp/codex-home", null, false, "omx-session-123", "/tmp/project/.codex-project", "/tmp/project/.omx/runtime/codex-home/omx-session-123");
3357
+ assert.match(leaderCmd, /powershell\.exe -NoLogo -NoProfile -NonInteractive -Command/);
3358
+ assert.match(leaderCmd, /__detached-session-leader/);
3359
+ assert.doesNotMatch(leaderCmd, /__detached-post-launch|Test-Path -LiteralPath/);
3360
+ });
3361
+ it("buildDetachedSessionBootstrapSteps gives the POSIX leader persistent lifecycle ownership", () => {
3362
+ const releaseMarkerPath = "/tmp/project/.omx/runtime/detached-release/omx-session-123.release";
3363
+ const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null, "/tmp/codex-home", null, false, "omx-session-123", "/tmp/project/.codex-project", "/tmp/project/.omx/runtime/codex-home/omx-session-123", undefined, process.env, undefined, undefined, undefined, releaseMarkerPath);
3039
3364
  const leaderCmd = steps[0]?.args.at(-1);
3040
3365
  assert.equal(typeof leaderCmd, "string");
3041
- assert.match(leaderCmd, /runDetachedSessionPostLaunch/);
3042
- assert.match(leaderCmd, /omx-session-123/);
3043
- assert.match(leaderCmd, /\/tmp\/codex-home/);
3044
- assert.match(leaderCmd, /\/tmp\/project\/\.codex-project/);
3045
- assert.match(leaderCmd, /\/tmp\/project\/\.omx\/runtime\/codex-home\/omx-session-123/);
3046
- const helperIndex = leaderCmd.indexOf("runDetachedSessionPostLaunch");
3047
- const signalGateIndex = leaderCmd.indexOf('if [ "$status" -eq 0 ]');
3048
- assert.ok(helperIndex >= 0);
3049
- assert.ok(signalGateIndex >= 0);
3050
- assert.ok(helperIndex < signalGateIndex, "detached postLaunch helper must run before the signal-derived tmux kill-session gate");
3051
- });
3052
- it("detached leader command keeps stdin open for the Codex child", async () => {
3053
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-stdin-"));
3054
- const fakeBin = join(cwd, "bin");
3055
- const stdinLogPath = join(cwd, "stdin.log");
3056
- try {
3057
- await mkdir(fakeBin, { recursive: true });
3058
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3059
- if IFS= read -r line; then
3060
- printf 'stdin:%s\n' "$line" > "${stdinLogPath}"
3061
- else
3062
- printf 'stdin:EOF\n' > "${stdinLogPath}"
3063
- fi
3064
- `);
3065
- await chmod(join(fakeBin, "codex"), 0o755);
3066
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3067
- case "$1" in
3068
- display-message)
3069
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3070
- printf '/tmp/tmux-test.sock\n'
3071
- else
3072
- printf '0\n'
3073
- fi
3074
- ;;
3075
- show-options)
3076
- printf 'off\n'
3077
- ;;
3078
- set-option|kill-session)
3079
- ;;
3080
- esac
3081
- exit 0
3082
- `);
3083
- await chmod(join(fakeBin, "tmux"), 0o755);
3084
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", [], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3085
- const leaderCmd = steps[0]?.args.at(-1);
3086
- assert.equal(typeof leaderCmd, "string");
3087
- const result = (await import("node:child_process")).spawnSync("/bin/sh", ["-c", leaderCmd], {
3088
- cwd,
3089
- env: {
3090
- ...process.env,
3091
- PATH: `${fakeBin}:/usr/bin:/bin`,
3092
- HOME: cwd,
3093
- },
3094
- input: "hello from leader\n",
3095
- encoding: "utf-8",
3096
- });
3097
- assert.equal(result.status, 0, result.stderr || result.stdout);
3098
- const stdinLog = await readFile(stdinLogPath, "utf-8");
3099
- assert.match(stdinLog, /stdin:hello from leader/);
3100
- }
3101
- finally {
3102
- await rm(cwd, { recursive: true, force: true });
3103
- }
3104
- });
3105
- it("detached leader command preserves cwd and cleanup without shell-quote breakage", async () => {
3106
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-"));
3107
- const fakeBin = join(cwd, "bin");
3108
- const logPath = join(cwd, "leader.log");
3109
- try {
3110
- await mkdir(fakeBin, { recursive: true });
3111
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3112
- printf 'codex:%s\\n' "$*" >> "${logPath}"
3113
- printf 'codex-pwd:%s\\n' "$(pwd)" >> "${logPath}"
3114
- printf 'codex-bridge-env:%s\\n' "\${OMX_HERMES_MCP_BRIDGE-unset}" >> "${logPath}"
3115
- exit 0
3116
- `);
3117
- await chmod(join(fakeBin, "codex"), 0o755);
3118
- await writeFile(join(cwd, ".profile"), "cd ..\n");
3119
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3120
- printf 'tmux:%s\\n' "$*" >> "${logPath}"
3121
- case "$1" in
3122
- display-message)
3123
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3124
- printf '/tmp/tmux-test.sock\\n'
3125
- else
3126
- printf '0\\n'
3127
- fi
3128
- ;;
3129
- show-options)
3130
- printf 'off\\n'
3131
- ;;
3132
- set-option|kill-session)
3133
- ;;
3134
- esac
3135
- exit 0
3136
- `);
3137
- await chmod(join(fakeBin, "tmux"), 0o755);
3138
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", ["--dangerously-bypass-approvals-and-sandbox"], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3139
- const leaderCmd = steps[0]?.args.at(-1);
3140
- assert.equal(typeof leaderCmd, "string");
3141
- (await import("node:child_process")).execFileSync("/bin/sh", ["-c", leaderCmd], {
3142
- cwd,
3143
- env: {
3144
- ...process.env,
3145
- PATH: `${fakeBin}:/usr/bin:/bin`,
3146
- HOME: cwd,
3147
- OMX_HERMES_MCP_BRIDGE: "1",
3148
- },
3149
- stdio: "ignore",
3150
- });
3151
- const log = await readFile(logPath, "utf-8");
3152
- assert.match(log, /codex:--dangerously-bypass-approvals-and-sandbox/);
3153
- assert.match(normalizeDarwinTmpPath(log), new RegExp(`codex-pwd:${escapeRegExp(normalizeDarwinTmpPath(cwd))}`));
3154
- assert.match(log, /codex-bridge-env:unset/);
3155
- assert.match(log, /tmux:display-message -p #\{socket_path\}/);
3156
- assert.match(log, /tmux:show-options -sv extended-keys/);
3157
- assert.match(log, /tmux:set-option -sq extended-keys always/);
3158
- assert.match(log, /tmux:set-option -sq extended-keys off/);
3159
- assert.match(log, /tmux:kill-session -t omx-demo/);
3160
- }
3161
- finally {
3162
- await rm(cwd, { recursive: true, force: true });
3163
- }
3164
- });
3165
- it("detached leader command preserves the detached tmux session on signal-derived exits", async () => {
3166
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-signal-"));
3167
- const fakeBin = join(cwd, "bin");
3168
- const logPath = join(cwd, "leader.log");
3169
- try {
3170
- await mkdir(fakeBin, { recursive: true });
3171
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3172
- printf 'codex:%s\\n' "$*" >> "${logPath}"
3173
- exit 143
3174
- `);
3175
- await chmod(join(fakeBin, "codex"), 0o755);
3176
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3177
- printf 'tmux:%s\\n' "$*" >> "${logPath}"
3178
- case "$1" in
3179
- display-message)
3180
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3181
- printf '/tmp/tmux-test.sock\\n'
3182
- else
3183
- printf '0\\n'
3184
- fi
3185
- ;;
3186
- show-options)
3187
- printf 'off\\n'
3188
- ;;
3189
- set-option|kill-session)
3190
- ;;
3191
- esac
3192
- exit 0
3193
- `);
3194
- await chmod(join(fakeBin, "tmux"), 0o755);
3195
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", ["--dangerously-bypass-approvals-and-sandbox"], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3196
- const leaderCmd = steps[0]?.args.at(-1);
3197
- assert.equal(typeof leaderCmd, "string");
3198
- const result = (await import("node:child_process")).spawnSync("/bin/sh", ["-c", leaderCmd], {
3199
- cwd,
3200
- env: {
3201
- ...process.env,
3202
- PATH: `${fakeBin}:/usr/bin:/bin`,
3203
- HOME: cwd,
3204
- },
3205
- encoding: "utf-8",
3206
- });
3207
- assert.equal(result.status, 143);
3208
- const log = await readFile(logPath, "utf-8");
3209
- assert.match(log, /codex:--dangerously-bypass-approvals-and-sandbox/);
3210
- assert.match(log, /tmux:display-message -p #\{socket_path\}/);
3211
- assert.match(log, /tmux:show-options -sv extended-keys/);
3212
- assert.match(log, /tmux:set-option -sq extended-keys always/);
3213
- assert.match(log, /tmux:set-option -sq extended-keys off/);
3214
- assert.doesNotMatch(log, /tmux:kill-session -t omx-demo/);
3215
- }
3216
- finally {
3217
- await rm(cwd, { recursive: true, force: true });
3218
- }
3219
- });
3220
- it("detached leader command keeps child startup errors visible instead of killing the session", async () => {
3221
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-error-"));
3222
- const fakeBin = join(cwd, "bin");
3223
- const logPath = join(cwd, "leader.log");
3224
- try {
3225
- await mkdir(fakeBin, { recursive: true });
3226
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3227
- printf 'codex-stderr: unsupported startup flag\\n' >&2
3228
- exit 42
3229
- `);
3230
- await chmod(join(fakeBin, "codex"), 0o755);
3231
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3232
- printf 'tmux:%s\\n' "$*" >> "${logPath}"
3233
- case "$1" in
3234
- display-message)
3235
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3236
- printf '/tmp/tmux-test.sock\\n'
3237
- else
3238
- printf '0\\n'
3239
- fi
3240
- ;;
3241
- show-options)
3242
- printf 'off\\n'
3243
- ;;
3244
- set-option|kill-session)
3245
- ;;
3246
- esac
3247
- exit 0
3248
- `);
3249
- await chmod(join(fakeBin, "tmux"), 0o755);
3250
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", ["--bad-startup-flag"], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3251
- const leaderCmd = steps[0]?.args.at(-1);
3252
- assert.equal(typeof leaderCmd, "string");
3253
- const result = (await import("node:child_process")).spawnSync("/bin/sh", ["-c", leaderCmd], {
3254
- cwd,
3255
- env: {
3256
- ...process.env,
3257
- PATH: `${fakeBin}:/usr/bin:/bin`,
3258
- HOME: cwd,
3259
- },
3260
- input: "\n",
3261
- encoding: "utf-8",
3262
- });
3263
- assert.equal(result.status, 42);
3264
- assert.match(result.stderr, /codex-stderr: unsupported startup flag/);
3265
- assert.match(result.stderr, /codex exited with code 42 during startup/);
3266
- assert.match(result.stderr, /detached tmux session is being kept open/);
3267
- const log = await readFile(logPath, "utf-8");
3268
- assert.doesNotMatch(log, /tmux:kill-session -t omx-demo/);
3269
- }
3270
- finally {
3271
- await rm(cwd, { recursive: true, force: true });
3272
- }
3273
- });
3274
- it("detached leader command keeps immediate zero-code exits visible instead of silently closing", async () => {
3275
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-zero-"));
3276
- const fakeBin = join(cwd, "bin");
3277
- const logPath = join(cwd, "leader.log");
3278
- try {
3279
- await mkdir(fakeBin, { recursive: true });
3280
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3281
- printf 'codex-started-then-quit\\n' >&2
3282
- exit 0
3283
- `);
3284
- await chmod(join(fakeBin, "codex"), 0o755);
3285
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3286
- printf 'tmux:%s\\n' "$*" >> "${logPath}"
3287
- case "$1" in
3288
- display-message)
3289
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3290
- printf '/tmp/tmux-test.sock\\n'
3291
- else
3292
- printf '0\\n'
3293
- fi
3294
- ;;
3295
- show-options)
3296
- printf 'off\\n'
3297
- ;;
3298
- set-option|kill-session)
3299
- ;;
3300
- esac
3301
- exit 0
3302
- `);
3303
- await chmod(join(fakeBin, "tmux"), 0o755);
3304
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", [], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3305
- const leaderCmd = steps[0]?.args.at(-1);
3306
- assert.equal(typeof leaderCmd, "string");
3307
- const result = (await import("node:child_process")).spawnSync("/bin/sh", ["-c", leaderCmd], {
3308
- cwd,
3309
- env: {
3310
- ...process.env,
3311
- PATH: `${fakeBin}:/usr/bin:/bin`,
3312
- HOME: cwd,
3313
- },
3314
- input: "\n",
3315
- encoding: "utf-8",
3316
- });
3317
- assert.equal(result.status, 0, result.stderr || result.stdout);
3318
- assert.match(result.stderr, /codex-started-then-quit/);
3319
- assert.match(result.stderr, /codex exited immediately with code 0 during startup/);
3320
- assert.match(result.stderr, /detached tmux session is being kept open/);
3321
- const log = await readFile(logPath, "utf-8");
3322
- assert.match(log, /tmux:kill-session -t omx-demo/);
3323
- }
3324
- finally {
3325
- await rm(cwd, { recursive: true, force: true });
3326
- }
3366
+ assert.match(leaderCmd, /^\/bin\/sh -c '/);
3367
+ assert.match(leaderCmd, /__detached-session-leader/);
3368
+ assert.doesNotMatch(leaderCmd, /__detached-post-launch|omx_codex_pid|while \[ ! -f/);
3327
3369
  });
3328
- it("detached leader command terminates codex child on external SIGHUP", async () => {
3329
- const cwd = await mkdtemp(join(tmpdir(), "omx-detached-leader-hup-"));
3330
- const fakeBin = join(cwd, "bin");
3331
- const pidFile = join(cwd, "codex.pid");
3332
- try {
3333
- await mkdir(fakeBin, { recursive: true });
3334
- await writeFile(join(fakeBin, "codex"), `#!/bin/sh
3335
- echo $$ > "${pidFile}"
3336
- trap '' HUP
3337
- while true; do sleep 1; done
3338
- `);
3339
- await chmod(join(fakeBin, "codex"), 0o755);
3340
- await writeFile(join(fakeBin, "tmux"), `#!/bin/sh
3341
- case "$1" in
3342
- display-message)
3343
- if [ "$3" = '#{socket_path}' ] || [ "$4" = '#{socket_path}' ]; then
3344
- printf '/tmp/tmux-test.sock\\n'
3345
- else
3346
- printf '0\\n'
3347
- fi
3348
- ;;
3349
- show-options) printf 'off\\n' ;;
3350
- set-option|kill-session) ;;
3351
- esac
3352
- exit 0
3353
- `);
3354
- await chmod(join(fakeBin, "tmux"), 0o755);
3355
- const steps = buildDetachedSessionBootstrapSteps("omx-demo", cwd, buildTmuxPaneCommand("codex", [], "/bin/sh"), "'node' '/tmp/omx.js' 'hud' '--watch'", null);
3356
- const leaderCmd = steps[0]?.args.at(-1);
3357
- assert.equal(typeof leaderCmd, "string");
3358
- const { spawn } = await import("node:child_process");
3359
- const child = spawn("/bin/sh", ["-c", `exec ${leaderCmd}`], {
3360
- cwd,
3361
- env: {
3362
- ...process.env,
3363
- PATH: `${fakeBin}:/usr/bin:/bin`,
3364
- HOME: cwd,
3365
- },
3366
- stdio: "ignore",
3367
- detached: true,
3368
- });
3369
- try {
3370
- for (let i = 0; i < 50; i += 1) {
3371
- if (existsSync(pidFile))
3372
- break;
3373
- await new Promise((resolve) => setTimeout(resolve, 100));
3374
- }
3375
- assert.ok(existsSync(pidFile), "codex pid file not written");
3376
- const codexPid = Number.parseInt((await readFile(pidFile, "utf-8")).trim(), 10);
3377
- assert.ok(codexPid > 0, "codex pid must be positive");
3378
- assert.doesNotThrow(() => process.kill(codexPid, 0), "codex must be alive before signal");
3379
- const leaderExit = once(child, "exit");
3380
- process.kill(child.pid, "SIGHUP");
3381
- await Promise.race([
3382
- leaderExit,
3383
- new Promise((_, reject) => setTimeout(() => reject(new Error("leader did not exit after SIGHUP")), 3000)),
3384
- ]);
3385
- assert.throws(() => process.kill(codexPid, 0), (err) => typeof err === "object" &&
3386
- err !== null &&
3387
- "code" in err &&
3388
- err.code === "ESRCH", "codex child must be terminated after leader SIGHUP");
3389
- }
3390
- finally {
3391
- try {
3392
- process.kill(child.pid, "SIGKILL");
3393
- }
3394
- catch {
3395
- /* already dead */
3396
- }
3397
- }
3398
- }
3399
- finally {
3400
- await rm(cwd, { recursive: true, force: true });
3401
- }
3370
+ it("keeps capability data out of the detached leader command payload", () => {
3371
+ const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "codex", "hud", null, undefined, null, false, "omx-session-123", undefined, undefined, undefined, process.env);
3372
+ const leaderCmd = steps[0]?.args.at(-1) ?? "";
3373
+ assert.match(leaderCmd, /__detached-session-leader/);
3374
+ assert.doesNotMatch(leaderCmd, /launch_lineage_token|directoryIdentity|FileHandle/);
3402
3375
  });
3403
3376
  it("withTmuxExtendedKeys enables tmux extended keys during codex launch and restores them afterwards", async () => {
3404
3377
  const cwd = await mkdtemp(join(tmpdir(), "omx-tmux-lease-wrapper-"));
@@ -3953,6 +3926,45 @@ describe("buildWindowsPromptCommand", () => {
3953
3926
  assert.equal(decoded, "$ErrorActionPreference = 'Stop'; & { & 'codex' '--dangerously-bypass-approvals-and-sandbox' '-c' 'model_reasoning_effort=\"high\"' 'it''s' }");
3954
3927
  });
3955
3928
  });
3929
+ describe("buildWindowsDetachedChildCommand", () => {
3930
+ it("exits with the Codex child status instead of retaining an interactive shell", () => {
3931
+ const result = buildWindowsDetachedChildCommand("codex", ["--model", "gpt-5"]);
3932
+ const prefix = "powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand ";
3933
+ assert.ok(result.startsWith(prefix));
3934
+ assert.doesNotMatch(result, /-NoExit/);
3935
+ assert.equal(Buffer.from(result.slice(prefix.length), "base64").toString("utf16le"), "$ErrorActionPreference = 'Stop'; & 'codex' '--model' 'gpt-5'; exit $LASTEXITCODE");
3936
+ });
3937
+ });
3938
+ describe("Windows detached leader environment", () => {
3939
+ it("serializes inherited parent values into the detached Windows leader command", () => {
3940
+ const steps = buildDetachedSessionBootstrapSteps("omx-test", "C:/project", "codex", "hud", null, undefined, null, true, "session-1", undefined, undefined, undefined, {
3941
+ PATH: "C:/runtime-shim;C:/parent/bin",
3942
+ CUSTOM_VALUE: "retained",
3943
+ CODEX_HOME: "C:/project/.codex-runtime",
3944
+ CODEX_SQLITE_HOME: "C:/project/.codex-sqlite",
3945
+ OMX_ROOT: "C:/project/.omx-run",
3946
+ OMX_CODEX_LAUNCH_ID: "launch-id",
3947
+ OMX_NOTIFY_TEMP_CONTRACT: "{\"active\":true}",
3948
+ OMX_TEAM_WORKER_LAUNCH_ARGS: "[\"--model\",\"gpt-5\"]",
3949
+ TMUX: "foreign",
3950
+ TMUX_PANE: "%9",
3951
+ TERM: "xterm-256color",
3952
+ });
3953
+ const command = steps[0]?.args.at(-1) ?? "";
3954
+ const encodedPayload = command.match(/__detached-session-leader ''([^']+)''/)?.[1];
3955
+ assert.ok(encodedPayload);
3956
+ const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
3957
+ assert.deepEqual(payload.parentEnv, {
3958
+ CODEX_HOME: "C:/project/.codex-runtime",
3959
+ CODEX_SQLITE_HOME: "C:/project/.codex-sqlite",
3960
+ CUSTOM_VALUE: "retained",
3961
+ OMX_CODEX_LAUNCH_ID: "launch-id",
3962
+ OMX_NOTIFY_TEMP_CONTRACT: "{\"active\":true}",
3963
+ OMX_TEAM_WORKER_LAUNCH_ARGS: "[\"--model\",\"gpt-5\"]",
3964
+ PATH: "C:/runtime-shim;C:/parent/bin",
3965
+ });
3966
+ });
3967
+ });
3956
3968
  describe("buildTmuxSessionName", () => {
3957
3969
  it("uses detached fallback quietly outside git repos", () => {
3958
3970
  const name = buildTmuxSessionName("/tmp/My Repo", "omx-1770992424158-abc123");
@@ -4256,4 +4268,153 @@ describe("isExistingTmuxWindowTooCrampedForLaunchHud (#2754)", () => {
4256
4268
  assert.equal(isExistingTmuxWindowTooCrampedForLaunchHud(39, 40), true);
4257
4269
  });
4258
4270
  });
4271
+ describe("detached control-plane binding", () => {
4272
+ it("bounds inside-tmux lifecycle propagation to the finite launch-owned control plane", async () => {
4273
+ const source = await readFile(join(repoRoot, "src", "cli", "index.ts"), "utf8");
4274
+ assert.match(source, /if \(launchPolicy === "inside-tmux"\) \{[\s\S]*?buildDetachedLaunchControlPlane\(/);
4275
+ assert.match(source, /const restoreInsideTmuxControlPlane = insideTmuxControlPlane\s*\? applyLaunchOwnedControlPlane\(insideTmuxControlPlane\)/);
4276
+ assert.match(source, /const codexBaseEnv = prependOmxRuntimeCommandShimToEnv\([\s\S]*?\.\.\.launchOwnedControlPlaneEnv/);
4277
+ assert.match(source, /const runtimeHookEnv = launchOwnedControlPlane\s*\?[\s\S]*?\.\.\.launchOwnedControlPlaneEnv/);
4278
+ });
4279
+ it("emits one stable assignment for every managed key and clears poisoned identity", () => {
4280
+ const env = {
4281
+ OMX_ROOT: "",
4282
+ OMX_STATE_ROOT: "",
4283
+ OMX_TEAM_STATE_ROOT: "",
4284
+ OMX_RUNS_DIR: "/foreign/runs",
4285
+ OMX_TEAM_LEADER_CWD: "/foreign",
4286
+ OMX_TEAM_WORKER: "foreign/worker-1",
4287
+ OMX_TEAM_INTERNAL_WORKER: "foreign/worker-1",
4288
+ };
4289
+ const controlPlane = buildDetachedLaunchControlPlane({ cwd: "/tmp/project", sessionId: "sess-binding", env });
4290
+ assert.deepEqual([...controlPlane.keys], [...DETACHED_LAUNCH_CONTROL_PLANE_KEYS]);
4291
+ assert.deepEqual(Object.keys(controlPlane.values), [...DETACHED_LAUNCH_CONTROL_PLANE_KEYS]);
4292
+ assert.equal(controlPlane.values.OMX_SESSION_ID, "sess-binding");
4293
+ assert.equal(controlPlane.values.OMX_TMUX_HUD_OWNER, "1");
4294
+ for (const key of [
4295
+ "CODEX_SESSION_ID", "SESSION_ID", "OMX_TMUX_HUD_LEADER_PANE", "OMX_ROOT", "OMX_STATE_ROOT",
4296
+ "OMX_TEAM_STATE_ROOT", "OMXBOX_ACTIVE", "OMX_SOURCE_CWD", "OMX_MADMAX_DETACHED_CONTEXT", "OMX_RUNS_DIR",
4297
+ "OMX_TEAM_LEADER_CWD", "OMX_TEAM_WORKER", "OMX_TEAM_INTERNAL_WORKER",
4298
+ ])
4299
+ assert.equal(controlPlane.values[key], "", `${key} must be cleared`);
4300
+ const steps = buildDetachedSessionBootstrapSteps("omx-binding", "/tmp/project", "codex", "hud", null, undefined, null, false, "sess-binding", undefined, undefined, undefined, env, undefined, undefined, undefined, undefined, undefined, undefined, controlPlane);
4301
+ const newSession = steps.find((step) => step.name === "new-session");
4302
+ assert.ok(newSession);
4303
+ const managedAssignments = newSession.args.filter((arg) => DETACHED_LAUNCH_CONTROL_PLANE_KEYS.some((key) => arg.startsWith(`${key}=`)));
4304
+ assert.equal(managedAssignments.length, DETACHED_LAUNCH_CONTROL_PLANE_KEYS.length);
4305
+ assert.deepEqual(managedAssignments.map((assignment) => assignment.slice(0, assignment.indexOf("="))), [...DETACHED_LAUNCH_CONTROL_PLANE_KEYS]);
4306
+ });
4307
+ it("preserves selected explicit roots and valid Madmax runs identity", () => {
4308
+ const explicit = buildDetachedLaunchControlPlane({
4309
+ cwd: "/tmp/project", sessionId: "sess-explicit",
4310
+ env: { OMX_ROOT: "/tmp/selected-root", OMX_STATE_ROOT: "/tmp/stale-state" },
4311
+ });
4312
+ assert.equal(explicit.values.OMX_ROOT, "/tmp/selected-root");
4313
+ assert.equal(explicit.values.OMX_STATE_ROOT, "");
4314
+ assert.equal(explicit.values.OMX_RUNS_DIR, "");
4315
+ const madmax = buildDetachedLaunchControlPlane({
4316
+ cwd: "/tmp/project", sessionId: "sess-madmax",
4317
+ omxRootOverride: "/tmp/run-root",
4318
+ verifiedMadmaxContext: {
4319
+ root: "/tmp/run-root",
4320
+ sourceCwd: "/tmp/project",
4321
+ context: "ctx-madmax",
4322
+ runsRoot: "/tmp/outer-runs",
4323
+ },
4324
+ env: {
4325
+ OMX_ROOT: "/tmp/run-root", OMXBOX_ACTIVE: "1", OMX_SOURCE_CWD: "/tmp/project",
4326
+ OMX_MADMAX_DETACHED_CONTEXT: "ctx-madmax", OMX_RUNS_DIR: "/tmp/outer-runs",
4327
+ },
4328
+ });
4329
+ assert.deepEqual([madmax.values.OMX_ROOT, madmax.values.OMXBOX_ACTIVE, madmax.values.OMX_SOURCE_CWD,
4330
+ madmax.values.OMX_MADMAX_DETACHED_CONTEXT, madmax.values.OMX_RUNS_DIR], ["/tmp/run-root", "1", "/tmp/project", "ctx-madmax", "/tmp/outer-runs"]);
4331
+ const poisoned = buildDetachedLaunchControlPlane({
4332
+ cwd: "/tmp/project", sessionId: "sess-poisoned", omxRootOverride: "/foreign/root",
4333
+ env: {
4334
+ OMX_ROOT: "/foreign/root", OMXBOX_ACTIVE: "1", OMX_SOURCE_CWD: "/foreign/source",
4335
+ OMX_MADMAX_DETACHED_CONTEXT: "poisoned-context", OMX_RUNS_DIR: "/foreign/runs",
4336
+ },
4337
+ });
4338
+ assert.deepEqual([poisoned.values.OMX_ROOT, poisoned.values.OMX_SOURCE_CWD,
4339
+ poisoned.values.OMX_MADMAX_DETACHED_CONTEXT, poisoned.values.OMX_RUNS_DIR], ["", "", "", ""]);
4340
+ });
4341
+ it("preserves an explicit team root while clearing unverified boxed lineage and worker tuple", () => {
4342
+ const cases = [
4343
+ {
4344
+ name: "boxed guard without worker tuple",
4345
+ env: {
4346
+ OMX_TEAM_STATE_ROOT: "/tmp/explicit-team-root",
4347
+ OMX_ROOT: "/tmp/poison-root",
4348
+ OMX_STATE_ROOT: "/tmp/poison-state-root",
4349
+ OMXBOX_ACTIVE: "1",
4350
+ OMX_SOURCE_CWD: "/tmp/poison-source",
4351
+ OMX_MADMAX_DETACHED_CONTEXT: "poison-context",
4352
+ OMX_RUNS_DIR: "/tmp/poison-runs",
4353
+ },
4354
+ },
4355
+ {
4356
+ name: "boxed guard with poisoned worker tuple",
4357
+ env: {
4358
+ OMX_TEAM_STATE_ROOT: "/tmp/explicit-team-root",
4359
+ OMX_ROOT: "/tmp/poison-root",
4360
+ OMX_STATE_ROOT: "/tmp/poison-state-root",
4361
+ OMXBOX_ACTIVE: "1",
4362
+ OMX_SOURCE_CWD: "/tmp/poison-source",
4363
+ OMX_MADMAX_DETACHED_CONTEXT: "poison-context",
4364
+ OMX_RUNS_DIR: "/tmp/poison-runs",
4365
+ OMX_TEAM_LEADER_CWD: "/tmp/poison-leader",
4366
+ OMX_TEAM_WORKER: "poison-team/worker-1",
4367
+ OMX_TEAM_INTERNAL_WORKER: "poison-team/worker-2",
4368
+ },
4369
+ },
4370
+ ];
4371
+ for (const testCase of cases) {
4372
+ const controlPlane = buildDetachedLaunchControlPlane({
4373
+ cwd: "/tmp/project",
4374
+ sessionId: `sess-${testCase.name.replaceAll(" ", "-")}`,
4375
+ env: testCase.env,
4376
+ });
4377
+ assert.equal(controlPlane.values.OMX_TEAM_STATE_ROOT, "/tmp/explicit-team-root", testCase.name);
4378
+ for (const key of [
4379
+ "OMX_ROOT", "OMX_STATE_ROOT", "OMXBOX_ACTIVE", "OMX_SOURCE_CWD",
4380
+ "OMX_MADMAX_DETACHED_CONTEXT", "OMX_RUNS_DIR", "OMX_TEAM_LEADER_CWD",
4381
+ "OMX_TEAM_WORKER", "OMX_TEAM_INTERNAL_WORKER",
4382
+ ]) {
4383
+ assert.equal(controlPlane.values[key], "", `${testCase.name}: ${key} must be cleared`);
4384
+ }
4385
+ }
4386
+ });
4387
+ it("keeps unrelated parent configuration while filtering exact managed replay names", () => {
4388
+ const serialized = serializeDetachedSessionParentEnv({
4389
+ OMX_ROOT: "/foreign/root", omx_root: "/foreign/mixed-case-root",
4390
+ OMX_TEAM_WORKER: "foreign/worker", Omx_Notify_Fallback: "1",
4391
+ PROVIDER_SENTINEL: "provider-value", Path: "/system/path",
4392
+ });
4393
+ assert.doesNotMatch(serialized, /export OMX_ROOT=/);
4394
+ assert.doesNotMatch(serialized, /export OMX_TEAM_WORKER=/);
4395
+ assert.match(serialized, /export omx_root='/);
4396
+ assert.match(serialized, /export PROVIDER_SENTINEL='provider-value'/);
4397
+ assert.match(serialized, /export Path='\/system\/path'/);
4398
+ assert.match(serialized, /export Omx_Notify_Fallback='1'/);
4399
+ });
4400
+ it("filters every managed Windows case variant while preserving unrelated configuration", () => {
4401
+ const env = {
4402
+ oMx_RoOt: "/foreign/root",
4403
+ OmX_TeAm_WorkEr: "foreign/worker",
4404
+ OMX_PROVIDER_URL: "https://provider.invalid",
4405
+ Path: "/system/path",
4406
+ Omx_Notify_Fallback: "1",
4407
+ };
4408
+ const steps = buildDetachedSessionBootstrapSteps("omx-windows-binding", "/tmp/project", "codex", "hud", null, undefined, null, true, "sess-windows", undefined, undefined, undefined, env);
4409
+ const command = steps[0]?.args.at(-1) ?? "";
4410
+ const encoded = /__detached-session-leader [^A-Za-z0-9_-]*([A-Za-z0-9_-]{40,})/.exec(command)?.[1];
4411
+ assert.ok(encoded);
4412
+ const payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
4413
+ assert.equal(payload.parentEnv?.oMx_RoOt, undefined);
4414
+ assert.equal(payload.parentEnv?.OmX_TeAm_WorkEr, undefined);
4415
+ assert.equal(payload.parentEnv?.OMX_PROVIDER_URL, "https://provider.invalid");
4416
+ assert.equal(payload.parentEnv?.Path, "/system/path");
4417
+ assert.equal(payload.parentEnv?.Omx_Notify_Fallback, "1");
4418
+ });
4419
+ });
4259
4420
  //# sourceMappingURL=index.test.js.map