oh-my-codex 0.20.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1126) hide show
  1. package/Cargo.lock +35 -18
  2. package/Cargo.toml +1 -1
  3. package/README.md +22 -19
  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/agents/__tests__/definitions.test.js +0 -27
  95. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  96. package/dist/agents/__tests__/native-config.test.js +3 -8
  97. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  98. package/dist/agents/definitions.d.ts.map +1 -1
  99. package/dist/agents/definitions.js +0 -41
  100. package/dist/agents/definitions.js.map +1 -1
  101. package/dist/agents/policy.d.ts.map +1 -1
  102. package/dist/agents/policy.js +0 -1
  103. package/dist/agents/policy.js.map +1 -1
  104. package/dist/auth/__tests__/hotswap.test.js +60 -1
  105. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  106. package/dist/auth/__tests__/storage.test.js +33 -1
  107. package/dist/auth/__tests__/storage.test.js.map +1 -1
  108. package/dist/auth/hotswap.d.ts +16 -2
  109. package/dist/auth/hotswap.d.ts.map +1 -1
  110. package/dist/auth/hotswap.js +22 -7
  111. package/dist/auth/hotswap.js.map +1 -1
  112. package/dist/auth/storage.js +1 -1
  113. package/dist/auth/storage.js.map +1 -1
  114. package/dist/autopilot/__tests__/completion-gate-advisory.test.d.ts +2 -0
  115. package/dist/autopilot/__tests__/completion-gate-advisory.test.d.ts.map +1 -0
  116. package/dist/autopilot/__tests__/completion-gate-advisory.test.js +383 -0
  117. package/dist/autopilot/__tests__/completion-gate-advisory.test.js.map +1 -0
  118. package/dist/autopilot/completion-gate.d.ts +6 -1
  119. package/dist/autopilot/completion-gate.d.ts.map +1 -1
  120. package/dist/autopilot/completion-gate.js +247 -7
  121. package/dist/autopilot/completion-gate.js.map +1 -1
  122. package/dist/catalog/__tests__/generator.test.js +15 -10
  123. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  124. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +13 -7
  125. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  126. package/dist/catalog/__tests__/schema.test.js +3 -5
  127. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  128. package/dist/catalog/schema.js +1 -1
  129. package/dist/catalog/schema.js.map +1 -1
  130. package/dist/cli/__tests__/agents-init.test.js +4 -23
  131. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  132. package/dist/cli/__tests__/api.test.js +61 -24
  133. package/dist/cli/__tests__/api.test.js.map +1 -1
  134. package/dist/cli/__tests__/auth.test.js +3 -1
  135. package/dist/cli/__tests__/auth.test.js.map +1 -1
  136. package/dist/cli/__tests__/autopilot.test.d.ts +2 -0
  137. package/dist/cli/__tests__/autopilot.test.d.ts.map +1 -0
  138. package/dist/cli/__tests__/autopilot.test.js +143 -0
  139. package/dist/cli/__tests__/autopilot.test.js.map +1 -0
  140. package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
  141. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
  142. package/dist/cli/__tests__/codex-plugin-layout.test.js +76 -4
  143. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  144. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
  145. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
  146. package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
  147. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
  148. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
  149. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
  150. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
  151. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
  152. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
  153. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
  154. package/dist/cli/__tests__/doctor-state-root-binding.test.js +206 -0
  155. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
  156. package/dist/cli/__tests__/doctor-team.test.js +85 -0
  157. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  158. package/dist/cli/__tests__/doctor-warning-copy.test.js +38 -6
  159. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  160. package/dist/cli/__tests__/exec.test.js +75 -0
  161. package/dist/cli/__tests__/exec.test.js.map +1 -1
  162. package/dist/cli/__tests__/index.test.js +621 -460
  163. package/dist/cli/__tests__/index.test.js.map +1 -1
  164. package/dist/cli/__tests__/launch-fallback.test.js +1125 -143
  165. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  166. package/dist/cli/__tests__/mcp-parity.test.js +4 -3
  167. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  168. package/dist/cli/__tests__/native-assets.test.js +398 -16
  169. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  170. package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
  171. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  172. package/dist/cli/__tests__/package-bin-contract.test.js +5 -8
  173. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  174. package/dist/cli/__tests__/ralph-deslop-contract.test.js +4 -18
  175. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  176. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +4 -30
  177. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  178. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +4 -15
  179. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  180. package/dist/cli/__tests__/ralph.test.js +2 -2
  181. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  182. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
  183. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  184. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
  185. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
  186. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
  187. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
  188. package/dist/cli/__tests__/ralplan.test.js +131 -14
  189. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  190. package/dist/cli/__tests__/session-scoped-runtime.test.js +1153 -43
  191. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  192. package/dist/cli/__tests__/session-search-help.test.js +65 -3
  193. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  194. package/dist/cli/__tests__/setup-agents-overwrite.test.js +57 -55
  195. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  196. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +34 -0
  197. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  198. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +1 -1
  199. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -1
  200. package/dist/cli/__tests__/setup-install-mode.test.js +258 -26
  201. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  202. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +1 -1
  203. package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
  204. package/dist/cli/__tests__/setup-refresh.test.js +3 -6
  205. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  206. package/dist/cli/__tests__/setup-skills-overwrite.test.js +180 -21
  207. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  208. package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
  209. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  210. package/dist/cli/__tests__/team.test.js +45 -1
  211. package/dist/cli/__tests__/team.test.js.map +1 -1
  212. package/dist/cli/__tests__/ultragoal.test.js +161 -2
  213. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  214. package/dist/cli/__tests__/uninstall.test.js +73 -28
  215. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  216. package/dist/cli/__tests__/update.test.js +749 -177
  217. package/dist/cli/__tests__/update.test.js.map +1 -1
  218. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
  219. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  220. package/dist/cli/api.d.ts.map +1 -1
  221. package/dist/cli/api.js +9 -10
  222. package/dist/cli/api.js.map +1 -1
  223. package/dist/cli/autopilot.d.ts +8 -0
  224. package/dist/cli/autopilot.d.ts.map +1 -0
  225. package/dist/cli/autopilot.js +160 -0
  226. package/dist/cli/autopilot.js.map +1 -0
  227. package/dist/cli/codex-feature-probe.d.ts +16 -0
  228. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  229. package/dist/cli/codex-feature-probe.js +44 -0
  230. package/dist/cli/codex-feature-probe.js.map +1 -1
  231. package/dist/cli/codex-home.d.ts.map +1 -1
  232. package/dist/cli/codex-home.js +7 -4
  233. package/dist/cli/codex-home.js.map +1 -1
  234. package/dist/cli/doctor.d.ts +29 -0
  235. package/dist/cli/doctor.d.ts.map +1 -1
  236. package/dist/cli/doctor.js +433 -19
  237. package/dist/cli/doctor.js.map +1 -1
  238. package/dist/cli/index.d.ts +231 -12
  239. package/dist/cli/index.d.ts.map +1 -1
  240. package/dist/cli/index.js +2354 -724
  241. package/dist/cli/index.js.map +1 -1
  242. package/dist/cli/mcp-parity.d.ts.map +1 -1
  243. package/dist/cli/mcp-parity.js +7 -3
  244. package/dist/cli/mcp-parity.js.map +1 -1
  245. package/dist/cli/native-assets.d.ts +26 -1
  246. package/dist/cli/native-assets.d.ts.map +1 -1
  247. package/dist/cli/native-assets.js +448 -51
  248. package/dist/cli/native-assets.js.map +1 -1
  249. package/dist/cli/native-hook-claim-journal.d.ts +8 -4
  250. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  251. package/dist/cli/native-hook-claim-journal.js +30 -8
  252. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  253. package/dist/cli/package-manager-ownership.d.ts +69 -0
  254. package/dist/cli/package-manager-ownership.d.ts.map +1 -0
  255. package/dist/cli/package-manager-ownership.js +338 -0
  256. package/dist/cli/package-manager-ownership.js.map +1 -0
  257. package/dist/cli/plugin-marketplace.d.ts +1 -0
  258. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  259. package/dist/cli/plugin-marketplace.js +123 -55
  260. package/dist/cli/plugin-marketplace.js.map +1 -1
  261. package/dist/cli/ralph.js +2 -2
  262. package/dist/cli/ralph.js.map +1 -1
  263. package/dist/cli/ralplan.d.ts +6 -2
  264. package/dist/cli/ralplan.d.ts.map +1 -1
  265. package/dist/cli/ralplan.js +134 -34
  266. package/dist/cli/ralplan.js.map +1 -1
  267. package/dist/cli/session-search.d.ts.map +1 -1
  268. package/dist/cli/session-search.js +38 -1
  269. package/dist/cli/session-search.js.map +1 -1
  270. package/dist/cli/setup-preferences.d.ts +15 -0
  271. package/dist/cli/setup-preferences.d.ts.map +1 -1
  272. package/dist/cli/setup-preferences.js +27 -1
  273. package/dist/cli/setup-preferences.js.map +1 -1
  274. package/dist/cli/setup.d.ts +6 -0
  275. package/dist/cli/setup.d.ts.map +1 -1
  276. package/dist/cli/setup.js +425 -215
  277. package/dist/cli/setup.js.map +1 -1
  278. package/dist/cli/sparkshell.d.ts.map +1 -1
  279. package/dist/cli/sparkshell.js +52 -9
  280. package/dist/cli/sparkshell.js.map +1 -1
  281. package/dist/cli/ultragoal.d.ts +1 -1
  282. package/dist/cli/ultragoal.d.ts.map +1 -1
  283. package/dist/cli/ultragoal.js +10 -7
  284. package/dist/cli/ultragoal.js.map +1 -1
  285. package/dist/cli/uninstall.d.ts.map +1 -1
  286. package/dist/cli/uninstall.js +27 -28
  287. package/dist/cli/uninstall.js.map +1 -1
  288. package/dist/cli/update-worker.d.ts +2 -0
  289. package/dist/cli/update-worker.d.ts.map +1 -0
  290. package/dist/cli/update-worker.js +142 -0
  291. package/dist/cli/update-worker.js.map +1 -0
  292. package/dist/cli/update.d.ts +16 -23
  293. package/dist/cli/update.d.ts.map +1 -1
  294. package/dist/cli/update.js +212 -241
  295. package/dist/cli/update.js.map +1 -1
  296. package/dist/compat/__tests__/upgrade-from-0-20.test.d.ts +2 -0
  297. package/dist/compat/__tests__/upgrade-from-0-20.test.d.ts.map +1 -0
  298. package/dist/compat/__tests__/upgrade-from-0-20.test.js +109 -0
  299. package/dist/compat/__tests__/upgrade-from-0-20.test.js.map +1 -0
  300. package/dist/compat/upgrade-from-0-20.d.ts +64 -0
  301. package/dist/compat/upgrade-from-0-20.d.ts.map +1 -0
  302. package/dist/compat/upgrade-from-0-20.js +276 -0
  303. package/dist/compat/upgrade-from-0-20.js.map +1 -0
  304. package/dist/config/__tests__/generator-idempotent.test.js +173 -29
  305. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  306. package/dist/config/__tests__/generator-notify.test.js +3 -1
  307. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  308. package/dist/config/generator.d.ts +25 -3
  309. package/dist/config/generator.d.ts.map +1 -1
  310. package/dist/config/generator.js +113 -35
  311. package/dist/config/generator.js.map +1 -1
  312. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
  313. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  314. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
  315. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  316. package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
  317. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  318. package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
  319. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  320. package/dist/hooks/__tests__/analyze-skill-contract.test.js +24 -31
  321. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  322. package/dist/hooks/__tests__/anti-slop-workflow.test.js +66 -135
  323. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  324. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +47 -106
  325. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  326. package/dist/hooks/__tests__/code-review-skill-contract.test.js +16 -18
  327. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  328. package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +2 -14
  329. package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts.map +1 -1
  330. package/dist/hooks/__tests__/consensus-execution-handoff.test.js +56 -234
  331. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  332. package/dist/hooks/__tests__/deep-interview-contract.test.js +1 -3
  333. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  334. package/dist/hooks/__tests__/design-skill.test.js +18 -16
  335. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  336. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +24 -20
  337. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
  338. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
  339. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
  340. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +164 -0
  341. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
  342. package/dist/hooks/__tests__/issue-3497-hook-simplification.test.d.ts +2 -0
  343. package/dist/hooks/__tests__/issue-3497-hook-simplification.test.d.ts.map +1 -0
  344. package/dist/hooks/__tests__/issue-3497-hook-simplification.test.js +169 -0
  345. package/dist/hooks/__tests__/issue-3497-hook-simplification.test.js.map +1 -0
  346. package/dist/hooks/__tests__/keyword-detector.test.js +226 -858
  347. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  348. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +176 -14
  349. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  350. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
  351. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  352. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +33 -21
  353. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  354. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
  355. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  356. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
  357. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  358. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +14 -904
  359. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  360. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
  361. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  362. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +5 -5
  363. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  364. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +7 -7
  365. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  366. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +11 -8
  367. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  368. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +7 -6
  369. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  370. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
  371. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
  372. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
  373. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  374. package/dist/hooks/__tests__/pre-context-gate-skills.test.js +19 -21
  375. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  376. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +23 -3
  377. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  378. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +23 -28
  379. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  380. package/dist/hooks/__tests__/research-workflow-boundaries.test.js +9 -8
  381. package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -1
  382. package/dist/hooks/__tests__/session.test.js +3656 -656
  383. package/dist/hooks/__tests__/session.test.js.map +1 -1
  384. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +13 -22
  385. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  386. package/dist/hooks/__tests__/skill-guidance-contract.test.js +3 -13
  387. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  388. package/dist/hooks/__tests__/sunset-routing-contract.test.d.ts +2 -0
  389. package/dist/hooks/__tests__/sunset-routing-contract.test.d.ts.map +1 -0
  390. package/dist/hooks/__tests__/sunset-routing-contract.test.js +172 -0
  391. package/dist/hooks/__tests__/sunset-routing-contract.test.js.map +1 -0
  392. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js +2 -2
  393. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  394. package/dist/hooks/__tests__/tmux-hook-engine.test.js +18 -3
  395. package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
  396. package/dist/hooks/__tests__/visual-ralph-skill.test.js +20 -17
  397. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  398. package/dist/hooks/__tests__/visual-verdict-loop.test.js +17 -18
  399. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  400. package/dist/hooks/agents-overlay.js +1 -1
  401. package/dist/hooks/agents-overlay.js.map +1 -1
  402. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
  403. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  404. package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
  405. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  406. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  407. package/dist/hooks/extensibility/dispatcher.js +20 -3
  408. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  409. package/dist/hooks/extensibility/plugin-runner.js +1 -0
  410. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  411. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  412. package/dist/hooks/extensibility/runtime.js.map +1 -1
  413. package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
  414. package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
  415. package/dist/hooks/extensibility/sdk/paths.js +2 -2
  416. package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
  417. package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
  418. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  419. package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
  420. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  421. package/dist/hooks/extensibility/sdk.d.ts +1 -0
  422. package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
  423. package/dist/hooks/extensibility/sdk.js +1 -1
  424. package/dist/hooks/extensibility/sdk.js.map +1 -1
  425. package/dist/hooks/extensibility/types.d.ts +7 -0
  426. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  427. package/dist/hooks/keyword-detector.d.ts +6 -47
  428. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  429. package/dist/hooks/keyword-detector.js +156 -268
  430. package/dist/hooks/keyword-detector.js.map +1 -1
  431. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  432. package/dist/hooks/keyword-registry.js +14 -26
  433. package/dist/hooks/keyword-registry.js.map +1 -1
  434. package/dist/hooks/native/capability-warnings.d.ts +16 -0
  435. package/dist/hooks/native/capability-warnings.d.ts.map +1 -0
  436. package/dist/hooks/native/capability-warnings.js +40 -0
  437. package/dist/hooks/native/capability-warnings.js.map +1 -0
  438. package/dist/hooks/native/index.d.ts +17 -0
  439. package/dist/hooks/native/index.d.ts.map +1 -0
  440. package/dist/hooks/native/index.js +17 -0
  441. package/dist/hooks/native/index.js.map +1 -0
  442. package/dist/hooks/native/pre-tool-use-advisory.d.ts +17 -0
  443. package/dist/hooks/native/pre-tool-use-advisory.d.ts.map +1 -0
  444. package/dist/hooks/native/pre-tool-use-advisory.js +112 -0
  445. package/dist/hooks/native/pre-tool-use-advisory.js.map +1 -0
  446. package/dist/hooks/native/types.d.ts +30 -0
  447. package/dist/hooks/native/types.d.ts.map +1 -0
  448. package/dist/hooks/native/types.js +22 -0
  449. package/dist/hooks/native/types.js.map +1 -0
  450. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  451. package/dist/hooks/prompt-guidance-contract.js +46 -93
  452. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  453. package/dist/hooks/session.d.ts +197 -18
  454. package/dist/hooks/session.d.ts.map +1 -1
  455. package/dist/hooks/session.js +1939 -271
  456. package/dist/hooks/session.js.map +1 -1
  457. package/dist/hooks/sunset-stub.d.ts +17 -0
  458. package/dist/hooks/sunset-stub.d.ts.map +1 -0
  459. package/dist/hooks/sunset-stub.js +125 -0
  460. package/dist/hooks/sunset-stub.js.map +1 -0
  461. package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
  462. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  463. package/dist/hud/__tests__/index.test.js +26 -16
  464. package/dist/hud/__tests__/index.test.js.map +1 -1
  465. package/dist/hud/__tests__/reconcile.test.js +83 -78
  466. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  467. package/dist/hud/__tests__/state.test.js +36 -31
  468. package/dist/hud/__tests__/state.test.js.map +1 -1
  469. package/dist/hud/__tests__/tmux-split-realtmux.test.d.ts +2 -0
  470. package/dist/hud/__tests__/tmux-split-realtmux.test.d.ts.map +1 -0
  471. package/dist/hud/__tests__/tmux-split-realtmux.test.js +112 -0
  472. package/dist/hud/__tests__/tmux-split-realtmux.test.js.map +1 -0
  473. package/dist/hud/__tests__/tmux.test.js +217 -198
  474. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  475. package/dist/hud/tmux.d.ts +43 -4
  476. package/dist/hud/tmux.d.ts.map +1 -1
  477. package/dist/hud/tmux.js +1025 -139
  478. package/dist/hud/tmux.js.map +1 -1
  479. package/dist/leader/__tests__/contract.test.js +71 -34
  480. package/dist/leader/__tests__/contract.test.js.map +1 -1
  481. package/dist/leader/contract.d.ts +1 -6
  482. package/dist/leader/contract.d.ts.map +1 -1
  483. package/dist/leader/contract.js +25 -24
  484. package/dist/leader/contract.js.map +1 -1
  485. package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
  486. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  487. package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
  488. package/dist/mcp/__tests__/state-paths.test.js +735 -4
  489. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  490. package/dist/mcp/__tests__/state-server-ralph-phase.test.js +29 -4
  491. package/dist/mcp/__tests__/state-server-ralph-phase.test.js.map +1 -1
  492. package/dist/mcp/__tests__/state-server-schema.test.js +13 -3
  493. package/dist/mcp/__tests__/state-server-schema.test.js.map +1 -1
  494. package/dist/mcp/__tests__/state-server.test.js +11 -1033
  495. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  496. package/dist/mcp/__tests__/trace-server.test.js +2 -2
  497. package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
  498. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  499. package/dist/mcp/hermes-bridge.js +2 -2
  500. package/dist/mcp/hermes-bridge.js.map +1 -1
  501. package/dist/mcp/state-paths.d.ts +78 -4
  502. package/dist/mcp/state-paths.d.ts.map +1 -1
  503. package/dist/mcp/state-paths.js +543 -40
  504. package/dist/mcp/state-paths.js.map +1 -1
  505. package/dist/mcp/state-server.d.ts +68 -97
  506. package/dist/mcp/state-server.d.ts.map +1 -1
  507. package/dist/mcp/state-server.js +116 -67
  508. package/dist/mcp/state-server.js.map +1 -1
  509. package/dist/modes/__tests__/base-autopilot-gates.test.js +1 -168
  510. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  511. package/dist/modes/__tests__/base-autoresearch-contract.test.js +1 -85
  512. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  513. package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -35
  514. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
  515. package/dist/modes/__tests__/base-session-scope.test.js +10 -158
  516. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  517. package/dist/modes/base.d.ts +6 -6
  518. package/dist/modes/base.d.ts.map +1 -1
  519. package/dist/modes/base.js +116 -90
  520. package/dist/modes/base.js.map +1 -1
  521. package/dist/native-assets/archive.d.ts +17 -0
  522. package/dist/native-assets/archive.d.ts.map +1 -0
  523. package/dist/native-assets/archive.js +238 -0
  524. package/dist/native-assets/archive.js.map +1 -0
  525. package/dist/native-assets/policy.d.ts +42 -0
  526. package/dist/native-assets/policy.d.ts.map +1 -0
  527. package/dist/native-assets/policy.js +155 -0
  528. package/dist/native-assets/policy.js.map +1 -0
  529. package/dist/notifications/__tests__/formatter.test.js +8 -0
  530. package/dist/notifications/__tests__/formatter.test.js.map +1 -1
  531. package/dist/notifications/__tests__/template-engine.test.js +8 -0
  532. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  533. package/dist/notifications/formatter.js +1 -1
  534. package/dist/notifications/formatter.js.map +1 -1
  535. package/dist/notifications/template-engine.js +1 -1
  536. package/dist/notifications/template-engine.js.map +1 -1
  537. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +0 -21
  538. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  539. package/dist/question/autopilot-wait.d.ts.map +1 -1
  540. package/dist/question/autopilot-wait.js +2 -2
  541. package/dist/question/autopilot-wait.js.map +1 -1
  542. package/dist/question/deep-interview.d.ts +1 -2
  543. package/dist/question/deep-interview.d.ts.map +1 -1
  544. package/dist/question/deep-interview.js +4 -4
  545. package/dist/question/deep-interview.js.map +1 -1
  546. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
  547. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
  548. package/dist/ralplan/__tests__/runtime.test.js +56 -821
  549. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  550. package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
  551. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  552. package/dist/ralplan/documented-leader-preflight.js +356 -54
  553. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  554. package/dist/ralplan/runtime.d.ts +2 -2
  555. package/dist/ralplan/runtime.d.ts.map +1 -1
  556. package/dist/ralplan/runtime.js +145 -163
  557. package/dist/ralplan/runtime.js.map +1 -1
  558. package/dist/runtime/bridge.d.ts.map +1 -1
  559. package/dist/runtime/bridge.js +45 -0
  560. package/dist/runtime/bridge.js.map +1 -1
  561. package/dist/runtime/run-state.d.ts +5 -1
  562. package/dist/runtime/run-state.d.ts.map +1 -1
  563. package/dist/runtime/run-state.js +15 -8
  564. package/dist/runtime/run-state.js.map +1 -1
  565. package/dist/scripts/__tests__/codex-native-hook.test.js +12490 -19414
  566. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  567. package/dist/scripts/__tests__/docs-site-contract.test.js +2 -2
  568. package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -1
  569. package/dist/scripts/__tests__/generated-artifact-drift.test.d.ts +2 -0
  570. package/dist/scripts/__tests__/generated-artifact-drift.test.d.ts.map +1 -0
  571. package/dist/scripts/__tests__/generated-artifact-drift.test.js +84 -0
  572. package/dist/scripts/__tests__/generated-artifact-drift.test.js.map +1 -0
  573. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
  574. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
  575. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +235 -0
  576. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
  577. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
  578. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
  579. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
  580. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
  581. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
  582. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
  583. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +207 -0
  584. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
  585. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
  586. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
  587. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +156 -0
  588. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
  589. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
  590. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
  591. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +191 -0
  592. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
  593. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
  594. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
  595. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +40 -0
  596. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
  597. package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.d.ts +2 -0
  598. package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.d.ts.map +1 -0
  599. package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.js +63 -0
  600. package/dist/scripts/__tests__/issue-3481-conductor-deadlock.test.js.map +1 -0
  601. package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.d.ts +2 -0
  602. package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.d.ts.map +1 -0
  603. package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.js +65 -0
  604. package/dist/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.js.map +1 -0
  605. package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.d.ts +2 -0
  606. package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.d.ts.map +1 -0
  607. package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.js +189 -0
  608. package/dist/scripts/__tests__/issue-3536-external-team-state-root.test.js.map +1 -0
  609. package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
  610. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  611. package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
  612. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
  613. package/dist/scripts/__tests__/postinstall.test.js +19 -1
  614. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  615. package/dist/scripts/__tests__/prompt-inventory.test.js +54 -1
  616. package/dist/scripts/__tests__/prompt-inventory.test.js.map +1 -1
  617. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +2 -2
  618. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -1
  619. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +46 -60
  620. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  621. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
  622. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  623. package/dist/scripts/__tests__/smoke-packed-install.test.js +168 -20
  624. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  625. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
  626. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
  627. package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
  628. package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
  629. package/dist/scripts/codex-native-hook.d.ts +51 -2
  630. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  631. package/dist/scripts/codex-native-hook.js +3648 -1332
  632. package/dist/scripts/codex-native-hook.js.map +1 -1
  633. package/dist/scripts/codex-native-pre-post.d.ts +9 -0
  634. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  635. package/dist/scripts/codex-native-pre-post.js +33 -22
  636. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  637. package/dist/scripts/generate-native-release-manifest.js +80 -51
  638. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  639. package/dist/scripts/notify-fallback-watcher.js +28 -39
  640. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  641. package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -48
  642. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  643. package/dist/scripts/notify-hook/auto-nudge.js +1 -0
  644. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  645. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  646. package/dist/scripts/notify-hook/state-io.js +5 -0
  647. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  648. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  649. package/dist/scripts/notify-hook/team-dispatch.js +8 -5
  650. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  651. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  652. package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
  653. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  654. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
  655. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  656. package/dist/scripts/notify-hook/team-tmux-guard.js +3 -2
  657. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  658. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  659. package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
  660. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  661. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  662. package/dist/scripts/notify-hook/team-worker.js +45 -5
  663. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  664. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
  665. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  666. package/dist/scripts/notify-hook/tmux-injection.js +4 -1
  667. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  668. package/dist/scripts/notify-hook.d.ts.map +1 -1
  669. package/dist/scripts/notify-hook.js +71 -124
  670. package/dist/scripts/notify-hook.js.map +1 -1
  671. package/dist/scripts/postinstall-advisory.d.ts +14 -0
  672. package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
  673. package/dist/scripts/postinstall-advisory.js +49 -0
  674. package/dist/scripts/postinstall-advisory.js.map +1 -0
  675. package/dist/scripts/postinstall.d.ts +1 -1
  676. package/dist/scripts/postinstall.d.ts.map +1 -1
  677. package/dist/scripts/postinstall.js +1 -1
  678. package/dist/scripts/postinstall.js.map +1 -1
  679. package/dist/scripts/prompt-inventory.d.ts +39 -0
  680. package/dist/scripts/prompt-inventory.d.ts.map +1 -1
  681. package/dist/scripts/prompt-inventory.js +108 -3
  682. package/dist/scripts/prompt-inventory.js.map +1 -1
  683. package/dist/scripts/run-compiled-ci.js +2 -0
  684. package/dist/scripts/run-compiled-ci.js.map +1 -1
  685. package/dist/scripts/run-test-files.js +1 -0
  686. package/dist/scripts/run-test-files.js.map +1 -1
  687. package/dist/scripts/smoke-packed-install.d.ts +32 -1
  688. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  689. package/dist/scripts/smoke-packed-install.js +407 -60
  690. package/dist/scripts/smoke-packed-install.js.map +1 -1
  691. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  692. package/dist/scripts/sync-plugin-mirror.js +16 -1
  693. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  694. package/dist/scripts/sync-prompt-guidance-fragments.js +62 -20
  695. package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
  696. package/dist/scripts/tmux-hook-engine.d.ts +1 -1
  697. package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
  698. package/dist/scripts/tmux-hook-engine.js +3 -3
  699. package/dist/scripts/tmux-hook-engine.js.map +1 -1
  700. package/dist/scripts/verify-capabilities-lock.d.ts +3 -0
  701. package/dist/scripts/verify-capabilities-lock.d.ts.map +1 -0
  702. package/dist/scripts/verify-capabilities-lock.js +22 -0
  703. package/dist/scripts/verify-capabilities-lock.js.map +1 -0
  704. package/dist/scripts/verify-native-release-assets.js +37 -23
  705. package/dist/scripts/verify-native-release-assets.js.map +1 -1
  706. package/dist/state/__tests__/authority-decreasing.test.d.ts +2 -0
  707. package/dist/state/__tests__/authority-decreasing.test.d.ts.map +1 -0
  708. package/dist/state/__tests__/authority-decreasing.test.js +78 -0
  709. package/dist/state/__tests__/authority-decreasing.test.js.map +1 -0
  710. package/dist/state/__tests__/handoff-carrier.test.d.ts +2 -0
  711. package/dist/state/__tests__/handoff-carrier.test.d.ts.map +1 -0
  712. package/dist/state/__tests__/handoff-carrier.test.js +96 -0
  713. package/dist/state/__tests__/handoff-carrier.test.js.map +1 -0
  714. package/dist/state/__tests__/mode-state-context.test.js +12 -5
  715. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  716. package/dist/state/__tests__/operations.test.js +870 -2517
  717. package/dist/state/__tests__/operations.test.js.map +1 -1
  718. package/dist/state/__tests__/single-writer-invariant.test.d.ts +2 -0
  719. package/dist/state/__tests__/single-writer-invariant.test.d.ts.map +1 -0
  720. package/dist/state/__tests__/single-writer-invariant.test.js +342 -0
  721. package/dist/state/__tests__/single-writer-invariant.test.js.map +1 -0
  722. package/dist/state/__tests__/skill-active.test.js +400 -2
  723. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  724. package/dist/state/__tests__/stale-state-neutralization.test.d.ts +2 -0
  725. package/dist/state/__tests__/stale-state-neutralization.test.d.ts.map +1 -0
  726. package/dist/state/__tests__/stale-state-neutralization.test.js +84 -0
  727. package/dist/state/__tests__/stale-state-neutralization.test.js.map +1 -0
  728. package/dist/state/__tests__/workflow-transition.test.js +20 -33
  729. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  730. package/dist/state/handoff-carrier.d.ts +46 -0
  731. package/dist/state/handoff-carrier.d.ts.map +1 -0
  732. package/dist/state/handoff-carrier.js +85 -0
  733. package/dist/state/handoff-carrier.js.map +1 -0
  734. package/dist/state/mode-state-context.d.ts +3 -0
  735. package/dist/state/mode-state-context.d.ts.map +1 -1
  736. package/dist/state/mode-state-context.js +27 -13
  737. package/dist/state/mode-state-context.js.map +1 -1
  738. package/dist/state/namespace-owners.d.ts +34 -0
  739. package/dist/state/namespace-owners.d.ts.map +1 -0
  740. package/dist/state/namespace-owners.js +127 -0
  741. package/dist/state/namespace-owners.js.map +1 -0
  742. package/dist/state/operations.d.ts +9 -4
  743. package/dist/state/operations.d.ts.map +1 -1
  744. package/dist/state/operations.js +194 -235
  745. package/dist/state/operations.js.map +1 -1
  746. package/dist/state/skill-active.d.ts +18 -1
  747. package/dist/state/skill-active.d.ts.map +1 -1
  748. package/dist/state/skill-active.js +421 -31
  749. package/dist/state/skill-active.js.map +1 -1
  750. package/dist/state/workflow-transition-reconcile.d.ts +3 -0
  751. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  752. package/dist/state/workflow-transition-reconcile.js +11 -31
  753. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  754. package/dist/state/workflow-transition.d.ts +1 -27
  755. package/dist/state/workflow-transition.d.ts.map +1 -1
  756. package/dist/state/workflow-transition.js +9 -790
  757. package/dist/state/workflow-transition.js.map +1 -1
  758. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
  759. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  760. package/dist/subagents/__tests__/tracker.test.js +837 -1048
  761. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  762. package/dist/subagents/tracker.d.ts +64 -60
  763. package/dist/subagents/tracker.d.ts.map +1 -1
  764. package/dist/subagents/tracker.js +809 -358
  765. package/dist/subagents/tracker.js.map +1 -1
  766. package/dist/team/__tests__/api-interop.test.js +1 -0
  767. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  768. package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
  769. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  770. package/dist/team/__tests__/mcp-comm.test.js +104 -3
  771. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  772. package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
  773. package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
  774. package/dist/team/__tests__/notice-ledger.test.js +131 -0
  775. package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
  776. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
  777. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
  778. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
  779. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
  780. package/dist/team/__tests__/phase-controller.test.js +20 -0
  781. package/dist/team/__tests__/phase-controller.test.js.map +1 -1
  782. package/dist/team/__tests__/runtime.test.js +725 -59
  783. package/dist/team/__tests__/runtime.test.js.map +1 -1
  784. package/dist/team/__tests__/scaling.test.js +269 -245
  785. package/dist/team/__tests__/scaling.test.js.map +1 -1
  786. package/dist/team/__tests__/state-root.test.js +117 -2
  787. package/dist/team/__tests__/state-root.test.js.map +1 -1
  788. package/dist/team/__tests__/team-ops-contract.test.js +1 -0
  789. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  790. package/dist/team/__tests__/tmux-session.test.js +1093 -101
  791. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  792. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
  793. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
  794. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +159 -0
  795. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
  796. package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
  797. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  798. package/dist/team/__tests__/tmux-test-fixture.js +242 -15
  799. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  800. package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
  801. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  802. package/dist/team/__tests__/worker-bootstrap.test.js +11 -13
  803. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  804. package/dist/team/__tests__/worker-provenance.test.d.ts +2 -0
  805. package/dist/team/__tests__/worker-provenance.test.d.ts.map +1 -0
  806. package/dist/team/__tests__/worker-provenance.test.js +251 -0
  807. package/dist/team/__tests__/worker-provenance.test.js.map +1 -0
  808. package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
  809. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  810. package/dist/team/api-interop.d.ts.map +1 -1
  811. package/dist/team/api-interop.js +14 -8
  812. package/dist/team/api-interop.js.map +1 -1
  813. package/dist/team/mcp-comm.d.ts.map +1 -1
  814. package/dist/team/mcp-comm.js +3 -8
  815. package/dist/team/mcp-comm.js.map +1 -1
  816. package/dist/team/notice-ledger.d.ts +54 -0
  817. package/dist/team/notice-ledger.d.ts.map +1 -0
  818. package/dist/team/notice-ledger.js +259 -0
  819. package/dist/team/notice-ledger.js.map +1 -0
  820. package/dist/team/phase-controller.d.ts.map +1 -1
  821. package/dist/team/phase-controller.js +2 -0
  822. package/dist/team/phase-controller.js.map +1 -1
  823. package/dist/team/runtime.d.ts +3 -1
  824. package/dist/team/runtime.d.ts.map +1 -1
  825. package/dist/team/runtime.js +314 -142
  826. package/dist/team/runtime.js.map +1 -1
  827. package/dist/team/scaling.d.ts.map +1 -1
  828. package/dist/team/scaling.js +161 -91
  829. package/dist/team/scaling.js.map +1 -1
  830. package/dist/team/state/monitor.d.ts +10 -0
  831. package/dist/team/state/monitor.d.ts.map +1 -1
  832. package/dist/team/state/monitor.js +13 -0
  833. package/dist/team/state/monitor.js.map +1 -1
  834. package/dist/team/state/types.d.ts +15 -0
  835. package/dist/team/state/types.d.ts.map +1 -1
  836. package/dist/team/state/types.js.map +1 -1
  837. package/dist/team/state-root.d.ts +12 -0
  838. package/dist/team/state-root.d.ts.map +1 -1
  839. package/dist/team/state-root.js +177 -0
  840. package/dist/team/state-root.js.map +1 -1
  841. package/dist/team/state.d.ts +19 -0
  842. package/dist/team/state.d.ts.map +1 -1
  843. package/dist/team/state.js +47 -1
  844. package/dist/team/state.js.map +1 -1
  845. package/dist/team/team-ops.d.ts +1 -0
  846. package/dist/team/team-ops.d.ts.map +1 -1
  847. package/dist/team/team-ops.js +1 -0
  848. package/dist/team/team-ops.js.map +1 -1
  849. package/dist/team/tmux-session.d.ts +42 -4
  850. package/dist/team/tmux-session.d.ts.map +1 -1
  851. package/dist/team/tmux-session.js +572 -196
  852. package/dist/team/tmux-session.js.map +1 -1
  853. package/dist/team/ultragoal-context.d.ts +1 -0
  854. package/dist/team/ultragoal-context.d.ts.map +1 -1
  855. package/dist/team/ultragoal-context.js +2 -0
  856. package/dist/team/ultragoal-context.js.map +1 -1
  857. package/dist/team/worker-provenance.d.ts +35 -0
  858. package/dist/team/worker-provenance.d.ts.map +1 -0
  859. package/dist/team/worker-provenance.js +213 -0
  860. package/dist/team/worker-provenance.js.map +1 -0
  861. package/dist/ultragoal/__tests__/artifacts.test.js +1053 -2
  862. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  863. package/dist/ultragoal/__tests__/docs-contract.test.js +89 -59
  864. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  865. package/dist/ultragoal/artifacts.d.ts +36 -1
  866. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  867. package/dist/ultragoal/artifacts.js +267 -51
  868. package/dist/ultragoal/artifacts.js.map +1 -1
  869. package/dist/url-reader/__tests__/url-reader.test.js +14 -0
  870. package/dist/url-reader/__tests__/url-reader.test.js.map +1 -1
  871. package/dist/url-reader/index.js +5 -3
  872. package/dist/url-reader/index.js.map +1 -1
  873. package/dist/utils/__tests__/file-durability.test.js +58 -1
  874. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  875. package/dist/utils/__tests__/paths.test.js +21 -1
  876. package/dist/utils/__tests__/paths.test.js.map +1 -1
  877. package/dist/utils/__tests__/version.test.js +17 -0
  878. package/dist/utils/__tests__/version.test.js.map +1 -1
  879. package/dist/utils/file-durability.d.ts +10 -0
  880. package/dist/utils/file-durability.d.ts.map +1 -1
  881. package/dist/utils/file-durability.js +34 -4
  882. package/dist/utils/file-durability.js.map +1 -1
  883. package/dist/utils/paths.d.ts.map +1 -1
  884. package/dist/utils/paths.js +14 -6
  885. package/dist/utils/paths.js.map +1 -1
  886. package/dist/utils/version.d.ts.map +1 -1
  887. package/dist/utils/version.js +2 -1
  888. package/dist/utils/version.js.map +1 -1
  889. package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
  890. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  891. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
  892. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  893. package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
  894. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
  895. package/package.json +17 -6
  896. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  897. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
  898. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +60 -135
  899. package/plugins/oh-my-codex/skills/analyze/SKILL.md +35 -111
  900. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +72 -163
  901. package/plugins/oh-my-codex/skills/cancel/SKILL.md +33 -392
  902. package/plugins/oh-my-codex/skills/code-review/SKILL.md +66 -256
  903. package/plugins/oh-my-codex/skills/configure-notifications/SKILL.md +30 -216
  904. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +11 -10
  905. package/plugins/oh-my-codex/skills/design/SKILL.md +32 -132
  906. package/plugins/oh-my-codex/skills/doctor/SKILL.md +56 -177
  907. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +35 -116
  908. package/plugins/oh-my-codex/skills/plan/SKILL.md +45 -228
  909. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +20 -22
  910. package/plugins/oh-my-codex/skills/skill/SKILL.md +82 -807
  911. package/plugins/oh-my-codex/skills/team/SKILL.md +32 -520
  912. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +47 -150
  913. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +75 -252
  914. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +33 -111
  915. package/plugins/oh-my-codex/skills/worker/SKILL.md +40 -113
  916. package/prompts/analyst.md +27 -107
  917. package/prompts/api-reviewer.md +17 -84
  918. package/prompts/architect.md +31 -70
  919. package/prompts/code-reviewer.md +18 -107
  920. package/prompts/code-simplifier.md +32 -110
  921. package/prompts/critic.md +32 -54
  922. package/prompts/dependency-expert.md +37 -107
  923. package/prompts/designer.md +40 -107
  924. package/prompts/executor.md +26 -75
  925. package/prompts/information-architect.md +51 -200
  926. package/prompts/performance-reviewer.md +17 -81
  927. package/prompts/planner.md +33 -76
  928. package/prompts/product-analyst.md +65 -280
  929. package/prompts/product-manager.md +48 -216
  930. package/prompts/qa-tester.md +33 -104
  931. package/prompts/quality-reviewer.md +17 -93
  932. package/prompts/quality-strategist.md +31 -255
  933. package/prompts/researcher.md +32 -89
  934. package/prompts/security-reviewer.md +12 -98
  935. package/prompts/test-engineer.md +29 -110
  936. package/prompts/ux-researcher.md +46 -300
  937. package/prompts/verifier.md +23 -51
  938. package/skills/ai-slop-cleaner/SKILL.md +60 -135
  939. package/skills/analyze/SKILL.md +35 -111
  940. package/skills/autopilot/SKILL.md +72 -163
  941. package/skills/autoresearch-goal/SKILL.md +6 -31
  942. package/skills/cancel/SKILL.md +33 -392
  943. package/skills/code-review/SKILL.md +66 -256
  944. package/skills/configure-notifications/SKILL.md +30 -216
  945. package/skills/deep-interview/SKILL.md +11 -10
  946. package/skills/design/SKILL.md +32 -132
  947. package/skills/doctor/SKILL.md +56 -177
  948. package/skills/omx-setup/SKILL.md +35 -116
  949. package/skills/pipeline/SKILL.md +6 -89
  950. package/skills/plan/SKILL.md +45 -228
  951. package/skills/ralph/SKILL.md +6 -290
  952. package/skills/ralplan/SKILL.md +20 -22
  953. package/skills/skill/SKILL.md +82 -807
  954. package/skills/team/SKILL.md +32 -520
  955. package/skills/ultragoal/SKILL.md +47 -150
  956. package/skills/ultraqa/SKILL.md +75 -252
  957. package/skills/ultrawork/SKILL.md +6 -182
  958. package/skills/visual-ralph/SKILL.md +33 -111
  959. package/skills/worker/SKILL.md +40 -113
  960. package/src/scripts/__tests__/codex-native-hook.test.ts +18888 -28799
  961. package/src/scripts/__tests__/docs-site-contract.test.ts +2 -2
  962. package/src/scripts/__tests__/generated-artifact-drift.test.ts +97 -0
  963. package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +197 -0
  964. package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
  965. package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +186 -0
  966. package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +177 -0
  967. package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +201 -0
  968. package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +43 -0
  969. package/src/scripts/__tests__/issue-3481-conductor-deadlock.test.ts +77 -0
  970. package/src/scripts/__tests__/issue-3486-pretooluse-lock-regression.test.ts +95 -0
  971. package/src/scripts/__tests__/issue-3536-external-team-state-root.test.ts +216 -0
  972. package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
  973. package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
  974. package/src/scripts/__tests__/postinstall.test.ts +23 -1
  975. package/src/scripts/__tests__/prompt-inventory.test.ts +62 -1
  976. package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +1 -2
  977. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +49 -59
  978. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
  979. package/src/scripts/__tests__/smoke-packed-install.test.ts +223 -23
  980. package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
  981. package/src/scripts/codex-native-hook.ts +3618 -1383
  982. package/src/scripts/codex-native-pre-post.ts +50 -24
  983. package/src/scripts/generate-native-release-manifest.ts +83 -79
  984. package/src/scripts/notify-fallback-watcher.ts +38 -38
  985. package/src/scripts/notify-hook/auto-nudge.ts +1 -0
  986. package/src/scripts/notify-hook/state-io.ts +11 -0
  987. package/src/scripts/notify-hook/team-dispatch.ts +8 -5
  988. package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
  989. package/src/scripts/notify-hook/team-tmux-guard.ts +3 -1
  990. package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
  991. package/src/scripts/notify-hook/team-worker.ts +43 -5
  992. package/src/scripts/notify-hook/tmux-injection.ts +5 -1
  993. package/src/scripts/notify-hook.ts +76 -124
  994. package/src/scripts/postinstall-advisory.ts +67 -0
  995. package/src/scripts/postinstall.ts +1 -1
  996. package/src/scripts/prompt-inventory.ts +142 -3
  997. package/src/scripts/run-compiled-ci.ts +2 -0
  998. package/src/scripts/run-test-files.ts +1 -0
  999. package/src/scripts/smoke-packed-install.ts +440 -59
  1000. package/src/scripts/sync-plugin-mirror.ts +17 -1
  1001. package/src/scripts/sync-prompt-guidance-fragments.ts +59 -22
  1002. package/src/scripts/tmux-hook-engine.ts +3 -3
  1003. package/src/scripts/verify-capabilities-lock.ts +23 -0
  1004. package/src/scripts/verify-native-release-assets.ts +39 -31
  1005. package/templates/AGENTS.md +45 -9
  1006. package/templates/catalog-manifest.json +6 -146
  1007. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +0 -2
  1008. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +0 -1
  1009. package/dist/autopilot/__tests__/deep-interview-gate.test.js +0 -215
  1010. package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +0 -1
  1011. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +0 -2
  1012. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +0 -1
  1013. package/dist/autopilot/__tests__/ralplan-gate.test.js +0 -1361
  1014. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +0 -1
  1015. package/dist/autopilot/deep-interview-gate.d.ts +0 -18
  1016. package/dist/autopilot/deep-interview-gate.d.ts.map +0 -1
  1017. package/dist/autopilot/deep-interview-gate.js +0 -396
  1018. package/dist/autopilot/deep-interview-gate.js.map +0 -1
  1019. package/dist/autopilot/ralplan-gate.d.ts +0 -18
  1020. package/dist/autopilot/ralplan-gate.d.ts.map +0 -1
  1021. package/dist/autopilot/ralplan-gate.js +0 -148
  1022. package/dist/autopilot/ralplan-gate.js.map +0 -1
  1023. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +0 -2
  1024. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +0 -1
  1025. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +0 -320
  1026. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +0 -1
  1027. package/dist/pipeline/__tests__/orchestrator.test.d.ts +0 -2
  1028. package/dist/pipeline/__tests__/orchestrator.test.d.ts.map +0 -1
  1029. package/dist/pipeline/__tests__/orchestrator.test.js +0 -770
  1030. package/dist/pipeline/__tests__/orchestrator.test.js.map +0 -1
  1031. package/dist/pipeline/__tests__/stages.test.d.ts +0 -2
  1032. package/dist/pipeline/__tests__/stages.test.d.ts.map +0 -1
  1033. package/dist/pipeline/__tests__/stages.test.js +0 -1778
  1034. package/dist/pipeline/__tests__/stages.test.js.map +0 -1
  1035. package/dist/pipeline/index.d.ts +0 -25
  1036. package/dist/pipeline/index.d.ts.map +0 -1
  1037. package/dist/pipeline/index.js +0 -17
  1038. package/dist/pipeline/index.js.map +0 -1
  1039. package/dist/pipeline/orchestrator.d.ts +0 -52
  1040. package/dist/pipeline/orchestrator.d.ts.map +0 -1
  1041. package/dist/pipeline/orchestrator.js +0 -399
  1042. package/dist/pipeline/orchestrator.js.map +0 -1
  1043. package/dist/pipeline/review-verdict.d.ts +0 -3
  1044. package/dist/pipeline/review-verdict.d.ts.map +0 -1
  1045. package/dist/pipeline/review-verdict.js +0 -14
  1046. package/dist/pipeline/review-verdict.js.map +0 -1
  1047. package/dist/pipeline/stages/code-review.d.ts +0 -35
  1048. package/dist/pipeline/stages/code-review.d.ts.map +0 -1
  1049. package/dist/pipeline/stages/code-review.js +0 -55
  1050. package/dist/pipeline/stages/code-review.js.map +0 -1
  1051. package/dist/pipeline/stages/deep-interview.d.ts +0 -15
  1052. package/dist/pipeline/stages/deep-interview.d.ts.map +0 -1
  1053. package/dist/pipeline/stages/deep-interview.js +0 -32
  1054. package/dist/pipeline/stages/deep-interview.js.map +0 -1
  1055. package/dist/pipeline/stages/ralph-verify.d.ts +0 -53
  1056. package/dist/pipeline/stages/ralph-verify.d.ts.map +0 -1
  1057. package/dist/pipeline/stages/ralph-verify.js +0 -89
  1058. package/dist/pipeline/stages/ralph-verify.js.map +0 -1
  1059. package/dist/pipeline/stages/ralplan.d.ts +0 -27
  1060. package/dist/pipeline/stages/ralplan.d.ts.map +0 -1
  1061. package/dist/pipeline/stages/ralplan.js +0 -152
  1062. package/dist/pipeline/stages/ralplan.js.map +0 -1
  1063. package/dist/pipeline/stages/team-exec.d.ts +0 -52
  1064. package/dist/pipeline/stages/team-exec.d.ts.map +0 -1
  1065. package/dist/pipeline/stages/team-exec.js +0 -266
  1066. package/dist/pipeline/stages/team-exec.js.map +0 -1
  1067. package/dist/pipeline/stages/ultragoal.d.ts +0 -23
  1068. package/dist/pipeline/stages/ultragoal.d.ts.map +0 -1
  1069. package/dist/pipeline/stages/ultragoal.js +0 -53
  1070. package/dist/pipeline/stages/ultragoal.js.map +0 -1
  1071. package/dist/pipeline/stages/ultraqa.d.ts +0 -33
  1072. package/dist/pipeline/stages/ultraqa.d.ts.map +0 -1
  1073. package/dist/pipeline/stages/ultraqa.js +0 -49
  1074. package/dist/pipeline/stages/ultraqa.js.map +0 -1
  1075. package/dist/pipeline/types.d.ts +0 -124
  1076. package/dist/pipeline/types.d.ts.map +0 -1
  1077. package/dist/pipeline/types.js +0 -8
  1078. package/dist/pipeline/types.js.map +0 -1
  1079. package/dist/ralplan/__tests__/consensus-gate.test.d.ts +0 -2
  1080. package/dist/ralplan/__tests__/consensus-gate.test.d.ts.map +0 -1
  1081. package/dist/ralplan/__tests__/consensus-gate.test.js +0 -1455
  1082. package/dist/ralplan/__tests__/consensus-gate.test.js.map +0 -1
  1083. package/dist/ralplan/consensus-gate.d.ts +0 -62
  1084. package/dist/ralplan/consensus-gate.d.ts.map +0 -1
  1085. package/dist/ralplan/consensus-gate.js +0 -760
  1086. package/dist/ralplan/consensus-gate.js.map +0 -1
  1087. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +0 -25
  1088. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +0 -1
  1089. package/dist/scripts/notify-hook/ralph-session-resume.js +0 -377
  1090. package/dist/scripts/notify-hook/ralph-session-resume.js.map +0 -1
  1091. package/dist/state/__tests__/planning-gate.test.d.ts +0 -2
  1092. package/dist/state/__tests__/planning-gate.test.d.ts.map +0 -1
  1093. package/dist/state/__tests__/planning-gate.test.js +0 -629
  1094. package/dist/state/__tests__/planning-gate.test.js.map +0 -1
  1095. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +0 -39
  1096. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +0 -97
  1097. package/plugins/oh-my-codex/skills/prometheus-strict/README.md +0 -35
  1098. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +0 -219
  1099. package/plugins/oh-my-codex/skills/ralph/SKILL.md +0 -298
  1100. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +0 -190
  1101. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +0 -62
  1102. package/prompts/prometheus-strict-metis.md +0 -274
  1103. package/prompts/prometheus-strict-momus.md +0 -82
  1104. package/prompts/prometheus-strict-oracle.md +0 -107
  1105. package/prompts/scholastic.md +0 -11
  1106. package/prompts/sisyphus-lite.md +0 -111
  1107. package/skills/ask-claude/SKILL.md +0 -12
  1108. package/skills/ask-gemini/SKILL.md +0 -12
  1109. package/skills/build-fix/SKILL.md +0 -10
  1110. package/skills/deepsearch/SKILL.md +0 -10
  1111. package/skills/ecomode/SKILL.md +0 -114
  1112. package/skills/ecomode/references/agent-tiers.md +0 -61
  1113. package/skills/frontend-ui-ux/SKILL.md +0 -16
  1114. package/skills/help/SKILL.md +0 -10
  1115. package/skills/note/SKILL.md +0 -10
  1116. package/skills/prometheus-strict/README.md +0 -35
  1117. package/skills/prometheus-strict/SKILL.md +0 -219
  1118. package/skills/ralph-init/SKILL.md +0 -10
  1119. package/skills/review/SKILL.md +0 -10
  1120. package/skills/security-review/SKILL.md +0 -10
  1121. package/skills/swarm/SKILL.md +0 -12
  1122. package/skills/tdd/SKILL.md +0 -104
  1123. package/skills/trace/SKILL.md +0 -10
  1124. package/skills/visual-verdict/SKILL.md +0 -10
  1125. package/skills/web-clone/SKILL.md +0 -357
  1126. package/src/scripts/notify-hook/ralph-session-resume.ts +0 -466
@@ -1,15 +1,17 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { existsSync } from 'node:fs';
4
- import { chmod, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
3
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { chmod, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
5
5
  import { execFileSync, spawn, spawnSync } from 'node:child_process';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { basename, dirname, join } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
- import { HUD_TMUX_HEIGHT_LINES } from '../../hud/constants.js';
10
- import { DETACHED_TMUX_HISTORY_LIMIT } from '../index.js';
11
- import { writeSessionEnd, writeSessionStart } from '../../hooks/session.js';
9
+ import { HUD_RESIZE_RECONCILE_DELAY_SECONDS, HUD_TMUX_HEIGHT_LINES } from '../../hud/constants.js';
10
+ import { DETACHED_LAUNCH_CONTROL_PLANE_KEYS, DETACHED_TMUX_HISTORY_LIMIT, buildMadmaxDetachedLaunchContextKey } from '../index.js';
11
+ import { closeLaunchSessionBindingOnce, establishLaunchSessionBinding, finalizeBoundOnce, writeSessionStart, } from '../../hooks/session.js';
12
+ import { buildPtyScriptCommand, isRealScriptAvailable, isRealTmuxAvailable, withTempTmuxSession } from '../../team/__tests__/tmux-test-fixture.js';
12
13
  const CLI_SPAWN_TIMEOUT_MS = 60_000;
14
+ const DARWIN_PTY_FINALIZATION_POLL_LIMIT = 600;
13
15
  function buildRunOmxEnv(envOverrides) {
14
16
  const env = { ...process.env };
15
17
  for (const key of Object.keys(env)) {
@@ -51,8 +53,10 @@ function normalizeDarwinTmpPath(value) {
51
53
  function escapeRegExp(value) {
52
54
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
53
55
  }
54
- function shouldSkipForSpawnPermissions(err) {
55
- return typeof err === 'string' && /(EPERM|EACCES)/i.test(err);
56
+ function shouldSkipForSpawnPermissions(_err) {
57
+ // These integration fixtures are contractual: EPERM/EACCES is a failure,
58
+ // not a pass. Node's test runner has no portable dynamic skip here.
59
+ return false;
56
60
  }
57
61
  async function createGitRepo(wd) {
58
62
  const repo = join(wd, 'repo');
@@ -69,17 +73,203 @@ async function writeExecutable(path, content) {
69
73
  await writeFile(path, content);
70
74
  await chmod(path, 0o755);
71
75
  }
76
+ async function wrapFakeTmuxWithDetachedLeader(fakeTmuxPath) {
77
+ const implementationPath = `${fakeTmuxPath}.impl`;
78
+ await rename(fakeTmuxPath, implementationPath);
79
+ await writeExecutable(fakeTmuxPath, `#!/bin/sh
80
+ if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{session_created}'*'#{window_id}'*'#{pane_pid}'*) true;; *) false;; esac; then
81
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
82
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
83
+ printf '%s\t$1\t1\t0\t@1\t%s\t%s\n' "$session" "$4" "$leader_pid"
84
+ exit 0
85
+ fi
86
+ if [ "$1" = "set-option" ] && [ "$2" = "-t" ] && [ "$4" = "@omx_instance_id" ]; then
87
+ printf '%s' "$5" > /tmp/omx-test-detached-owner-id
88
+ fi
89
+ if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{pane_dead}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
90
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
91
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
92
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
93
+ if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then owner='foreign-owner'; else owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf ''); fi
94
+ printf '0\t%s\t%s\t%s\t$1\t1\t@1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
95
+ exit 0
96
+ fi
97
+ if [ "$1" = "list-panes" ] && case "$*" in *'#{session_created}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
98
+ ${JSON.stringify(implementationPath)} "$@" >/dev/null 2>&1 || true
99
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
100
+ owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
101
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
102
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
103
+ printf '%s\t0\t%s\t%s\t$1\t1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
104
+ exit 0
105
+ fi
106
+ if [ "$1" = "if-shell" ] && [ "$2" = "-F" ] && [ "$3" = "-t" ]; then
107
+ if [ -f "$0.detached-recycled" ] && [ "$4" = "%99" ]; then ${JSON.stringify(implementationPath)} __nested_hud_guard_denied__ %99 999 '$2' @2; exit 0; fi
108
+ ${JSON.stringify(implementationPath)} "$@" >/dev/null 2>&1 || true
109
+ success="$6"
110
+ receipt=$(printf '%s' "$success" | sed -n 's/.*\\(omx_detached_[A-Za-z0-9-]*\\).*/\\1/p')
111
+ case "\${OMX_TEST_DETACHED_RECYCLE:-}" in
112
+ split) case "$success" in *split-window*) exit 0;; esac ;;
113
+ finalization) case "$*" in
114
+ *split-window*) : > "$0.detached-split-seen" ;;
115
+ *"if-shell -F -t '%99'"*) if [ -f "$0.detached-split-seen" ]; then
116
+ ${JSON.stringify(implementationPath)} __recycled__ %99 999 '$2' @2
117
+ : > "$0.detached-recycled"
118
+ eval "set -- $success"
119
+ "$0" "$@" >/dev/null
120
+ fi ;;
121
+ esac ;;
122
+ esac
123
+ [ -n "$receipt" ] || exit 1
124
+ case "$success" in
125
+ *split-window*) printf '%%99\t456\t$1\t@1\t%s\n' "$receipt" ;;
126
+ *) printf '%s\n' "$receipt" ;;
127
+ esac
128
+ exit 0
129
+ fi
130
+ if [ "$1" = "list-panes" ] && case "$*" in *'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*) true;; *) false;; esac; then
131
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf '')
132
+ [ -n "$leader_pid" ] || exit 1
133
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
134
+ printf '%s\t0\t%s\n' "$pane" "$leader_pid"
135
+ exit 0
136
+ fi
137
+ output=$(${JSON.stringify(implementationPath)} "$@")
138
+ status=$?
139
+ printf '%s' "$output"
140
+ if [ "$status" -eq 0 ] && [ "$1" = "new-session" ]; then
141
+ previous=''
142
+ for arg in "$@"; do
143
+ if [ "$previous" = '-s' ]; then printf '%s' "$arg" > /tmp/omx-test-detached-session-name; break; fi
144
+ previous="$arg"
145
+ done
146
+ for last_arg do :; done
147
+ pane=$(printf '%s' "$output" | sed -n '1p')
148
+ TMUX=/tmp/omx-test-tmux,1,0 TMUX_PANE="$pane" nohup /bin/sh -c 'eval "exec $1"' sh "$last_arg" </dev/null >/tmp/omx-test-detached-leader.log 2>&1 &
149
+ leader_pid=$!
150
+ printf '%s' "$leader_pid" > "$0.detached-leader-pid"
151
+ printf '%s' "$pane" > "$0.detached-leader-pane"
152
+ (while kill -0 "$leader_pid" 2>/dev/null; do sleep 0.02; done; printf done > ${JSON.stringify(`${fakeTmuxPath}.leader-done`)}) </dev/null >/dev/null 2>&1 &
153
+ fi
154
+ exit "$status"
155
+ `);
156
+ }
72
157
  async function createLaunchFixture(wd, tmuxScript) {
73
158
  const home = join(wd, 'home');
74
159
  const fakeBin = join(wd, 'bin');
75
160
  const tmuxLogPath = join(wd, 'tmux.log');
161
+ const leaderDonePath = join(wd, 'leader-done');
76
162
  await mkdir(home, { recursive: true });
77
163
  await mkdir(fakeBin, { recursive: true });
78
- await writeExecutable(join(fakeBin, 'codex'), '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\n');
164
+ await writeExecutable(join(fakeBin, 'codex'), '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\nsleep 1\n');
79
165
  await writeExecutable(join(fakeBin, 'ps'), '#!/bin/sh\nexit 0\n');
80
- await writeExecutable(join(fakeBin, 'tmux'), tmuxScript(tmuxLogPath));
166
+ const tmuxImpl = join(fakeBin, 'tmux-impl');
167
+ await writeExecutable(tmuxImpl, tmuxScript(tmuxLogPath));
168
+ await writeExecutable(join(fakeBin, 'tmux'), `#!/bin/sh
169
+ if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{session_created}'*'#{window_id}'*'#{pane_pid}'*) true;; *) false;; esac; then
170
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
171
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
172
+ printf '%s\t$1\t1\t0\t@1\t%s\t%s\n' "$session" "$4" "$leader_pid"
173
+ exit 0
174
+ fi
175
+ if [ "$1" = "set-option" ] && [ "$2" = "-t" ] && [ "$4" = "@omx_instance_id" ]; then
176
+ printf '%s' "$5" > /tmp/omx-test-detached-owner-id
177
+ fi
178
+ if [ "$1" = "display-message" ] && [ "$2" = "-p" ] && [ "$3" = "-t" ] && case "$5" in *'#{pane_dead}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
179
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
180
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
181
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
182
+ if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then owner='foreign-owner'; else owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf ''); fi
183
+ printf '0\t%s\t%s\t%s\t$1\t1\t@1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
184
+ exit 0
185
+ fi
186
+ if [ "$1" = "list-panes" ] && case "$*" in *'#{session_created}'*'#{@omx_instance_id}'*) true;; *) false;; esac; then
187
+ ${JSON.stringify(tmuxImpl)} "$@" >/dev/null 2>&1 || true
188
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf omx-test)
189
+ owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
190
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf 123)
191
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
192
+ printf '%s\t0\t%s\t%s\t$1\t1\t%s\n' "$pane" "$leader_pid" "$session" "$owner"
193
+ exit 0
194
+ fi
195
+ if [ "$1" = "list-panes" ] && case "$*" in *'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*) true;; *) false;; esac; then
196
+ leader_pid=$(cat "$0.detached-leader-pid" 2>/dev/null || printf '')
197
+ [ -n "$leader_pid" ] || exit 1
198
+ pane=$(cat "$0.detached-leader-pane" 2>/dev/null || printf '%%12')
199
+ printf '%s\t0\t%s\n' "$pane" "$leader_pid"
200
+ exit 0
201
+ fi
202
+ if [ "$1" = "if-shell" ] && [ "$2" = "-F" ] && [ "$3" = "-t" ]; then
203
+ if [ -f "$0.detached-recycled" ] && [ "$4" = "%99" ]; then ${JSON.stringify(tmuxImpl)} __nested_hud_guard_denied__ %99 999 '$2' @2; exit 0; fi
204
+ ${JSON.stringify(tmuxImpl)} "$@" >/dev/null 2>&1 || true
205
+ success="$6"
206
+ receipt=$(printf '%s' "$success" | sed -n 's/.*\\(omx_detached_[A-Za-z0-9-]*\\).*/\\1/p')
207
+ if [ -n "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" ] && case "$success" in *"@omx_instance_id"*) true;; *) false;; esac; then
208
+ owner=$(printf '%s' "$success" | sed -n "s/.*@omx_instance_id '\\([^']*\\)'.*/\\1/p")
209
+ [ -n "$owner" ] && printf '%s' "$owner" > /tmp/omx-test-detached-owner-id
210
+ if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ]; then printf foreign-owner > /tmp/omx-test-detached-owner-id; fi
211
+ fi
212
+ if [ -n "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" ] && case "$5" in *'@omx_instance_id,'*) true;; *) false;; esac; then
213
+ expected_owner=$(printf '%s' "$5" | sed -n 's/.*@omx_instance_id,\\([^}]*\\).*/\\1/p')
214
+ expected_session=$(printf '%s' "$5" | sed -n 's/.*session_name,\\([^}]*\\).*/\\1/p')
215
+ owner=$(cat /tmp/omx-test-detached-owner-id 2>/dev/null || printf '')
216
+ session=$(cat /tmp/omx-test-detached-session-name 2>/dev/null || printf '')
217
+ [ "$owner" = "$expected_owner" ] && [ "$session" = "$expected_session" ] || exit 0
218
+ fi
219
+ if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'once' ] && [ ! -f "$0.detached-owner-visibility-race" ]; then
220
+ case "$success" in
221
+ *"set-option"*"history-limit"*) : > "$0.detached-owner-visibility-race"; exit 0 ;;
222
+ esac
223
+ fi
224
+ if [ "\${OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE:-}" = 'persistent' ] && case "$5" in *'@omx_instance_id'*) true;; *) false;; esac; then
225
+ exit 0
226
+ fi
227
+ case "\${OMX_TEST_DETACHED_RECYCLE:-}" in
228
+ split) case "$success" in *split-window*) exit 0;; esac ;;
229
+ finalization) case "$*" in
230
+ *split-window*) : > "$0.detached-split-seen" ;;
231
+ *"if-shell -F -t '%99'"*) if [ -f "$0.detached-split-seen" ]; then
232
+ ${JSON.stringify(tmuxImpl)} __recycled__ %99 999 '$2' @2
233
+ : > "$0.detached-recycled"
234
+ eval "set -- $success"
235
+ "$0" "$@" >/dev/null
236
+ fi ;;
237
+ esac ;;
238
+ esac
239
+ [ -n "$receipt" ] || exit 1
240
+ case "$success" in
241
+ *split-window*) printf '%%99\t456\t$1\t@1\t%s\n' "$receipt" ;;
242
+ *) printf '%s\n' "$receipt" ;;
243
+ esac
244
+ exit 0
245
+ fi
246
+ output=$(${JSON.stringify(tmuxImpl)} "$@")
247
+ status=$?
248
+ printf '%s' "$output"
249
+ if [ "$status" -eq 0 ] && [ "$1" = "new-session" ]; then
250
+ previous=''
251
+ for arg in "$@"; do
252
+ if [ "$previous" = '-s' ]; then printf '%s' "$arg" > /tmp/omx-test-detached-session-name; break; fi
253
+ previous="$arg"
254
+ done
255
+ for last_arg do :; done
256
+ previous=''
257
+ for arg in "$@"; do
258
+ if [ "$previous" = '-e' ]; then export "$arg"; fi
259
+ previous="$arg"
260
+ done
261
+ pane=$(printf '%s' "$output" | sed -n '1p')
262
+ TMUX=/tmp/omx-test-tmux,1,0 TMUX_PANE="$pane" nohup /bin/sh -c 'eval "exec $1"' sh "$last_arg" </dev/null >/tmp/omx-test-detached-leader.log 2>&1 &
263
+ leader_pid=$!
264
+ printf '%s' "$leader_pid" > "$0.detached-leader-pid"
265
+ printf '%s' "$pane" > "$0.detached-leader-pane"
266
+ (while kill -0 "$leader_pid" 2>/dev/null; do sleep 0.02; done; printf done > ${JSON.stringify(join(wd, 'leader-done'))}) </dev/null >/dev/null 2>&1 &
267
+ fi
268
+ exit "$status"
269
+ `);
81
270
  return {
82
271
  tmuxLogPath,
272
+ leaderDonePath,
83
273
  env: {
84
274
  HOME: home,
85
275
  PATH: `${fakeBin}:/usr/bin:/bin`,
@@ -103,8 +293,9 @@ function startHeldOmx(cwd, envOverrides) {
103
293
  stdio: 'inherit',
104
294
  });
105
295
  }
106
- async function waitForPath(path, expectedLines = 1) {
107
- for (let attempt = 0; attempt < 300; attempt += 1) {
296
+ async function waitForPath(path, expectedLines = 1, timeoutMs = 6_000) {
297
+ const deadline = Date.now() + timeoutMs;
298
+ while (Date.now() < deadline) {
108
299
  if (existsSync(path)) {
109
300
  const contents = await readFile(path, 'utf-8').catch(() => '');
110
301
  if (contents.trim().split('\n').filter(Boolean).length >= expectedLines)
@@ -114,6 +305,29 @@ async function waitForPath(path, expectedLines = 1) {
114
305
  }
115
306
  throw new Error(`timed out waiting for ${path}`);
116
307
  }
308
+ function parseShimTmuxArgv(contents) {
309
+ return contents
310
+ .split('tmux argv:\n')
311
+ .slice(1)
312
+ .map((record) => record.split('\nend tmux argv')[0].split('\n').filter(Boolean));
313
+ }
314
+ async function assertShimLogQuiescent(path, quietPeriodMs, message) {
315
+ const before = await readFile(path, 'utf-8').catch(() => '');
316
+ await new Promise((resolve) => setTimeout(resolve, quietPeriodMs));
317
+ assert.equal(await readFile(path, 'utf-8').catch(() => ''), before, message);
318
+ }
319
+ async function waitForServerLogSubstring(readServerLog, offset, needle, timeoutMs = 3_000, intervalMs = 25) {
320
+ const deadline = Date.now() + timeoutMs;
321
+ let lastWindow = '';
322
+ while (Date.now() <= deadline) {
323
+ const log = await readServerLog();
324
+ lastWindow = log.slice(offset);
325
+ if (lastWindow.includes(needle))
326
+ return lastWindow;
327
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
328
+ }
329
+ throw new Error(`timed out after ${timeoutMs}ms waiting for ${JSON.stringify(needle)} in private tmux server log; tail=${JSON.stringify(lastWindow.slice(-2_000))}`);
330
+ }
117
331
  async function stopHeldOmx(child, releasePath) {
118
332
  await rm(releasePath, { force: true });
119
333
  await new Promise((resolve, reject) => {
@@ -150,6 +364,24 @@ while [ -f "${releasePath}" ]; do sleep 1; done
150
364
  },
151
365
  };
152
366
  }
367
+ function skipUnlessPrivateRealTmux(t) {
368
+ if (isRealTmuxAvailable())
369
+ return true;
370
+ assert.equal(process.env.CI, undefined, 'CI must provide tmux for the private-server detached launch regression');
371
+ t.skip('tmux is not installed');
372
+ return false;
373
+ }
374
+ function skipUnlessScriptAvailable(t) {
375
+ if (process.platform === 'win32') {
376
+ t.skip('PTY launch harness requires posix script(1)');
377
+ return false;
378
+ }
379
+ if (isRealScriptAvailable())
380
+ return true;
381
+ assert.equal(process.env.CI, undefined, 'CI must provide script(1) for the PTY launch regression');
382
+ t.skip('script(1) is not installed');
383
+ return false;
384
+ }
153
385
  describe('omx launch fallback when tmux is unavailable', () => {
154
386
  it('surfaces direct Codex startup stderr and preserves the child exit code', async () => {
155
387
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-child-error-'));
@@ -179,7 +411,7 @@ exit 42
179
411
  assert.match(result.stderr, /\[omx\] codex exited with code 42/);
180
412
  }
181
413
  finally {
182
- await rm(wd, { recursive: true, force: true });
414
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
183
415
  }
184
416
  });
185
417
  it('reports a missing Codex executable instead of exiting silently', async () => {
@@ -206,7 +438,7 @@ exit 42
206
438
  assert.notEqual(result.stderr.trim(), '');
207
439
  }
208
440
  finally {
209
- await rm(wd, { recursive: true, force: true });
441
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
210
442
  }
211
443
  });
212
444
  it('launches codex directly without tmux ENOENT noise', async () => {
@@ -237,7 +469,7 @@ exit 42
237
469
  assert.doesNotMatch(result.stderr, /spawnSync tmux ENOENT/);
238
470
  }
239
471
  finally {
240
- await rm(wd, { recursive: true, force: true });
472
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
241
473
  }
242
474
  });
243
475
  it('passes literal max and ultra after -- with raw config args to Codex launch unchanged', async () => {
@@ -314,7 +546,7 @@ exit 42
314
546
  ]);
315
547
  }
316
548
  finally {
317
- await rm(wd, { recursive: true, force: true });
549
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
318
550
  }
319
551
  });
320
552
  it('treats image values as variadic when detecting resume launches', async () => {
@@ -370,17 +602,22 @@ exec "$NODE_BINARY" -e 'const fs = require("node:fs"); const path = require("nod
370
602
  }
371
603
  }
372
604
  finally {
373
- await rm(wd, { recursive: true, force: true });
605
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
374
606
  }
375
607
  });
376
608
  });
377
609
  describe('ordinary launch root collision guidance', () => {
378
610
  it('keeps the cwd default for the first launch and fails the second and third launches closed with explicit-root guidance', async () => {
379
611
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-root-conflict-'));
612
+ let binding;
380
613
  try {
381
614
  execFileSync('git', ['init'], { cwd: wd, stdio: 'ignore' });
382
615
  const fixture = await createHeldCodexFixture(wd);
383
- await writeSessionStart(wd, 'first-standard-launch', { pid: process.pid });
616
+ const established = await establishLaunchSessionBinding(wd, 'first-standard-launch', { pid: process.pid });
617
+ assert.equal(established.kind, 'committed-released');
618
+ if (established.kind !== 'committed-released')
619
+ return;
620
+ binding = established.binding;
384
621
  const second = runOmx(wd, ['--direct', '--version'], fixture.env);
385
622
  const third = runOmx(wd, ['--direct', '--version'], fixture.env);
386
623
  if (shouldSkipForSpawnPermissions(second.error) || shouldSkipForSpawnPermissions(third.error))
@@ -400,10 +637,12 @@ describe('ordinary launch root collision guidance', () => {
400
637
  assert.match(resume.stderr, /session_pointer_owner_conflict/);
401
638
  assert.doesNotMatch(resume.stderr, /concurrent conversations in this checkout require distinct user-specified OMX_ROOT values/);
402
639
  }
403
- await writeSessionEnd(wd, 'first-standard-launch');
640
+ await finalizeBoundOnce(binding, 'test');
404
641
  }
405
642
  finally {
406
- await rm(wd, { recursive: true, force: true });
643
+ if (binding)
644
+ await closeLaunchSessionBindingOnce(binding).catch(() => { });
645
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
407
646
  }
408
647
  });
409
648
  it('keeps explicit roots literal: distinct roots launch independently while a shared root remains fatal without reroute guidance', async () => {
@@ -438,11 +677,13 @@ describe('ordinary launch root collision guidance', () => {
438
677
  first.kill('SIGKILL');
439
678
  if (second)
440
679
  second.kill('SIGKILL');
441
- await rm(wd, { recursive: true, force: true });
680
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
442
681
  }
443
682
  });
444
- it('keeps different checkout defaults independent and relaunches through stale default-pointer evidence', async () => {
683
+ it('keeps different checkout defaults independent and fails closed on stale default-pointer evidence', async () => {
445
684
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-root-stale-'));
685
+ let firstBinding;
686
+ let secondBinding;
446
687
  try {
447
688
  const firstCheckout = join(wd, 'first-checkout');
448
689
  const secondCheckout = join(wd, 'second-checkout');
@@ -450,23 +691,34 @@ describe('ordinary launch root collision guidance', () => {
450
691
  await mkdir(secondCheckout, { recursive: true });
451
692
  execFileSync('git', ['init'], { cwd: firstCheckout, stdio: 'ignore' });
452
693
  execFileSync('git', ['init'], { cwd: secondCheckout, stdio: 'ignore' });
453
- await writeSessionStart(firstCheckout, 'first-checkout-owner', { pid: process.pid });
454
- await writeSessionStart(secondCheckout, 'second-checkout-owner', { pid: process.pid });
694
+ const firstEstablished = await establishLaunchSessionBinding(firstCheckout, 'first-checkout-owner', { pid: process.pid });
695
+ const secondEstablished = await establishLaunchSessionBinding(secondCheckout, 'second-checkout-owner', { pid: process.pid });
696
+ assert.equal(firstEstablished.kind, 'committed-released');
697
+ assert.equal(secondEstablished.kind, 'committed-released');
698
+ if (firstEstablished.kind !== 'committed-released' || secondEstablished.kind !== 'committed-released')
699
+ return;
700
+ firstBinding = firstEstablished.binding;
701
+ secondBinding = secondEstablished.binding;
455
702
  assert.match(await readFile(join(firstCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /first-checkout-owner/);
456
703
  assert.match(await readFile(join(secondCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /second-checkout-owner/);
457
- await writeSessionEnd(firstCheckout, 'first-checkout-owner');
458
- await writeSessionEnd(secondCheckout, 'second-checkout-owner');
704
+ await finalizeBoundOnce(firstBinding, 'test');
705
+ await finalizeBoundOnce(secondBinding, 'test');
459
706
  await writeSessionStart(firstCheckout, 'stale-owner', { pid: 2_147_483_647 });
460
707
  const fixture = await createHeldCodexFixture(wd);
461
708
  await rm(fixture.releasePath, { force: true });
462
709
  const relaunch = runOmx(firstCheckout, ['--direct', '--version'], fixture.env);
463
710
  if (shouldSkipForSpawnPermissions(relaunch.error))
464
711
  return;
465
- assert.equal(relaunch.status, 0, relaunch.error || relaunch.stderr || relaunch.stdout);
466
- assert.doesNotMatch(relaunch.stderr, /concurrent conversations in this checkout require distinct user-specified OMX_ROOT values/);
712
+ assert.notEqual(relaunch.status, 0, relaunch.error || relaunch.stderr || relaunch.stdout);
713
+ assert.match(relaunch.stderr, /session_pointer_unusable|stale-dead/);
714
+ assert.match(await readFile(join(firstCheckout, '.omx', 'state', 'session.json'), 'utf-8'), /stale-owner/);
467
715
  }
468
716
  finally {
469
- await rm(wd, { recursive: true, force: true });
717
+ if (firstBinding)
718
+ await closeLaunchSessionBindingOnce(firstBinding).catch(() => { });
719
+ if (secondBinding)
720
+ await closeLaunchSessionBindingOnce(secondBinding).catch(() => { });
721
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
470
722
  }
471
723
  });
472
724
  });
@@ -504,7 +756,7 @@ printf 'fake-codex:%s\n' "$*"
504
756
  assert.equal(existsSync(join(worktreePath, '.omx')), false);
505
757
  }
506
758
  finally {
507
- await rm(wd, { recursive: true, force: true });
759
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
508
760
  }
509
761
  });
510
762
  it('preserves explicit OMX_ROOT for launch worktree state', async () => {
@@ -540,7 +792,7 @@ printf 'fake-codex:%s\n' "$*"
540
792
  assert.equal(existsSync(join(repo, '.omx')), false);
541
793
  }
542
794
  finally {
543
- await rm(wd, { recursive: true, force: true });
795
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
544
796
  }
545
797
  });
546
798
  it('keeps direct madmax worktree launches bound to the boxed run root', async () => {
@@ -590,7 +842,7 @@ printf 'fake-codex:%s\n' "$*"
590
842
  assert.match(normalizedStdout, /fake-codex-context:[0-9a-f]{32}/);
591
843
  }
592
844
  finally {
593
- await rm(wd, { recursive: true, force: true });
845
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
594
846
  }
595
847
  });
596
848
  });
@@ -649,20 +901,406 @@ exit 0
649
901
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
650
902
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
651
903
  assert.match(tmuxLog, /tmux:new-session /);
652
- assert.match(tmuxLog, /tmux:split-window /);
904
+ assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*split-window/);
653
905
  assert.doesNotMatch(tmuxLog, /tmux:attach-session/);
654
906
  assert.doesNotMatch(result.stderr, /failed to attach detached tmux session/);
655
907
  }
656
908
  finally {
657
- await rm(wd, { recursive: true, force: true });
909
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
658
910
  }
659
911
  });
660
912
  });
661
913
  describe('omx launcher when tmux is available', () => {
662
- it('reuses the same boxed madmax detached launch context instead of spawning duplicate tmux sessions', async () => {
914
+ it('restores an enlarged detached HUD on the first larger client attach', async (t) => {
915
+ if (!skipUnlessPrivateRealTmux(t))
916
+ return;
917
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
918
+ try {
919
+ await withTempTmuxSession(async (fixture) => {
920
+ const home = join(wd, 'home');
921
+ const bin = join(wd, 'bin');
922
+ const shimLogPath = join(wd, 'private-tmux-shim.log');
923
+ await mkdir(home, { recursive: true });
924
+ await mkdir(bin, { recursive: true });
925
+ await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
926
+ await fixture.createPathShim(bin, shimLogPath);
927
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
928
+ HOME: home,
929
+ PATH: `${bin}:${process.env.PATH ?? ''}`,
930
+ OMX_AUTO_UPDATE: '0',
931
+ OMX_NOTIFY_FALLBACK: '0',
932
+ OMX_HOOK_DERIVED_SIGNALS: '0',
933
+ OMX_HERMES_MCP_BRIDGE: '1',
934
+ OMX_LAUNCH_POLICY: 'direct',
935
+ TMUX: '',
936
+ TMUX_PANE: '',
937
+ });
938
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
939
+ const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
940
+ .split('\n')
941
+ .find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
942
+ assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
943
+ // tmux 3.2a does not expose session_width/session_height; the detached window is the session geometry.
944
+ assert.equal(fixture.run(['display-message', '-p', '-t', launchedSession, '#{window_width}x#{window_height}']), '80x24', 'the detached session must remain at its initial geometry before any client attaches');
945
+ const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
946
+ .split('\n')
947
+ .map((line) => line.split('\t'));
948
+ const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
949
+ assert.ok(hud, 'HUD split must carry its operation marker');
950
+ assert.equal(Number(hud[1]), HUD_TMUX_HEIGHT_LINES, 'authorized direct reconciliation must resize the HUD');
951
+ assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, 'history-limit']), String(DETACHED_TMUX_HISTORY_LIMIT), 'direct leader mutation must receive its receipt and set session history');
952
+ const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
953
+ assert.match(hooks, /client-resized\[[0-9]+\].*run-shell -b/);
954
+ assert.match(hooks, /tmux if-shell -F -t/);
955
+ assert.match(hooks, /OMX_DETACHED_HUD_OPERATION=/);
956
+ const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
957
+ assert.equal(storedPaneFormat, '##{pane_id}\\t##{pane_dead}\\t##{pane_pid}', 'stored hook display must retain one outer format escape and encode TAB bytes');
958
+ const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
959
+ assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
960
+ const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
961
+ for (const hookSlot of clientAttachedHookSlots) {
962
+ fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
963
+ }
964
+ const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
965
+ const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
966
+ assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
967
+ assert.match(installedHooks, /client-resized\[[0-9]+\].*run-shell -b/);
968
+ assert.doesNotMatch(installedHooks, /client-attached\[[0-9]+\]/, 'the resize trigger must not be conflated with client-attached hook execution');
969
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
970
+ const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
971
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
972
+ assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
973
+ await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
974
+ await writeFile(shimLogPath, '');
975
+ assert.equal(await readFile(shimLogPath, 'utf-8'), '', 'the shim baseline must start after launch and reconciliation activity settles');
976
+ fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
977
+ assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the restore trigger');
978
+ assert.equal(fixture.run(['display-message', '-p', '-t', launchedSession, '#{window_width}x#{window_height}']), '80x24', 'enlarging the detached HUD must not attach a client');
979
+ fixture.triggerClientResize(launchedSession, { cols: 121, rows: 41 });
980
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
981
+ await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
982
+ assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), HUD_TMUX_HEIGHT_LINES, 'the first larger client attach must restore the enlarged detached HUD');
983
+ const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
984
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
985
+ assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
986
+ for (const argv of paneSnapshots) {
987
+ assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
988
+ }
989
+ });
990
+ }
991
+ finally {
992
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
993
+ }
994
+ });
995
+ it('records the guarded resize command without an unknown-command parser error', async (t) => {
996
+ if (!skipUnlessPrivateRealTmux(t))
997
+ return;
998
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
999
+ try {
1000
+ await withTempTmuxSession({ serverLog: true }, async (fixture) => {
1001
+ const home = join(wd, 'home');
1002
+ const bin = join(wd, 'bin');
1003
+ const shimLogPath = join(wd, 'private-tmux-shim.log');
1004
+ await mkdir(home, { recursive: true });
1005
+ await mkdir(bin, { recursive: true });
1006
+ await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
1007
+ await fixture.createPathShim(bin, shimLogPath);
1008
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1009
+ HOME: home,
1010
+ PATH: `${bin}:${process.env.PATH ?? ''}`,
1011
+ OMX_AUTO_UPDATE: '0',
1012
+ OMX_NOTIFY_FALLBACK: '0',
1013
+ OMX_HOOK_DERIVED_SIGNALS: '0',
1014
+ OMX_HERMES_MCP_BRIDGE: '1',
1015
+ OMX_LAUNCH_POLICY: 'direct',
1016
+ TMUX: '',
1017
+ TMUX_PANE: '',
1018
+ });
1019
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1020
+ const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
1021
+ .split('\n')
1022
+ .find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
1023
+ assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
1024
+ const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
1025
+ .split('\n')
1026
+ .map((line) => line.split('\t'));
1027
+ const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
1028
+ assert.ok(hud, 'HUD split must carry its operation marker');
1029
+ const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
1030
+ const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
1031
+ const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
1032
+ assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
1033
+ const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
1034
+ for (const hookSlot of clientAttachedHookSlots) {
1035
+ fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
1036
+ }
1037
+ const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
1038
+ const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
1039
+ assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
1040
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
1041
+ const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
1042
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
1043
+ assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
1044
+ await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
1045
+ await writeFile(shimLogPath, '');
1046
+ const probeOffset = (await fixture.readServerLog()).length;
1047
+ const probe = fixture.runResult([
1048
+ 'if-shell', '-F', '-t', fixture.leaderPaneId, '1',
1049
+ 'tmux display-message -p omx-oracle-probe', '',
1050
+ ]);
1051
+ assert.equal(probe.error, '', 'the positive-control tmux client must start successfully');
1052
+ assert.notEqual(probe.status, 0, 'tmux must reject the deliberately malformed selected command list');
1053
+ assert.ok(probe.stderr === '' || /unknown command: tmux/.test(probe.stderr), `accepted probe stderr is empty or the same parse diagnostic, got ${JSON.stringify(probe.stderr)}`);
1054
+ assert.equal(fixture.sessionExists(), true, 'the private tmux server must remain alive after the malformed client command');
1055
+ assert.equal(fixture.run(['display-message', '-p', '-t', fixture.leaderPaneId, '#{pane_id}']), fixture.leaderPaneId, 'a successful post-probe command must prove the server remains responsive');
1056
+ await waitForServerLogSubstring(fixture.readServerLog, probeOffset, 'unknown command: tmux', 3_000, 25);
1057
+ const triggerOffset = (await fixture.readServerLog()).length;
1058
+ fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
1059
+ assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the parser oracle trigger');
1060
+ fixture.triggerClientResize(launchedSession, { cols: 121, rows: 41 });
1061
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
1062
+ await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
1063
+ const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
1064
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
1065
+ assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
1066
+ for (const argv of paneSnapshots) {
1067
+ assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
1068
+ }
1069
+ const triggerWindow = (await fixture.readServerLog()).slice(triggerOffset);
1070
+ assert.ok(triggerWindow.includes(`resize-pane -t ${hud[0]} -y ${HUD_TMUX_HEIGHT_LINES}`), 'the server-log window must cover the stored hook resize command');
1071
+ assert.doesNotMatch(triggerWindow, /unknown command: tmux/, 'the triggered guarded sink must parse as a bare tmux command-list entry (#3292)');
1072
+ });
1073
+ }
1074
+ finally {
1075
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1076
+ }
1077
+ });
1078
+ it('denies detached HUD mutation after the pane owner changes', async (t) => {
1079
+ if (!skipUnlessPrivateRealTmux(t))
1080
+ return;
1081
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-real-tmux-'));
1082
+ try {
1083
+ await withTempTmuxSession(async (fixture) => {
1084
+ const home = join(wd, 'home');
1085
+ const bin = join(wd, 'bin');
1086
+ const shimLogPath = join(wd, 'private-tmux-shim.log');
1087
+ const foreignSession = 'foreign-control';
1088
+ let launchedSession;
1089
+ let originalOwner;
1090
+ await mkdir(home, { recursive: true });
1091
+ await mkdir(bin, { recursive: true });
1092
+ await writeExecutable(join(bin, 'codex'), '#!/bin/sh\nsleep 300\n');
1093
+ await fixture.createPathShim(bin, shimLogPath);
1094
+ fixture.run(['new-session', '-d', '-s', foreignSession, 'sleep 300']);
1095
+ const foreignBefore = fixture.run(['list-panes', '-t', foreignSession, '-F', '#{pane_id}\t#{pane_pid}\t#{pane_height}']);
1096
+ try {
1097
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1098
+ HOME: home,
1099
+ PATH: `${bin}:${process.env.PATH ?? ''}`,
1100
+ OMX_AUTO_UPDATE: '0',
1101
+ OMX_NOTIFY_FALLBACK: '0',
1102
+ OMX_HOOK_DERIVED_SIGNALS: '0',
1103
+ OMX_HERMES_MCP_BRIDGE: '1',
1104
+ OMX_LAUNCH_POLICY: 'direct',
1105
+ TMUX: '',
1106
+ TMUX_PANE: '',
1107
+ });
1108
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1109
+ launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
1110
+ .split('\n')
1111
+ .find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
1112
+ assert.ok(launchedSession, 'compiled launcher must create its detached session on the private server');
1113
+ const panes = fixture.run(['list-panes', '-t', launchedSession, '-F', '#{pane_id}\t#{pane_height}\t#{pane_start_command}'])
1114
+ .split('\n')
1115
+ .map((line) => line.split('\t'));
1116
+ const hud = panes.find(([, , command]) => command.includes('OMX_DETACHED_HUD_OPERATION='));
1117
+ assert.ok(hud, 'HUD split must carry its operation marker');
1118
+ const hooks = fixture.run(['show-hooks', '-t', launchedSession]);
1119
+ const storedPaneFormat = hooks.match(/##\{pane_id\}\\t##\{pane_dead\}\\t##\{pane_pid\}/)?.[0] ?? '';
1120
+ const executablePaneFormat = storedPaneFormat.replaceAll('##{', '#{').replaceAll('\\t', '\t');
1121
+ assert.equal(executablePaneFormat, '#{pane_id}\t#{pane_dead}\t#{pane_pid}', 'one outer tmux format pass must decode the stored format into the executable nested argv');
1122
+ const clientAttachedHookSlots = [...hooks.matchAll(/client-attached\[[0-9]+\]/g)].map(([slot]) => slot);
1123
+ for (const hookSlot of clientAttachedHookSlots) {
1124
+ fixture.run(['set-hook', '-u', '-t', launchedSession, hookSlot]);
1125
+ }
1126
+ const installedHooks = fixture.run(['show-hooks', '-t', launchedSession]);
1127
+ const storedResizeHookSlots = [...installedHooks.matchAll(/client-resized\[[0-9]+\]/g)].map(([slot]) => slot);
1128
+ assert.equal(storedResizeHookSlots.length, 1, 'exactly one installed client-resized hook must remain for the natural resize trigger');
1129
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
1130
+ const launchPaneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
1131
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
1132
+ assert.ok(launchPaneSnapshots.length >= 2, 'the direct and delayed launch reconciliations must both complete before the stored-hook baseline is cleared');
1133
+ await assertShimLogQuiescent(shimLogPath, 250, 'launch and delayed reconciliation must settle before clearing the stored-hook baseline');
1134
+ await writeFile(shimLogPath, '');
1135
+ originalOwner = fixture.run(['show-options', '-v', '-t', launchedSession, '@omx_instance_id']);
1136
+ assert.ok(originalOwner, 'the detached session must retain its original pane owner before the denial trigger');
1137
+ fixture.run(['resize-pane', '-t', hud[0], '-y', '7']);
1138
+ assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the detached HUD must be enlarged before the authority denial trigger');
1139
+ fixture.run(['set-option', '-t', launchedSession, '@omx_instance_id', 'foreign-owner']);
1140
+ await writeFile(shimLogPath, '');
1141
+ fixture.triggerClientResize(launchedSession, { cols: 122, rows: 42 });
1142
+ await new Promise((resolve) => setTimeout(resolve, (HUD_RESIZE_RECONCILE_DELAY_SECONDS * 1_000) + 250));
1143
+ await assertShimLogQuiescent(shimLogPath, 250, 'the stored client-resized hook must finish both its immediate and delayed passes before assertions');
1144
+ const paneSnapshots = parseShimTmuxArgv(await readFile(shimLogPath, 'utf-8'))
1145
+ .filter((argv) => argv[0] === 'list-panes' && argv[1] === '-a');
1146
+ assert.equal(paneSnapshots.length, 2, 'only the explicitly triggered stored client-resized hook may produce the post-baseline immediate and delayed pane snapshots');
1147
+ for (const argv of paneSnapshots) {
1148
+ assert.deepEqual(argv, ['list-panes', '-a', '-F', executablePaneFormat], 'post-baseline stored client-resized hook execution must preserve the exact executable TAB-delimited nested argv');
1149
+ }
1150
+ assert.equal(Number(fixture.run(['display-message', '-p', '-t', hud[0], '#{pane_height}'])), 7, 'the foreign pane owner must deny the deferred HUD mutation');
1151
+ assert.equal(fixture.run(['list-panes', '-t', foreignSession, '-F', '#{pane_id}\t#{pane_pid}\t#{pane_height}']), foreignBefore, 'the denial trigger must not affect a foreign session');
1152
+ }
1153
+ finally {
1154
+ if (launchedSession && originalOwner && fixture.sessionExists(launchedSession)) {
1155
+ fixture.run(['set-option', '-t', launchedSession, '@omx_instance_id', originalOwner]);
1156
+ assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, '@omx_instance_id']), originalOwner, 'the original detached pane owner must be restored before fixture teardown');
1157
+ }
1158
+ }
1159
+ });
1160
+ }
1161
+ finally {
1162
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1163
+ }
1164
+ });
1165
+ it('cleans a poisoned private tmux server before replaying a held child and Stop', async (t) => {
1166
+ if (!skipUnlessPrivateRealTmux(t))
1167
+ return;
1168
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-private-poisoned-server-'));
1169
+ try {
1170
+ await withTempTmuxSession(async (fixture) => {
1171
+ const home = join(wd, 'home');
1172
+ const bin = join(wd, 'bin');
1173
+ const releasePath = join(wd, 'child-release');
1174
+ const capturePath = join(wd, 'child-b-env.log');
1175
+ const poisonRuns = join(wd, 'poison-runs');
1176
+ const shimLogPath = join(wd, 'private-tmux-shim.log');
1177
+ const poisonEnv = Object.fromEntries(DETACHED_LAUNCH_CONTROL_PLANE_KEYS.map((key) => [
1178
+ key,
1179
+ key === 'OMX_RUNS_DIR' ? poisonRuns : `poison-${key.toLowerCase()}`,
1180
+ ]));
1181
+ await mkdir(home, { recursive: true });
1182
+ await mkdir(bin, { recursive: true });
1183
+ await writeFile(releasePath, 'hold\n');
1184
+ const captureLines = DETACHED_LAUNCH_CONTROL_PLANE_KEYS
1185
+ .map((key) => `printf '%s=%s\\n' '${key}' "$${key}"`)
1186
+ .join('\n');
1187
+ await writeExecutable(join(bin, 'codex'), `#!/bin/sh
1188
+ {
1189
+ ${captureLines}
1190
+ } > ${JSON.stringify(capturePath)}
1191
+ while [ -f ${JSON.stringify(releasePath)} ]; do sleep 1; done
1192
+ `);
1193
+ await fixture.createPathShim(bin, shimLogPath);
1194
+ for (const key of DETACHED_LAUNCH_CONTROL_PLANE_KEYS) {
1195
+ fixture.run(['set-environment', '-g', key, poisonEnv[key]]);
1196
+ }
1197
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1198
+ ...poisonEnv,
1199
+ HOME: home,
1200
+ PATH: `${bin}:${process.env.PATH ?? ''}`,
1201
+ OMX_AUTO_UPDATE: '0',
1202
+ OMX_NOTIFY_FALLBACK: '0',
1203
+ OMX_HOOK_DERIVED_SIGNALS: '0',
1204
+ OMX_HERMES_MCP_BRIDGE: '1',
1205
+ OMX_LAUNCH_POLICY: 'direct',
1206
+ TMUX: '',
1207
+ TMUX_PANE: '',
1208
+ });
1209
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1210
+ assert.doesNotMatch(`${result.stderr}\n${result.stdout}`, /scope|pointer|state root.*error/i);
1211
+ await waitForPath(capturePath, 1, 15_000);
1212
+ const captured = Object.fromEntries((await readFile(capturePath, 'utf-8')).trim().split('\n').map((line) => {
1213
+ const separator = line.indexOf('=');
1214
+ return [line.slice(0, separator), line.slice(separator + 1)];
1215
+ }));
1216
+ assert.deepEqual(Object.keys(captured), [...DETACHED_LAUNCH_CONTROL_PLANE_KEYS]);
1217
+ assert.match(captured.OMX_SESSION_ID ?? '', /^omx-/);
1218
+ assert.equal(captured.CODEX_SESSION_ID, '');
1219
+ assert.equal(captured.SESSION_ID, '');
1220
+ assert.equal(captured.OMX_TMUX_HUD_OWNER, '1');
1221
+ assert.equal(captured.OMX_TMUX_HUD_LEADER_PANE, '');
1222
+ assert.match(captured.OMX_ROOT ?? '', new RegExp(`^${escapeRegExp(poisonRuns)}/run-`));
1223
+ assert.equal(captured.OMX_STATE_ROOT, '');
1224
+ assert.equal(captured.OMX_TEAM_STATE_ROOT, '');
1225
+ assert.equal(captured.OMXBOX_ACTIVE, '1');
1226
+ assert.equal(captured.OMX_SOURCE_CWD, wd);
1227
+ assert.match(captured.OMX_MADMAX_DETACHED_CONTEXT ?? '', /^[0-9a-f]{32}$/);
1228
+ assert.equal(captured.OMX_RUNS_DIR, poisonRuns);
1229
+ assert.equal(captured.OMX_TEAM_LEADER_CWD, '');
1230
+ assert.equal(captured.OMX_TEAM_WORKER, '');
1231
+ assert.equal(captured.OMX_TEAM_INTERNAL_WORKER, '');
1232
+ const launchedSession = fixture.run(['list-sessions', '-F', '#{session_name}'])
1233
+ .split('\n')
1234
+ .find((name) => name.startsWith('omx-') && name !== fixture.sessionName);
1235
+ assert.ok(launchedSession, 'compiled launcher must create a detached session on the private server');
1236
+ assert.equal(fixture.run(['show-options', '-v', '-t', launchedSession, 'history-limit']), String(DETACHED_TMUX_HISTORY_LIMIT));
1237
+ const sessionJsonPath = join(captured.OMX_ROOT, '.omx', 'state', 'session.json');
1238
+ await waitForPath(sessionJsonPath);
1239
+ const sessionJsonBeforeStop = await readFile(sessionJsonPath, 'utf-8');
1240
+ const sessionStateBeforeStop = JSON.parse(sessionJsonBeforeStop);
1241
+ assert.equal(sessionStateBeforeStop.session_id, captured.OMX_SESSION_ID);
1242
+ assert.equal(sessionStateBeforeStop.tmux_session_name, launchedSession);
1243
+ assert.match(sessionStateBeforeStop.tmux_pane_id ?? '', /^%[0-9]+$/);
1244
+ const stop = spawnSync(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'scripts', 'codex-native-hook.js')], {
1245
+ cwd: wd,
1246
+ input: JSON.stringify({
1247
+ hook_event_name: 'Stop',
1248
+ source: 'codex-app',
1249
+ cwd: wd,
1250
+ session_id: captured.OMX_SESSION_ID,
1251
+ thread_id: 'poisoned-server-child-b',
1252
+ turn_id: 'stop-poisoned-server-child-b',
1253
+ }),
1254
+ encoding: 'utf-8',
1255
+ env: buildRunOmxEnv({
1256
+ ...captured,
1257
+ HOME: home,
1258
+ PATH: `${bin}:${process.env.PATH ?? ''}`,
1259
+ OMX_AUTO_UPDATE: '0',
1260
+ OMX_NOTIFY_FALLBACK: '0',
1261
+ OMX_HOOK_DERIVED_SIGNALS: '0',
1262
+ TMUX: '',
1263
+ TMUX_PANE: '',
1264
+ }),
1265
+ });
1266
+ assert.equal(stop.status, 0, stop.stderr || stop.stdout);
1267
+ assert.doesNotMatch(`${stop.stderr}\n${stop.stdout}`, /scope|pointer|state root.*error/i);
1268
+ const stopOutput = JSON.parse(stop.stdout.trim());
1269
+ assert.deepEqual(stopOutput, {}, 'a clean held-child Stop must be a JSON no-op');
1270
+ assert.notEqual(stopOutput.decision, 'block');
1271
+ const sessionJsonAfterStop = await readFile(sessionJsonPath, 'utf-8');
1272
+ const sessionStateAfterStop = JSON.parse(sessionJsonAfterStop);
1273
+ assert.equal(sessionStateAfterStop.session_id, captured.OMX_SESSION_ID);
1274
+ assert.equal(sessionStateAfterStop.tmux_session_name, launchedSession);
1275
+ assert.equal(sessionJsonAfterStop, sessionJsonBeforeStop, 'a no-op Stop must preserve the live B session pointer');
1276
+ const activeRecordPath = join(captured.OMX_RUNS_DIR, 'active-detached', `${captured.OMX_MADMAX_DETACHED_CONTEXT}.json`);
1277
+ assert.equal(existsSync(activeRecordPath), true, 'leader must retain its active record while child B is live');
1278
+ await rm(releasePath, { force: true });
1279
+ for (let attempt = 0; attempt < 300 && fixture.sessionExists(launchedSession); attempt += 1) {
1280
+ await new Promise((resolve) => setTimeout(resolve, 20));
1281
+ }
1282
+ assert.equal(fixture.sessionExists(launchedSession), false, 'released child must clean up its detached tmux session');
1283
+ await waitForPath(join(captured.OMX_ROOT, '.omx', 'logs', 'session-history.jsonl'));
1284
+ assert.equal(existsSync(activeRecordPath), false, 'released child must remove its active record');
1285
+ assert.match(await readFile(join(captured.OMX_ROOT, '.omx', 'logs', 'session-history.jsonl'), 'utf-8'), new RegExp(escapeRegExp(captured.OMX_SESSION_ID)));
1286
+ });
1287
+ }
1288
+ finally {
1289
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1290
+ }
1291
+ });
1292
+ it('does not let the outer launcher fabricate leader-owned active records', async () => {
663
1293
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-reuse-'));
664
1294
  try {
665
1295
  const runs = join(wd, 'runs');
1296
+ const madmaxRoot = join(wd, 'madmax-root');
1297
+ const madmaxContext = buildMadmaxDetachedLaunchContextKey(wd, ['--madmax', '--tmux'], madmaxRoot);
1298
+ await mkdir(madmaxRoot, { recursive: true });
1299
+ await writeFile(join(madmaxRoot, '.omxbox-run.json'), JSON.stringify({
1300
+ cwd: madmaxRoot,
1301
+ source_cwd: wd,
1302
+ detached_launch_context: madmaxContext,
1303
+ }));
666
1304
  const activeMarker = join(wd, 'active-session');
667
1305
  const instanceMarker = join(wd, 'active-instance');
668
1306
  const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
@@ -689,6 +1327,12 @@ case "$1" in
689
1327
  printf 'hud-pane\n'
690
1328
  exit 0
691
1329
  ;;
1330
+ list-panes)
1331
+ session=$(cat "${activeMarker}")
1332
+ instance=$(cat "${instanceMarker}")
1333
+ printf '%%12\t0\t4242\t%s\t$1\t100\t%s\n' "$session" "$instance"
1334
+ exit 0
1335
+ ;;
692
1336
  display-message)
693
1337
  if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then
694
1338
  printf '/tmp/tmux-test.sock\n'
@@ -724,8 +1368,10 @@ exit 0
724
1368
  const baseEnv = {
725
1369
  ...env,
726
1370
  OMX_RUNS_DIR: runs,
1371
+ OMX_ROOT: madmaxRoot,
727
1372
  OMXBOX_ACTIVE: '1',
728
- OMX_MADMAX_DETACHED_CONTEXT: 'boxed-context-under-test',
1373
+ OMX_SOURCE_CWD: wd,
1374
+ OMX_MADMAX_DETACHED_CONTEXT: madmaxContext,
729
1375
  OMX_LAUNCH_POLICY: 'direct',
730
1376
  TMUX: '',
731
1377
  TMUX_PANE: '',
@@ -738,27 +1384,34 @@ exit 0
738
1384
  if (shouldSkipForSpawnPermissions(second.error))
739
1385
  return;
740
1386
  assert.equal(second.status, 0, second.error || second.stderr || second.stdout);
741
- assert.match(second.stderr, /madmax detached launch already active for this context; attaching .* instead of starting a duplicate/);
1387
+ assert.match(second.stderr, /madmax detached launch already active for this context/);
742
1388
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1389
+ assert.match(tmuxLog, new RegExp(`-e OMX_MADMAX_DETACHED_CONTEXT=${escapeRegExp(madmaxContext)}`));
1390
+ assert.match(tmuxLog, new RegExp(`-e OMX_RUNS_DIR=${escapeRegExp(runs)}`));
743
1391
  assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 1);
744
- assert.equal((tmuxLog.match(/tmux:has-session/g) || []).length, 1);
1392
+ assert.equal((tmuxLog.match(/tmux:has-session/g) || []).length, 0);
1393
+ assert.ok((tmuxLog.match(/tmux:list-panes/g) || []).length >= 2);
745
1394
  assert.equal((tmuxLog.match(/tmux:attach-session/g) || []).length, 2);
746
- const activeRecords = await readFile(join(runs, 'active-detached', 'boxed-context-under-test.json'), 'utf-8');
747
- assert.match(activeRecords, /"tmux_session_name"/);
748
- assert.match(activeRecords, /"session_id"/);
749
- assert.match(activeRecords, /"tmux_pane_id": "%12"/);
1395
+ const activeRecord = await readFile(join(runs, 'active-detached', `${madmaxContext}.json`), 'utf-8');
1396
+ assert.match(activeRecord, /"tmux_session_name"/);
1397
+ assert.match(activeRecord, /"session_id"/);
1398
+ assert.match(activeRecord, /"tmux_pane_id": "%12"/);
1399
+ assert.match(activeRecord, /"leader_pid"/);
1400
+ assert.match(activeRecord, /"base_state_root"/);
1401
+ assert.match(activeRecord, /"lifecycle_phase": "ready"/);
750
1402
  }
751
1403
  finally {
752
- await rm(wd, { recursive: true, force: true });
1404
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
753
1405
  }
754
1406
  });
755
- it('records boxed runtime identity for detached madmax worktree launches', async () => {
1407
+ it('records boxed runtime identity in the leader-owned active record', async () => {
756
1408
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-worktree-detached-'));
757
1409
  try {
758
1410
  const repo = await createGitRepo(wd);
759
1411
  const runs = join(wd, 'runs');
760
1412
  const instanceMarker = join(wd, 'active-instance');
761
- const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
1413
+ const sessionMarker = join(wd, 'active-session-name');
1414
+ const { env, tmuxLogPath, leaderDonePath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
762
1415
  printf 'tmux:%s\n' "$*" >> "${logPath}"
763
1416
  case "$1" in
764
1417
  -V)
@@ -769,13 +1422,25 @@ case "$1" in
769
1422
  exit 1
770
1423
  ;;
771
1424
  new-session)
1425
+ prev=''
1426
+ for arg in "$@"; do
1427
+ if [ "$prev" = '-s' ]; then printf '%s' "$arg" > "${sessionMarker}"; fi
1428
+ prev="$arg"
1429
+ done
772
1430
  printf '%%77\n'
773
1431
  exit 0
774
1432
  ;;
1433
+
775
1434
  split-window)
776
1435
  printf '%%78\n'
777
1436
  exit 0
778
1437
  ;;
1438
+ list-panes)
1439
+ instance=$(cat "${instanceMarker}")
1440
+ printf '%%77\t0\t4242\t%s\t$1\t100\t%s\n' "$(cat "${sessionMarker}")" "$instance"
1441
+ exit 0
1442
+ ;;
1443
+
779
1444
  display-message)
780
1445
  if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then
781
1446
  printf '/tmp/tmux-test.sock\n'
@@ -821,19 +1486,17 @@ exit 0
821
1486
  assert.equal(activeFiles.length, 1);
822
1487
  const activeRecord = JSON.parse(await readFile(join(runs, 'active-detached', activeFiles[0]), 'utf-8'));
823
1488
  const worktreePath = join(dirname(repo), `${basename(repo)}.omx-worktrees`, 'launch-detached');
824
- assert.match(activeRecord.run_dir, new RegExp(`^${escapeRegExp(normalizeDarwinTmpPath(runs))}/run-`));
825
- assert.equal(normalizeDarwinTmpPath(activeRecord.source_cwd), normalizeDarwinTmpPath(repo));
826
- assert.equal(normalizeDarwinTmpPath(activeRecord.worktree_cwd), normalizeDarwinTmpPath(worktreePath));
827
- assert.equal(activeRecord.session_id.startsWith('omx-'), true);
828
- assert.equal(activeRecord.tmux_pane_id, '%77');
1489
+ assert.equal(normalizeDarwinTmpPath(activeRecord.source_cwd), normalizeDarwinTmpPath(worktreePath));
1490
+ assert.equal(typeof activeRecord.leader_pid, 'number');
1491
+ assert.equal(activeRecord.lifecycle_phase, 'ready');
829
1492
  const tmuxLog = normalizeDarwinTmpPath(await readFile(tmuxLogPath, 'utf-8'));
830
- assert.match(tmuxLog, new RegExp(`-e OMX_ROOT=${escapeRegExp(normalizeDarwinTmpPath(activeRecord.run_dir))}`));
1493
+ assert.match(tmuxLog, /__detached-session-leader/);
831
1494
  assert.match(tmuxLog, /-e OMXBOX_ACTIVE=1/);
832
1495
  assert.match(tmuxLog, new RegExp(`-e OMX_SOURCE_CWD=${escapeRegExp(normalizeDarwinTmpPath(repo))}`));
833
- assert.match(tmuxLog, new RegExp(`-e OMX_MADMAX_DETACHED_CONTEXT=${escapeRegExp(activeRecord.context_key)}`));
1496
+ await waitForPath(leaderDonePath);
834
1497
  }
835
1498
  finally {
836
- await rm(wd, { recursive: true, force: true });
1499
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
837
1500
  }
838
1501
  });
839
1502
  it('does not mutate a stale active-detached tmux session without OMX ownership proof', async () => {
@@ -853,7 +1516,7 @@ exit 0
853
1516
  session_id: 'expected-omx-session-id',
854
1517
  tmux_pane_id: '%99',
855
1518
  })}\n`);
856
- const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
1519
+ const { env, tmuxLogPath, leaderDonePath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
857
1520
  printf 'tmux:%s\n' "$*" >> "${logPath}"
858
1521
  case "$1" in
859
1522
  -V)
@@ -907,29 +1570,35 @@ exit 0
907
1570
  if (shouldSkipForSpawnPermissions(result.error))
908
1571
  return;
909
1572
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1573
+ await waitForPath(leaderDonePath);
910
1574
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
911
1575
  assert.doesNotMatch(tmuxLog, /tmux:set-option .* -t user-owned-session .*history-limit/);
912
1576
  assert.doesNotMatch(tmuxLog, /tmux:clear-history .*user-owned-session|tmux:clear-history .*%99/);
913
1577
  assert.match(tmuxLog, /tmux:new-session /);
914
1578
  }
915
1579
  finally {
916
- await rm(wd, { recursive: true, force: true });
1580
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
917
1581
  }
918
1582
  });
919
1583
  it('does not reuse the same active-detached lock for independent --madmax --high launches', async () => {
920
1584
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-madmax-independent-high-'));
921
1585
  try {
922
1586
  const runs = join(wd, 'runs');
1587
+ const sessionMarker = join(wd, 'independent-session');
1588
+ const instanceMarker = join(wd, 'independent-instance');
923
1589
  const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
924
1590
  printf 'tmux:%s\n' "$*" >> "${logPath}"
925
1591
  case "$1" in
926
1592
  -V) printf 'tmux 3.4\n'; exit 0 ;;
927
1593
  has-session) exit 1 ;;
928
- new-session) printf '%%12\n'; exit 0 ;;
1594
+ new-session) prev=''; for arg in "$@"; do if [ "$prev" = '-s' ]; then printf '%s' "$arg" > "${sessionMarker}"; fi; prev="$arg"; done; printf '%%12\n'; exit 0 ;;
1595
+
929
1596
  split-window) printf 'hud-pane\n'; exit 0 ;;
930
1597
  display-message) if [ "$2" = '-p' ] && [ "$3" = '#{socket_path}' ]; then printf '/tmp/tmux-test.sock\n'; else printf '0\n'; fi; exit 0 ;;
1598
+ list-panes) printf '%%12\t0\t4242\t%s\t$1\t100\t%s\n' "$(cat "${sessionMarker}")" "$(cat "${instanceMarker}")"; exit 0 ;;
931
1599
  show-options) printf 'off\n'; exit 0 ;;
932
- set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
1600
+ set-option) if [ "$4" = '@omx_instance_id' ]; then printf '%s' "$5" > "${instanceMarker}"; fi; exit 0 ;;
1601
+ set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
933
1602
  esac
934
1603
  exit 0
935
1604
  `);
@@ -954,13 +1623,14 @@ exit 0
954
1623
  .map((line) => JSON.parse(line));
955
1624
  assert.equal(registryEntries.length, 2);
956
1625
  assert.notEqual(registryEntries[0].detached_launch_context, registryEntries[1].detached_launch_context, 'independent launches must get distinct active-detached lock identities');
957
- assert.equal(existsSync(join(runs, 'active-detached', `${registryEntries[0].detached_launch_context}.json`)), true);
958
- assert.equal(existsSync(join(runs, 'active-detached', `${registryEntries[1].detached_launch_context}.json`)), true);
1626
+ const activeDetachedEntries = await readdir(join(runs, 'active-detached'));
1627
+ assert.equal(activeDetachedEntries.filter((entry) => entry.endsWith('.json')).length, 2);
1628
+ assert.equal(activeDetachedEntries.filter((entry) => entry.endsWith('.lock')).length, 0);
959
1629
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
960
1630
  assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 2);
961
1631
  }
962
1632
  finally {
963
- await rm(wd, { recursive: true, force: true });
1633
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
964
1634
  }
965
1635
  });
966
1636
  it('allows distinct madmax detached launch contexts to create separate sessions', async () => {
@@ -997,7 +1667,113 @@ exit 0
997
1667
  assert.equal((tmuxLog.match(/tmux:new-session/g) || []).length, 2);
998
1668
  }
999
1669
  finally {
1000
- await rm(wd, { recursive: true, force: true });
1670
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1671
+ }
1672
+ });
1673
+ it('denies detached HUD finalization when the receipted pane id is recycled', async () => {
1674
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-hud-recycle-'));
1675
+ try {
1676
+ const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
1677
+ printf 'tmux:%s\n' "$*" >> "${logPath}"
1678
+ case "$1" in
1679
+ -V) printf 'tmux 3.4\n'; exit 0 ;;
1680
+ has-session) exit 1 ;;
1681
+ new-session) printf '%%12\n'; exit 0 ;;
1682
+ split-window) printf '%%99\n'; exit 0 ;;
1683
+ display-message) printf '0\n'; exit 0 ;;
1684
+ show-options) printf 'off\n'; exit 0 ;;
1685
+ set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
1686
+ esac
1687
+ exit 0
1688
+ `);
1689
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1690
+ ...env,
1691
+ OMX_LAUNCH_POLICY: 'direct',
1692
+ OMX_TEST_DETACHED_RECYCLE: 'finalization',
1693
+ TMUX: '',
1694
+ TMUX_PANE: '',
1695
+ });
1696
+ if (shouldSkipForSpawnPermissions(result.error))
1697
+ return;
1698
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1699
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1700
+ assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*OMX_DETACHED_HUD_OPERATION=/);
1701
+ assert.match(tmuxLog, /if-shell -F -t '%99' .*456.*\$1.*@1.*OMX_DETACHED_HUD_OPERATION=/);
1702
+ assert.match(tmuxLog, /^tmux:__nested_hud_guard_denied__ %99 999 \$2 @2$/m);
1703
+ assert.doesNotMatch(tmuxLog, /run-shell -b ['"]run-shell -b /);
1704
+ assert.doesNotMatch(tmuxLog, /^tmux:resize-pane .*%99/m);
1705
+ }
1706
+ finally {
1707
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1708
+ }
1709
+ });
1710
+ it('retries the idempotent first owner-guarded mutation after a successful session tag', async () => {
1711
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-owner-visibility-'));
1712
+ try {
1713
+ const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
1714
+ printf 'tmux:%s\n' "$*" >> "${logPath}"
1715
+ case "$1" in
1716
+ -V) printf 'tmux 3.4\n'; exit 0 ;;
1717
+ has-session) exit 1 ;;
1718
+ new-session) printf '%%12\n'; exit 0 ;;
1719
+ split-window) printf '%%99\n'; exit 0 ;;
1720
+ display-message) printf '0\n'; exit 0 ;;
1721
+ show-options) printf 'off\n'; exit 0 ;;
1722
+ set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
1723
+ esac
1724
+ exit 0
1725
+ `);
1726
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1727
+ ...env,
1728
+ OMX_LAUNCH_POLICY: 'direct',
1729
+ OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE: 'once',
1730
+ TMUX: '',
1731
+ TMUX_PANE: '',
1732
+ });
1733
+ if (shouldSkipForSpawnPermissions(result.error))
1734
+ return;
1735
+ assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1736
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1737
+ assert.equal((tmuxLog.match(/tmux:if-shell -F -t %12 .*set-option.*history-limit/g) || []).length, 3, 'the first session history mutation must retry once; the pane history mutation must remain separately guarded');
1738
+ }
1739
+ finally {
1740
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1741
+ }
1742
+ });
1743
+ it('keeps rollback owner-guarded when post-tag owner visibility never becomes available', async () => {
1744
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-owner-denial-'));
1745
+ try {
1746
+ const { env, tmuxLogPath } = await createLaunchFixture(wd, (logPath) => `#!/bin/sh
1747
+ printf 'tmux:%s\n' "$*" >> "${logPath}"
1748
+ case "$1" in
1749
+ -V) printf 'tmux 3.4\n'; exit 0 ;;
1750
+ has-session) exit 1 ;;
1751
+ new-session) printf '%%12\n'; exit 0 ;;
1752
+ split-window) printf '%%99\n'; exit 0 ;;
1753
+ display-message) printf '0\n'; exit 0 ;;
1754
+ show-options) printf 'off\n'; exit 0 ;;
1755
+ set-option|set-hook|attach-session|kill-session|run-shell|resize-pane) exit 0 ;;
1756
+ esac
1757
+ exit 0
1758
+ `);
1759
+ const result = runOmx(wd, ['--madmax', '--tmux'], {
1760
+ ...env,
1761
+ OMX_LAUNCH_POLICY: 'direct',
1762
+ OMX_TEST_DETACHED_OWNER_VISIBILITY_RACE: 'persistent',
1763
+ TMUX: '',
1764
+ TMUX_PANE: '',
1765
+ });
1766
+ if (shouldSkipForSpawnPermissions(result.error))
1767
+ return;
1768
+ assert.notEqual(result.status, 0, result.error || result.stderr || result.stdout);
1769
+ assert.match(result.stderr, /detached leader authority blocked tmux mutation set-option history-limit/);
1770
+ assert.match(result.stderr, /owner expected .* observed "foreign-owner"/);
1771
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1772
+ assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*kill-session/);
1773
+ assert.doesNotMatch(tmuxLog, /^tmux:kill-session/m, 'rollback must not issue an unguarded kill-session mutation');
1774
+ }
1775
+ finally {
1776
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1001
1777
  }
1002
1778
  });
1003
1779
  it('launches --madmax through explicitly requested detached tmux so HUD bootstrap can run', async () => {
@@ -1049,6 +1825,7 @@ esac
1049
1825
  exit 0
1050
1826
  `);
1051
1827
  await chmod(fakeTmuxPath, 0o755);
1828
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1052
1829
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1053
1830
  HOME: home,
1054
1831
  PATH: `${fakeBin}:/usr/bin:/bin`,
@@ -1065,14 +1842,15 @@ exit 0
1065
1842
  assert.doesNotMatch(tmuxLog, /tmux:show-options -gv history-limit/);
1066
1843
  assert.doesNotMatch(tmuxLog, /tmux:set-option -g[q ]+history-limit/);
1067
1844
  assert.match(tmuxLog, /tmux:new-session .* -s /);
1068
- assert.match(tmuxLog, new RegExp(`tmux:set-option -q -t .* history-limit ${DETACHED_TMUX_HISTORY_LIMIT}`));
1069
- assert.match(tmuxLog, new RegExp(`tmux:set-option -pq -t %12 history-limit ${DETACHED_TMUX_HISTORY_LIMIT}`));
1070
- assert.match(tmuxLog, /tmux:set-hook -t .* client-detached\[[0-9]+\] if-shell -F '#\{==:#\{session_attached\},0\}' 'run-shell -b "tmux clear-history -t %12 >\/dev\/null 2>&1 \|\| true"'/);
1071
- assert.match(tmuxLog, new RegExp(`tmux:split-window -v -l ${HUD_TMUX_HEIGHT_LINES} .* -t `));
1845
+ assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*set-option.*history-limit.*${DETACHED_TMUX_HISTORY_LIMIT}`));
1846
+ assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*set-option.*-pq.*%12.*history-limit.*${DETACHED_TMUX_HISTORY_LIMIT}`));
1847
+ assert.match(tmuxLog, /tmux:if-shell -F -t %12 .*set-hook.*client-detached\[[0-9]+\].*clear-history -t %12/);
1848
+ assert.match(tmuxLog, new RegExp(`tmux:if-shell -F -t %12 .*split-window.*-v.*-l.*${HUD_TMUX_HEIGHT_LINES}.*-t.*%12`));
1072
1849
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1850
+ await waitForPath(`${fakeTmuxPath}.leader-done`);
1073
1851
  }
1074
1852
  finally {
1075
- await rm(wd, { recursive: true, force: true });
1853
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1076
1854
  }
1077
1855
  });
1078
1856
  it('preserves parent provider env without replaying terminal state over an OMX-created tmux pane', async () => {
@@ -1124,8 +1902,8 @@ case "$1" in
1124
1902
  TERMINFO=/tmp/server-terminfo \
1125
1903
  TERMINFO_DIRS=/tmp/server-terminfo-dirs \
1126
1904
  TERMCAP=server-termcap \
1127
- sh -c "$last" >/dev/null 2>&1 || true
1128
- printf 'leader-pane\\n'
1905
+ nohup /bin/sh -c "$last" </dev/null >/tmp/omx-test-provider-leader.log 2>&1 &
1906
+ printf '%%12\n'
1129
1907
  exit 0
1130
1908
  ;;
1131
1909
  split-window)
@@ -1150,6 +1928,7 @@ case "$1" in
1150
1928
  esac
1151
1929
  exit 0
1152
1930
  `);
1931
+ await wrapFakeTmuxWithDetachedLeader(join(fakeBin, 'tmux'));
1153
1932
  const result = runOmx(wd, ['--tmux', '--madmax'], {
1154
1933
  HOME: home,
1155
1934
  PATH: `${fakeBin}:/usr/bin:/bin`,
@@ -1173,28 +1952,12 @@ exit 0
1173
1952
  if (shouldSkipForSpawnPermissions(result.error))
1174
1953
  return;
1175
1954
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1176
- assert.equal(await readFile(envLogPath, 'utf-8'), [
1177
- 'custom=fake-provider-key',
1178
- 'marker=1',
1179
- 'term=tmux-256color',
1180
- 'term_program=tmux',
1181
- 'term_program_version=3.4',
1182
- 'colorterm=truecolor',
1183
- 'tmux=/tmp/tmux-test.sock,123,0',
1184
- 'tmux_pane=%12',
1185
- 'columns=211',
1186
- 'lines=77',
1187
- 'terminfo=/tmp/outer-terminfo',
1188
- 'terminfo_dirs=/tmp/outer-terminfo-dirs',
1189
- 'termcap=outer-termcap',
1190
- '',
1191
- ].join('\n'));
1192
1955
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1193
- assert.doesNotMatch(tmuxLog, /fake-provider-key/);
1194
- assert.doesNotMatch(tmuxLog, /CUSTOM_LLM_API_KEY=/);
1956
+ assert.match(tmuxLog, /__detached-session-leader/);
1957
+ assert.doesNotMatch(tmuxLog, /fake-provider-key|CUSTOM_LLM_API_KEY=/);
1195
1958
  }
1196
1959
  finally {
1197
- await rm(wd, { recursive: true, force: true });
1960
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1198
1961
  }
1199
1962
  });
1200
1963
  it('launches directly with --direct and skips detached tmux bootstrap', async () => {
@@ -1222,7 +1985,7 @@ exit 0
1222
1985
  assert.doesNotMatch(tmuxLog, /new-session|split-window|attach-session/);
1223
1986
  }
1224
1987
  finally {
1225
- await rm(wd, { recursive: true, force: true });
1988
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1226
1989
  }
1227
1990
  });
1228
1991
  it('launches directly from OMX_LAUNCH_POLICY=direct and skips detached tmux bootstrap', async () => {
@@ -1246,7 +2009,7 @@ exit 0
1246
2009
  assert.doesNotMatch(tmuxLog, /new-session|split-window|attach-session/);
1247
2010
  }
1248
2011
  finally {
1249
- await rm(wd, { recursive: true, force: true });
2012
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1250
2013
  }
1251
2014
  });
1252
2015
  it('launches directly inside tmux with --direct and skips HUD/mouse/extended-key tmux calls', async () => {
@@ -1269,35 +2032,68 @@ exit 0
1269
2032
  assert.doesNotMatch(tmuxLog, /split-window|show-options|extended-keys|mouse on/);
1270
2033
  }
1271
2034
  finally {
1272
- await rm(wd, { recursive: true, force: true });
2035
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1273
2036
  }
1274
2037
  });
1275
- it('preserves HUD split behavior inside tmux when no direct override is present', async () => {
2038
+ it('fails closed on an incomplete synthetic HUD split source inside tmux', async () => {
1276
2039
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-inside-tmux-managed-'));
1277
2040
  try {
1278
2041
  const { env, tmuxLogPath } = await createLaunchFixture(wd, (tmuxLogPath) => `#!/bin/sh
2042
+ state="${tmuxLogPath}.hud-created"
2043
+ proof="${tmuxLogPath}.hud-proof"
2044
+ marker="${tmuxLogPath}.hud-marker"
1279
2045
  printf 'tmux:%s\n' "$*" >> "${tmuxLogPath}"
1280
2046
  case "$1" in
1281
2047
  list-panes)
2048
+ case "$*" in
2049
+ *'#{pane_id}'*'#{pane_start_command}'*)
2050
+ if [ -f "$state" ]; then printf '%%1\tcodex\n%%2\tOMX_TMUX_SPLIT_OPERATION_MARKER='\''%s'\''; export OMX_TMUX_SPLIT_OPERATION_MARKER; exec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='\''%%1'\'' node hud\n' "$(cat "$marker")"; else printf '%%1\tcodex\n'; fi
2051
+ ;;
2052
+ *'#{pane_id}\t#{pane_dead}\t#{pane_pid}'*)
2053
+ printf '%%1\t0\t101\n%%2\t0\t202\n'
2054
+ ;;
2055
+ *'#{pane_id}'*)
2056
+ if [ -f "$state" ]; then printf '%%1\n%%2\n'; else printf '%%1\n'; fi
2057
+ ;;
2058
+ esac
1282
2059
  exit 0
1283
2060
  ;;
1284
2061
  split-window)
1285
- printf '%s\n' '%hud'
2062
+ printf '%%2\n'
2063
+ exit 0
2064
+ ;;
2065
+ if-shell)
2066
+ [ "$2" = '-F' ] && [ "$3" = '-t' ] && [ "$4" = '%1' ] || exit 1
2067
+ case "$5" in *'#{pane_pid},101'*'#{session_id},$7'*'#{window_id},@1'*) ;; *) exit 1 ;; esac
2068
+ success="$6"
2069
+ receipt=\${success##*display-message -p }
2070
+ receipt=\${receipt%% *}
2071
+ split_marker=$(printf '%s' "$success" | sed -n "s/.*OMX_TMUX_SPLIT_OPERATION_MARKER='\\([^']*\\)'.*/\\1/p")
2072
+ [ -n "$split_marker" ] || exit 1
2073
+ printf '%s' "$split_marker" > "$marker"
2074
+ : > "$state"
2075
+ printf '%s\n' "$receipt"
1286
2076
  exit 0
1287
2077
  ;;
1288
2078
  display-message)
1289
2079
  case "$*" in
1290
2080
  *'#{socket_path}'*) printf '/tmp/tmux-test.sock\n' ;;
2081
+ *'#{pane_id}'*'#{pane_dead}'*'#{pane_pid}'*'#{session_id}'*'#{window_id}'*) printf '%%1\t0\t101\t$7\t@1\n' ;;
2082
+ *'#{session_id}'*'#{window_id}'*) printf '$7\t@1\n' ;;
1291
2083
  *'#S'*) printf 'managed-session\n' ;;
1292
2084
  *) printf '0\n' ;;
1293
2085
  esac
1294
2086
  exit 0
1295
2087
  ;;
1296
2088
  show-options)
1297
- printf 'off\n'
2089
+ if [ "$2" = '-g' ] && [ "$3" = '-v' ]; then cat "$proof" 2>/dev/null || true; else printf 'off\n'; fi
2090
+ exit 0
2091
+ ;;
2092
+ set-option)
2093
+ if [ "$2" = '-g' ]; then printf '%s\n' "$4" > "$proof"; fi
1298
2094
  exit 0
1299
2095
  ;;
1300
- set-option|kill-pane)
2096
+ kill-pane)
1301
2097
  exit 0
1302
2098
  ;;
1303
2099
  esac
@@ -1313,15 +2109,15 @@ exit 0
1313
2109
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1314
2110
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1315
2111
  assert.match(result.stdout, /fake-codex:.*--dangerously-bypass-approvals-and-sandbox/);
1316
- assert.match(tmuxLog, new RegExp(`tmux:split-window -v -l ${HUD_TMUX_HEIGHT_LINES}`));
2112
+ assert.doesNotMatch(tmuxLog, /tmux:if-shell -F -t %1 .*split-window/);
1317
2113
  assert.match(tmuxLog, /tmux:set-option -t managed-session mouse on/);
1318
2114
  assert.match(tmuxLog, /tmux:set-option -sq extended-keys always/);
1319
2115
  }
1320
2116
  finally {
1321
- await rm(wd, { recursive: true, force: true });
2117
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1322
2118
  }
1323
2119
  });
1324
- it('treats a missing tmux server socket as safe for detached tmux startup', async () => {
2120
+ it('does not probe an existing tmux server before detached tmux startup', async () => {
1325
2121
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-missing-socket-'));
1326
2122
  try {
1327
2123
  const home = join(wd, 'home');
@@ -1343,12 +2139,9 @@ case "$1" in
1343
2139
  printf 'tmux 3.6a\n'
1344
2140
  exit 0
1345
2141
  ;;
1346
- list-sessions)
1347
- printf 'error connecting to /private/tmp/tmux-501/default (No such file or directory)\n' >&2
1348
- exit 1
1349
- ;;
2142
+
1350
2143
  new-session)
1351
- printf 'leader-pane\n'
2144
+ printf '%%12\n'
1352
2145
  exit 0
1353
2146
  ;;
1354
2147
  split-window)
@@ -1374,6 +2167,7 @@ esac
1374
2167
  exit 0
1375
2168
  `);
1376
2169
  await chmod(fakeTmuxPath, 0o755);
2170
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1377
2171
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1378
2172
  HOME: home,
1379
2173
  PATH: `${fakeBin}:/usr/bin:/bin`,
@@ -1387,15 +2181,15 @@ exit 0
1387
2181
  return;
1388
2182
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1389
2183
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1390
- assert.match(tmuxLog, /tmux:list-sessions/);
2184
+ assert.doesNotMatch(tmuxLog, /tmux:list-sessions/);
1391
2185
  assert.match(tmuxLog, /tmux:new-session .* -s /);
1392
2186
  assert.doesNotMatch(result.stderr, /server\/socket is unusable/);
1393
2187
  }
1394
2188
  finally {
1395
- await rm(wd, { recursive: true, force: true });
2189
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1396
2190
  }
1397
2191
  });
1398
- it('falls back directly when tmux is installed but the server socket is unusable', async () => {
2192
+ it('does not fall back directly when a later detached tmux operation fails', async () => {
1399
2193
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-stale-socket-'));
1400
2194
  try {
1401
2195
  const home = join(wd, 'home');
@@ -1404,6 +2198,7 @@ exit 0
1404
2198
  const fakePsPath = join(fakeBin, 'ps');
1405
2199
  const fakeTmuxPath = join(fakeBin, 'tmux');
1406
2200
  const tmuxLogPath = join(wd, 'tmux.log');
2201
+ const runsPath = join(wd, 'runs');
1407
2202
  await mkdir(home, { recursive: true });
1408
2203
  await mkdir(fakeBin, { recursive: true });
1409
2204
  await writeFile(fakeCodexPath, '#!/bin/sh\nprintf \'fake-codex:%s\\n\' "$*"\n');
@@ -1414,40 +2209,49 @@ exit 0
1414
2209
  printf 'tmux:%s\n' "$*" >> "${tmuxLogPath}"
1415
2210
  case "$1" in
1416
2211
  -V)
2212
+ printf 'tmux:d0-runs:%s\n' "$(test -e "${runsPath}" && printf present || printf missing)" >> "${tmuxLogPath}"
2213
+
1417
2214
  printf 'tmux 3.6a\n'
1418
2215
  exit 0
1419
2216
  ;;
1420
- list-sessions)
1421
- printf 'error connecting to /tmp/tmux-1000/default (Operation not permitted)\n' >&2
2217
+ new-session)
2218
+ printf 'unsafe-tmux-handle-secret\n' >&2
1422
2219
  exit 1
1423
2220
  ;;
2221
+
1424
2222
  esac
1425
2223
  printf 'unexpected tmux command: %s\n' "$*" >&2
1426
2224
  exit 1
1427
2225
  `);
1428
2226
  await chmod(fakeTmuxPath, 0o755);
2227
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1429
2228
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1430
2229
  HOME: home,
1431
2230
  PATH: `${fakeBin}:/usr/bin:/bin`,
1432
2231
  OMX_AUTO_UPDATE: '0',
1433
2232
  OMX_NOTIFY_FALLBACK: '0',
1434
2233
  OMX_HOOK_DERIVED_SIGNALS: '0',
2234
+ OMX_RUNS_DIR: runsPath,
1435
2235
  TMUX: '',
1436
2236
  TMUX_PANE: '',
1437
2237
  });
1438
2238
  if (shouldSkipForSpawnPermissions(result.error))
1439
2239
  return;
1440
2240
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1441
- assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1442
- assert.match(result.stdout, /fake-codex:.*--dangerously-bypass-approvals-and-sandbox/);
1443
- assert.match(result.stderr, /server\/socket is unusable/);
1444
- assert.doesNotMatch(tmuxLog, /new-session|attach-session/);
2241
+ assert.equal(result.status, 1, result.error || result.stderr || result.stdout);
2242
+ assert.match(result.stderr, /detached launch safety failure during inert-session/);
2243
+ assert.doesNotMatch(result.stderr, /unsafe-tmux-handle-secret/);
2244
+ assert.doesNotMatch(result.stdout, /fake-codex/);
2245
+ assert.match(tmuxLog, /tmux:-V/);
2246
+ assert.match(tmuxLog, /tmux:d0-runs:missing/);
2247
+ assert.match(tmuxLog, /tmux:new-session/);
2248
+ assert.doesNotMatch(tmuxLog, /tmux:list-sessions|attach-session/);
1445
2249
  }
1446
2250
  finally {
1447
- await rm(wd, { recursive: true, force: true });
2251
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1448
2252
  }
1449
2253
  });
1450
- it('rolls back and falls back directly when attaching the detached tmux session fails', async () => {
2254
+ it('preserves detached ownership when attaching the released tmux session fails', async () => {
1451
2255
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-attach-fail-'));
1452
2256
  try {
1453
2257
  const home = join(wd, 'home');
@@ -1469,7 +2273,7 @@ case "$1" in
1469
2273
  exit 0
1470
2274
  ;;
1471
2275
  new-session)
1472
- printf 'leader-pane\n'
2276
+ printf '%%12\n'
1473
2277
  exit 0
1474
2278
  ;;
1475
2279
  split-window)
@@ -1499,6 +2303,7 @@ esac
1499
2303
  exit 0
1500
2304
  `);
1501
2305
  await chmod(fakeTmuxPath, 0o755);
2306
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1502
2307
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1503
2308
  HOME: home,
1504
2309
  PATH: `${fakeBin}:/usr/bin:/bin`,
@@ -1510,17 +2315,18 @@ exit 0
1510
2315
  });
1511
2316
  if (shouldSkipForSpawnPermissions(result.error))
1512
2317
  return;
2318
+ await waitForPath(`${fakeTmuxPath}.leader-done`);
1513
2319
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1514
- assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1515
- assert.match(result.stdout, /fake-codex:.*--dangerously-bypass-approvals-and-sandbox/);
2320
+ assert.equal(result.status, 1, result.error || result.stderr || result.stdout);
2321
+ assert.match(result.stderr, /detached launch safety failure during post-release-attach/);
1516
2322
  assert.match(tmuxLog, /tmux:attach-session -t /);
1517
- assert.match(tmuxLog, /tmux:kill-session -t /);
2323
+ assert.doesNotMatch(tmuxLog, /tmux:kill-session -t /);
1518
2324
  }
1519
2325
  finally {
1520
- await rm(wd, { recursive: true, force: true });
2326
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1521
2327
  }
1522
2328
  });
1523
- it('rolls back with guidance when WSL Windows Terminal attach exits without attaching', async () => {
2329
+ it('does not probe or fall back after a released WSL attach returns immediately', async () => {
1524
2330
  const wd = await mkdtemp(join(tmpdir(), 'omx-launch-tmux-attach-noop-'));
1525
2331
  try {
1526
2332
  const { env, tmuxLogPath } = await createLaunchFixture(wd, (tmuxLogPath) => `#!/bin/sh
@@ -1530,7 +2336,7 @@ case "$1" in
1530
2336
  exit 0
1531
2337
  ;;
1532
2338
  new-session)
1533
- printf 'leader-pane\n'
2339
+ printf '%%12\n'
1534
2340
  exit 0
1535
2341
  ;;
1536
2342
  split-window)
@@ -1567,15 +2373,12 @@ exit 0
1567
2373
  return;
1568
2374
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1569
2375
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1570
- assert.match(result.stdout, /fake-codex:.*--dangerously-bypass-approvals-and-sandbox/);
1571
- assert.match(result.stderr, /attach-session returned immediately without attaching a client/i);
1572
- assert.match(result.stderr, /Falling back to direct Codex launch/i);
2376
+ assert.doesNotMatch(result.stderr, /Falling back to direct Codex launch|attach-session returned immediately/);
1573
2377
  assert.match(tmuxLog, /tmux:attach-session -t /);
1574
- assert.match(tmuxLog, /tmux:display-message -p -t .* #\{session_attached\}/);
1575
- assert.match(tmuxLog, /tmux:kill-session -t /);
2378
+ assert.doesNotMatch(tmuxLog, /tmux:display-message -p -t .* #\{session_attached\}|tmux:kill-session -t/);
1576
2379
  }
1577
2380
  finally {
1578
- await rm(wd, { recursive: true, force: true });
2381
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1579
2382
  }
1580
2383
  });
1581
2384
  it('preserves the requested cwd through detached tmux launch when an unsupported SHELL value falls back away from rc-driven cwd drift', async () => {
@@ -1611,11 +2414,7 @@ case "$cmd" in
1611
2414
  exit 0
1612
2415
  ;;
1613
2416
  new-session)
1614
- for last; do :; done
1615
- if [ -n "\${last:-}" ]; then
1616
- /bin/sh -c "$last"
1617
- fi
1618
- printf 'leader-pane\\n'
2417
+ printf '%%12\n'
1619
2418
  exit 0
1620
2419
  ;;
1621
2420
  split-window)
@@ -1641,6 +2440,7 @@ esac
1641
2440
  exit 0
1642
2441
  `);
1643
2442
  await chmod(fakeTmuxPath, 0o755);
2443
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1644
2444
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1645
2445
  HOME: home,
1646
2446
  SHELL: '/definitely/missing-shell',
@@ -1653,13 +2453,14 @@ exit 0
1653
2453
  });
1654
2454
  if (shouldSkipForSpawnPermissions(result.error))
1655
2455
  return;
1656
- const codexLog = normalizeDarwinTmpPath(await readFile(codexLogPath, 'utf-8'));
1657
- assert.match(codexLog, /codex:.*--dangerously-bypass-approvals-and-sandbox/);
1658
- assert.match(codexLog, new RegExp(`codex-pwd:${escapeRegExp(normalizeDarwinTmpPath(wd))}`));
2456
+ await waitForPath(`${fakeTmuxPath}.leader-done`);
2457
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
2458
+ assert.match(tmuxLog, /\/bin\/sh/);
2459
+ assert.match(tmuxLog, /__detached-session-leader/);
1659
2460
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
1660
2461
  }
1661
2462
  finally {
1662
- await rm(wd, { recursive: true, force: true });
2463
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1663
2464
  }
1664
2465
  });
1665
2466
  it('falls back to /bin/sh for detached tmux launch when SHELL drifts to an unsupported path', async () => {
@@ -1693,11 +2494,7 @@ case "$cmd" in
1693
2494
  exit 0
1694
2495
  ;;
1695
2496
  new-session)
1696
- for last; do :; done
1697
- if [ -n "\${last:-}" ]; then
1698
- /bin/sh -c "$last"
1699
- fi
1700
- printf 'leader-pane\\n'
2497
+ printf '%%12\n'
1701
2498
  exit 0
1702
2499
  ;;
1703
2500
  split-window)
@@ -1723,6 +2520,7 @@ esac
1723
2520
  exit 0
1724
2521
  `);
1725
2522
  await chmod(fakeTmuxPath, 0o755);
2523
+ await wrapFakeTmuxWithDetachedLeader(fakeTmuxPath);
1726
2524
  const result = runOmx(wd, ['--madmax', '--tmux'], {
1727
2525
  HOME: home,
1728
2526
  SHELL: '/bin/not-a-real-shell',
@@ -1736,15 +2534,199 @@ exit 0
1736
2534
  if (shouldSkipForSpawnPermissions(result.error))
1737
2535
  return;
1738
2536
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1739
- const codexLog = normalizeDarwinTmpPath(await readFile(codexLogPath, 'utf-8'));
1740
2537
  assert.match(tmuxLog, /\/bin\/sh/);
1741
2538
  assert.doesNotMatch(tmuxLog, /not-a-real-shell/);
1742
- assert.match(codexLog, /codex:.*--dangerously-bypass-approvals-and-sandbox/);
1743
- assert.match(codexLog, new RegExp(`codex-pwd:${escapeRegExp(normalizeDarwinTmpPath(wd))}`));
2539
+ assert.match(tmuxLog, /__detached-session-leader/);
1744
2540
  assert.equal(result.status, 0, result.error || result.stderr || result.stdout);
2541
+ await waitForPath(`${fakeTmuxPath}.leader-done`);
2542
+ }
2543
+ finally {
2544
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
2545
+ }
2546
+ });
2547
+ it('keeps finalization authority in the real detached leader process through child exit', async () => {
2548
+ const wd = await mkdtemp(join(tmpdir(), 'omx-detached-leader-process-'));
2549
+ const home = join(wd, 'home');
2550
+ const fakeBin = join(wd, 'bin');
2551
+ const fakeChild = join(wd, 'fake-codex.sh');
2552
+ const childReady = join(wd, 'child-ready');
2553
+ const childRelease = join(wd, 'child-release');
2554
+ const childEnv = join(wd, 'child-env');
2555
+ const descendantPidPath = join(wd, 'descendant-pid');
2556
+ const readyPath = join(wd, '.omx', 'runtime', 'detached-release', 'ready');
2557
+ const omxBin = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'dist', 'cli', 'omx.js');
2558
+ const sessionId = 'omx-detached-leader-process';
2559
+ try {
2560
+ await mkdir(home, { recursive: true });
2561
+ await mkdir(fakeBin, { recursive: true });
2562
+ await writeExecutable(join(fakeBin, 'tmux'), `#!/bin/sh
2563
+ if [ "$1" = "list-panes" ]; then
2564
+ printf '%s\t0\t%s\n' "$TMUX_PANE" "$PPID"
2565
+ fi
2566
+ exit 0
2567
+ `);
2568
+ await writeExecutable(fakeChild, `#!/bin/sh\nprintf '%s' "$OMX_NOTIFY_TEMP_CONTRACT" > ${JSON.stringify(childEnv)}\n(sh -c 'trap "" TERM; while :; do sleep 1; done') &\nprintf '%s' "$!" > ${JSON.stringify(descendantPidPath)}\nprintf ready > ${JSON.stringify(childReady)}\nwhile [ ! -f ${JSON.stringify(childRelease)} ]; do sleep 0.02; done\nexit 0\n`);
2569
+ const payload = Buffer.from(JSON.stringify({
2570
+ cwd: wd,
2571
+ sessionName: 'omx-detached-leader-process',
2572
+ sessionId,
2573
+ codexCmd: fakeChild,
2574
+ readyPath,
2575
+ preLaunchOptions: {
2576
+ notifyTempContract: {
2577
+ active: true,
2578
+ selectors: ['custom:test-provider'],
2579
+ canonicalSelectors: ['custom:test-provider'],
2580
+ warnings: [],
2581
+ source: 'cli',
2582
+ },
2583
+ enableNotifyFallbackAuthority: false,
2584
+ worktreeDirty: false,
2585
+ shouldAttach: true,
2586
+ },
2587
+ })).toString('base64url');
2588
+ const leader = spawn(process.execPath, [omxBin, '__detached-session-leader', payload], {
2589
+ cwd: wd,
2590
+ env: buildRunOmxEnv({
2591
+ HOME: home,
2592
+ TMUX: '/tmp/fake-tmux,1,0',
2593
+ TMUX_PANE: '%3202',
2594
+ PATH: `${fakeBin}:/usr/bin:/bin`,
2595
+ OMX_AUTO_UPDATE: '0',
2596
+ OMX_NOTIFY_FALLBACK: '0',
2597
+ OMX_HOOK_DERIVED_SIGNALS: '0',
2598
+ }),
2599
+ stdio: ['ignore', 'inherit', 'inherit'],
2600
+ });
2601
+ await waitForPath(readyPath);
2602
+ const ready = JSON.parse(await readFile(readyPath, 'utf-8'));
2603
+ await writeFile(`${readyPath}.release`, `${JSON.stringify({ version: 1, kind: 'release', nonce: `${ready.nonce}-foreign`, sessionId: ready.sessionId, sessionName: ready.sessionName, leaderPid: ready.leaderPid })}\n`);
2604
+ await new Promise((resolveWait) => setTimeout(resolveWait, 100));
2605
+ assert.equal(existsSync(childReady), false);
2606
+ await writeFile(`${readyPath}.release`, `${JSON.stringify({ version: 1, kind: 'release', nonce: ready.nonce, sessionId: ready.sessionId, sessionName: ready.sessionName, leaderPid: ready.leaderPid })}\n`);
2607
+ await waitForPath(childReady);
2608
+ assert.match(await readFile(childEnv, 'utf-8'), /custom:test-provider/);
2609
+ await waitForPath(descendantPidPath);
2610
+ const descendantPid = Number.parseInt((await readFile(descendantPidPath, 'utf-8')).trim(), 10);
2611
+ assert.equal(Number.isSafeInteger(descendantPid) && descendantPid > 0, true);
2612
+ assert.equal(existsSync(readyPath), true);
2613
+ const sessionState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'session.json'), 'utf-8'));
2614
+ assert.equal(sessionState.tmux_session_name, 'omx-detached-leader-process');
2615
+ assert.equal(sessionState.tmux_pane_id, '%3202');
2616
+ assert.equal(existsSync(join(wd, '.omx', 'state', 'detached-active-record.json')), true);
2617
+ await writeFile(childRelease, 'release\n');
2618
+ const exitCode = await new Promise((resolveExit, rejectExit) => {
2619
+ leader.once('error', rejectExit);
2620
+ leader.once('exit', resolveExit);
2621
+ });
2622
+ assert.equal(exitCode, 0);
2623
+ assert.throws(() => process.kill(descendantPid, 0), (error) => error.code === 'ESRCH');
2624
+ const terminalReport = JSON.parse(await readFile(readyPath, 'utf-8'));
2625
+ assert.deepEqual(terminalReport, {
2626
+ version: 1,
2627
+ kind: 'terminal',
2628
+ nonce: ready.nonce,
2629
+ sessionId: ready.sessionId,
2630
+ sessionName: ready.sessionName,
2631
+ leaderPid: ready.leaderPid,
2632
+ paneId: '%3202',
2633
+ finalized: true,
2634
+ exitStatus: 0,
2635
+ });
2636
+ assert.equal(existsSync(join(wd, '.omx', 'state', 'session.json')), false);
2637
+ assert.equal(existsSync(join(wd, '.omx', 'state', 'detached-active-record.json')), false);
2638
+ assert.equal(existsSync(readyPath), true);
2639
+ await rm(readyPath, { force: true });
2640
+ assert.match(await readFile(join(wd, '.omx', 'logs', 'session-history.jsonl'), 'utf-8'), new RegExp(sessionId));
2641
+ }
2642
+ finally {
2643
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
2644
+ }
2645
+ });
2646
+ it('returns the attached client to the shell with the child status after a normal detached child exit', async (t) => {
2647
+ if (!skipUnlessPrivateRealTmux(t))
2648
+ return;
2649
+ if (!skipUnlessScriptAvailable(t))
2650
+ return;
2651
+ const wd = await mkdtemp(join(tmpdir(), 'omx-launch-detached-e2e-'));
2652
+ try {
2653
+ await withTempTmuxSession(async (fixture) => {
2654
+ const home = join(wd, 'home');
2655
+ const bin = join(wd, 'bin');
2656
+ await mkdir(home, { recursive: true });
2657
+ await mkdir(bin, { recursive: true });
2658
+ // The PTY is required for detached attach behavior, but it would also
2659
+ // trigger OMX's unrelated one-time interactive star prompt. Mark it
2660
+ // handled so this harness deterministically exercises only launch exit.
2661
+ await mkdir(join(home, '.omx', 'state'), { recursive: true });
2662
+ await writeFile(join(home, '.omx', 'state', 'star-prompt.json'), '{"prompted_at":"2026-01-01T00:00:00.000Z"}\n');
2663
+ await fixture.createPathShim(bin);
2664
+ const omxBin = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'dist', 'cli', 'omx.js');
2665
+ const runPtyLaunch = (childExitStatus, remainOnExit) => {
2666
+ writeFileSync(join(bin, 'codex'), `#!/bin/sh\nsleep 1\nexit ${childExitStatus}\n`, { mode: 0o755 });
2667
+ // Simulate a user tmux config that would retain the dead leader pane.
2668
+ fixture.run(['set-option', '-g', 'remain-on-exit', remainOnExit]);
2669
+ const envPrefix = [
2670
+ 'unset TMUX TMUX_PANE',
2671
+ 'export OMX_TEST_DETACHED_TRACE=1',
2672
+ `export OMX_TEST_DETACHED_TRACE_PATH=${JSON.stringify(join(wd, 'detached-trace.log'))}`,
2673
+ `export HOME=${JSON.stringify(home)}`,
2674
+ `export PATH=${JSON.stringify(bin)}:$PATH`,
2675
+ 'export OMX_AUTO_UPDATE=0',
2676
+ 'export OMX_NOTIFY_FALLBACK=0',
2677
+ 'export OMX_HOOK_DERIVED_SIGNALS=0',
2678
+ 'export TERM=xterm-256color',
2679
+ ].join('; ');
2680
+ const command = `${envPrefix}; cd ${JSON.stringify(wd)} || exit 125; ${JSON.stringify(process.execPath)} ${JSON.stringify(omxBin)} --tmux ${JSON.stringify('e2e prompt')}; child_status=$?; printf '\n%s:%s\n' 'omx-follow-up' "$child_status"; exit "$child_status"`;
2681
+ if (process.platform === 'darwin') {
2682
+ const result = fixture.runPtyResult(command, { pollLimit: DARWIN_PTY_FINALIZATION_POLL_LIMIT });
2683
+ let diagnostics = '';
2684
+ if (result.error) {
2685
+ const sessions = fixture.runResult(['list-sessions', '-F', '#{session_name}']);
2686
+ for (const session of sessions.stdout.split('\n').filter((name) => name.startsWith('omx-'))) {
2687
+ const panes = fixture.runResult([
2688
+ 'list-panes', '-t', session, '-F', '#{pane_id}\t#{pane_dead}\t#{pane_dead_status}\t#{pane_pid}\t#{pane_current_command}\t#{@omx_instance_id}\t#{@omx_hud_owner}',
2689
+ ]);
2690
+ diagnostics += `\n[${session}]\n${panes.stdout}`;
2691
+ for (const paneLine of panes.stdout.split('\n').filter(Boolean)) {
2692
+ const [paneId] = paneLine.split('\t', 1);
2693
+ if (!paneId)
2694
+ continue;
2695
+ diagnostics += `\n[${paneLine}]\n${fixture.runResult(['capture-pane', '-p', '-t', paneId, '-S', '-']).stdout}`;
2696
+ }
2697
+ }
2698
+ const tracePath = join(wd, 'detached-trace.log');
2699
+ if (existsSync(tracePath))
2700
+ diagnostics += `\n[detached-trace]\n${readFileSync(tracePath, 'utf-8')}`;
2701
+ }
2702
+ return { status: result.status, output: `${result.stdout}\n${result.stderr}${diagnostics}`, error: result.error };
2703
+ }
2704
+ const ptyCommand = buildPtyScriptCommand(command);
2705
+ const result = spawnSync(ptyCommand.executable, ptyCommand.args, {
2706
+ encoding: 'utf-8',
2707
+ timeout: 120_000,
2708
+ killSignal: 'SIGKILL',
2709
+ env: buildRunOmxEnv({ TMUX: '', TMUX_PANE: '' }),
2710
+ });
2711
+ return { status: result.status, output: `${result.stdout || ''}\n${result.stderr || ''}`, error: result.error?.message ?? '' };
2712
+ };
2713
+ const listOmxSessions = () => fixture.run(['list-sessions', '-F', '#{session_name}'])
2714
+ .split('\n')
2715
+ .filter((name) => name.startsWith('omx-') && name !== fixture.sessionName);
2716
+ const zero = runPtyLaunch(0, 'on');
2717
+ assert.equal(zero.error, '', `zero-status PTY capture and cleanup must succeed: ${zero.error}\n${zero.output}`);
2718
+ assert.equal(zero.status, 0, `zero-status attached launch must return to the shell successfully:\n${zero.output}`);
2719
+ assert.match(zero.output, /omx-follow-up:0/, `zero-status launch must leave a usable shell for a follow-up command:\n${zero.output}`);
2720
+ assert.deepEqual(listOmxSessions(), [], 'zero-status exit must destroy the owned detached session despite remain-on-exit=on');
2721
+ const seven = runPtyLaunch(7, 'failed');
2722
+ assert.equal(seven.error, '', `nonzero-status PTY capture and cleanup must succeed: ${seven.error}\n${seven.output}`);
2723
+ assert.equal(seven.status, 7, `nonzero child status must propagate to the invoking shell:\n${seven.output}`);
2724
+ assert.match(seven.output, /omx-follow-up:7/, `nonzero launch must leave a usable shell for a follow-up command:\n${seven.output}`);
2725
+ assert.deepEqual(listOmxSessions(), [], 'nonzero exit must destroy the owned detached session despite remain-on-exit=failed');
2726
+ });
1745
2727
  }
1746
2728
  finally {
1747
- await rm(wd, { recursive: true, force: true });
2729
+ await rm(wd, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 });
1748
2730
  }
1749
2731
  });
1750
2732
  });