oh-my-codex 0.20.3 → 0.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (661) hide show
  1. package/Cargo.lock +35 -18
  2. package/Cargo.toml +1 -1
  3. package/README.md +2 -2
  4. package/crates/omx-runtime/Cargo.toml +3 -0
  5. package/crates/omx-runtime/src/main.rs +390 -0
  6. package/crates/omx-runtime/tests/execution.rs +264 -0
  7. package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
  8. package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
  9. package/dist/adapt/__tests__/herdr-target.test.js +72 -0
  10. package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
  11. package/dist/adapt/contracts.d.ts +1 -1
  12. package/dist/adapt/contracts.d.ts.map +1 -1
  13. package/dist/adapt/contracts.js +1 -1
  14. package/dist/adapt/contracts.js.map +1 -1
  15. package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
  16. package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
  17. package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
  18. package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
  19. package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
  20. package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
  21. package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
  22. package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
  23. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
  24. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
  25. package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
  26. package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
  27. package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
  28. package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
  29. package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
  30. package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
  31. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
  32. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
  33. package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
  34. package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
  35. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
  36. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
  37. package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
  38. package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
  39. package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
  40. package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
  41. package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
  42. package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
  43. package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
  44. package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
  45. package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
  46. package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
  47. package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
  48. package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
  49. package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
  50. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
  51. package/dist/adapt/herdr/authority.d.ts +34 -0
  52. package/dist/adapt/herdr/authority.d.ts.map +1 -0
  53. package/dist/adapt/herdr/authority.js +71 -0
  54. package/dist/adapt/herdr/authority.js.map +1 -0
  55. package/dist/adapt/herdr/bridge.d.ts +84 -0
  56. package/dist/adapt/herdr/bridge.d.ts.map +1 -0
  57. package/dist/adapt/herdr/bridge.js +222 -0
  58. package/dist/adapt/herdr/bridge.js.map +1 -0
  59. package/dist/adapt/herdr/sanitize.d.ts +20 -0
  60. package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
  61. package/dist/adapt/herdr/sanitize.js +70 -0
  62. package/dist/adapt/herdr/sanitize.js.map +1 -0
  63. package/dist/adapt/herdr/semantic.d.ts +67 -0
  64. package/dist/adapt/herdr/semantic.d.ts.map +1 -0
  65. package/dist/adapt/herdr/semantic.js +170 -0
  66. package/dist/adapt/herdr/semantic.js.map +1 -0
  67. package/dist/adapt/herdr/seq-store.d.ts +32 -0
  68. package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
  69. package/dist/adapt/herdr/seq-store.js +126 -0
  70. package/dist/adapt/herdr/seq-store.js.map +1 -0
  71. package/dist/adapt/herdr/team-map.d.ts +30 -0
  72. package/dist/adapt/herdr/team-map.d.ts.map +1 -0
  73. package/dist/adapt/herdr/team-map.js +37 -0
  74. package/dist/adapt/herdr/team-map.js.map +1 -0
  75. package/dist/adapt/herdr/transport.d.ts +115 -0
  76. package/dist/adapt/herdr/transport.d.ts.map +1 -0
  77. package/dist/adapt/herdr/transport.js +260 -0
  78. package/dist/adapt/herdr/transport.js.map +1 -0
  79. package/dist/adapt/herdr/wiring.d.ts +24 -0
  80. package/dist/adapt/herdr/wiring.d.ts.map +1 -0
  81. package/dist/adapt/herdr/wiring.js +62 -0
  82. package/dist/adapt/herdr/wiring.js.map +1 -0
  83. package/dist/adapt/herdr.d.ts +19 -0
  84. package/dist/adapt/herdr.d.ts.map +1 -0
  85. package/dist/adapt/herdr.js +115 -0
  86. package/dist/adapt/herdr.js.map +1 -0
  87. package/dist/adapt/index.d.ts +4 -0
  88. package/dist/adapt/index.d.ts.map +1 -1
  89. package/dist/adapt/index.js +48 -0
  90. package/dist/adapt/index.js.map +1 -1
  91. package/dist/adapt/registry.d.ts.map +1 -1
  92. package/dist/adapt/registry.js +11 -0
  93. package/dist/adapt/registry.js.map +1 -1
  94. package/dist/auth/__tests__/hotswap.test.js +60 -1
  95. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  96. package/dist/auth/__tests__/storage.test.js +33 -1
  97. package/dist/auth/__tests__/storage.test.js.map +1 -1
  98. package/dist/auth/hotswap.d.ts +16 -2
  99. package/dist/auth/hotswap.d.ts.map +1 -1
  100. package/dist/auth/hotswap.js +22 -7
  101. package/dist/auth/hotswap.js.map +1 -1
  102. package/dist/auth/storage.js +1 -1
  103. package/dist/auth/storage.js.map +1 -1
  104. package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
  105. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  106. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  107. package/dist/autopilot/ralplan-gate.js +7 -6
  108. package/dist/autopilot/ralplan-gate.js.map +1 -1
  109. package/dist/cli/__tests__/agents-init.test.js +4 -23
  110. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  111. package/dist/cli/__tests__/api.test.js +61 -24
  112. package/dist/cli/__tests__/api.test.js.map +1 -1
  113. package/dist/cli/__tests__/auth.test.js +3 -1
  114. package/dist/cli/__tests__/auth.test.js.map +1 -1
  115. package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
  116. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
  117. package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
  118. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  119. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
  120. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
  121. package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
  122. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
  123. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
  124. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
  125. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
  126. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
  127. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
  128. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
  129. package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
  130. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
  131. package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
  132. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  133. package/dist/cli/__tests__/exec.test.js +75 -0
  134. package/dist/cli/__tests__/exec.test.js.map +1 -1
  135. package/dist/cli/__tests__/index.test.js +621 -460
  136. package/dist/cli/__tests__/index.test.js.map +1 -1
  137. package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
  138. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  139. package/dist/cli/__tests__/mcp-parity.test.js +4 -3
  140. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  141. package/dist/cli/__tests__/native-assets.test.js +398 -16
  142. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  143. package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
  144. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  145. package/dist/cli/__tests__/ralph.test.js +2 -2
  146. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  147. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
  148. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  149. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
  150. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
  151. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
  152. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
  153. package/dist/cli/__tests__/ralplan.test.js +84 -12
  154. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  155. package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
  156. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  157. package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
  158. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  159. package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
  160. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  161. package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
  162. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  163. package/dist/cli/__tests__/team.test.js +45 -1
  164. package/dist/cli/__tests__/team.test.js.map +1 -1
  165. package/dist/cli/__tests__/ultragoal.test.js +117 -2
  166. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  167. package/dist/cli/__tests__/uninstall.test.js +50 -5
  168. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  169. package/dist/cli/__tests__/update.test.js +669 -177
  170. package/dist/cli/__tests__/update.test.js.map +1 -1
  171. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
  172. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  173. package/dist/cli/api.d.ts.map +1 -1
  174. package/dist/cli/api.js +9 -10
  175. package/dist/cli/api.js.map +1 -1
  176. package/dist/cli/codex-feature-probe.d.ts +16 -0
  177. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  178. package/dist/cli/codex-feature-probe.js +44 -0
  179. package/dist/cli/codex-feature-probe.js.map +1 -1
  180. package/dist/cli/codex-home.d.ts.map +1 -1
  181. package/dist/cli/codex-home.js +7 -4
  182. package/dist/cli/codex-home.js.map +1 -1
  183. package/dist/cli/doctor.d.ts +17 -0
  184. package/dist/cli/doctor.d.ts.map +1 -1
  185. package/dist/cli/doctor.js +277 -3
  186. package/dist/cli/doctor.js.map +1 -1
  187. package/dist/cli/index.d.ts +231 -12
  188. package/dist/cli/index.d.ts.map +1 -1
  189. package/dist/cli/index.js +2298 -735
  190. package/dist/cli/index.js.map +1 -1
  191. package/dist/cli/native-assets.d.ts +25 -0
  192. package/dist/cli/native-assets.d.ts.map +1 -1
  193. package/dist/cli/native-assets.js +448 -51
  194. package/dist/cli/native-assets.js.map +1 -1
  195. package/dist/cli/native-hook-claim-journal.d.ts +8 -4
  196. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  197. package/dist/cli/native-hook-claim-journal.js +30 -8
  198. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  199. package/dist/cli/package-manager-ownership.d.ts +63 -0
  200. package/dist/cli/package-manager-ownership.d.ts.map +1 -0
  201. package/dist/cli/package-manager-ownership.js +323 -0
  202. package/dist/cli/package-manager-ownership.js.map +1 -0
  203. package/dist/cli/ralph.js +2 -2
  204. package/dist/cli/ralph.js.map +1 -1
  205. package/dist/cli/ralplan.d.ts +4 -2
  206. package/dist/cli/ralplan.d.ts.map +1 -1
  207. package/dist/cli/ralplan.js +76 -34
  208. package/dist/cli/ralplan.js.map +1 -1
  209. package/dist/cli/setup-preferences.d.ts +15 -0
  210. package/dist/cli/setup-preferences.d.ts.map +1 -1
  211. package/dist/cli/setup-preferences.js +27 -1
  212. package/dist/cli/setup-preferences.js.map +1 -1
  213. package/dist/cli/setup.d.ts +3 -0
  214. package/dist/cli/setup.d.ts.map +1 -1
  215. package/dist/cli/setup.js +55 -32
  216. package/dist/cli/setup.js.map +1 -1
  217. package/dist/cli/sparkshell.d.ts.map +1 -1
  218. package/dist/cli/sparkshell.js +52 -9
  219. package/dist/cli/sparkshell.js.map +1 -1
  220. package/dist/cli/ultragoal.d.ts.map +1 -1
  221. package/dist/cli/ultragoal.js +2 -2
  222. package/dist/cli/ultragoal.js.map +1 -1
  223. package/dist/cli/uninstall.d.ts.map +1 -1
  224. package/dist/cli/uninstall.js +27 -28
  225. package/dist/cli/uninstall.js.map +1 -1
  226. package/dist/cli/update-worker.d.ts +2 -0
  227. package/dist/cli/update-worker.d.ts.map +1 -0
  228. package/dist/cli/update-worker.js +142 -0
  229. package/dist/cli/update-worker.js.map +1 -0
  230. package/dist/cli/update.d.ts +14 -22
  231. package/dist/cli/update.d.ts.map +1 -1
  232. package/dist/cli/update.js +192 -241
  233. package/dist/cli/update.js.map +1 -1
  234. package/dist/config/__tests__/generator-idempotent.test.js +173 -29
  235. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  236. package/dist/config/__tests__/generator-notify.test.js +3 -1
  237. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  238. package/dist/config/generator.d.ts +25 -3
  239. package/dist/config/generator.d.ts.map +1 -1
  240. package/dist/config/generator.js +113 -35
  241. package/dist/config/generator.js.map +1 -1
  242. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
  243. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  244. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
  245. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  246. package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
  247. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  248. package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
  249. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  250. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
  251. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  252. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
  255. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
  256. package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
  257. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
  259. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
  261. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
  263. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  264. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
  265. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  266. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
  267. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  268. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
  269. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  270. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
  271. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  272. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
  273. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  274. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
  275. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
  276. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
  277. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  278. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
  279. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  280. package/dist/hooks/__tests__/session.test.js +2976 -557
  281. package/dist/hooks/__tests__/session.test.js.map +1 -1
  282. package/dist/hooks/agents-overlay.js +1 -1
  283. package/dist/hooks/agents-overlay.js.map +1 -1
  284. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
  285. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  286. package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
  287. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  288. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  289. package/dist/hooks/extensibility/dispatcher.js +20 -3
  290. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  291. package/dist/hooks/extensibility/plugin-runner.js +1 -0
  292. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  293. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  294. package/dist/hooks/extensibility/runtime.js.map +1 -1
  295. package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
  296. package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
  297. package/dist/hooks/extensibility/sdk/paths.js +2 -2
  298. package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
  299. package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
  300. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  301. package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
  302. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk.d.ts +1 -0
  304. package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
  305. package/dist/hooks/extensibility/sdk.js +1 -1
  306. package/dist/hooks/extensibility/sdk.js.map +1 -1
  307. package/dist/hooks/extensibility/types.d.ts +7 -0
  308. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  309. package/dist/hooks/keyword-detector.d.ts +10 -3
  310. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  311. package/dist/hooks/keyword-detector.js +116 -20
  312. package/dist/hooks/keyword-detector.js.map +1 -1
  313. package/dist/hooks/session.d.ts +180 -14
  314. package/dist/hooks/session.d.ts.map +1 -1
  315. package/dist/hooks/session.js +1402 -281
  316. package/dist/hooks/session.js.map +1 -1
  317. package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
  318. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  319. package/dist/hud/__tests__/index.test.js +26 -16
  320. package/dist/hud/__tests__/index.test.js.map +1 -1
  321. package/dist/hud/__tests__/reconcile.test.js +83 -78
  322. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  323. package/dist/hud/__tests__/state.test.js +36 -31
  324. package/dist/hud/__tests__/state.test.js.map +1 -1
  325. package/dist/hud/__tests__/tmux.test.js +155 -198
  326. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  327. package/dist/hud/tmux.d.ts +42 -4
  328. package/dist/hud/tmux.d.ts.map +1 -1
  329. package/dist/hud/tmux.js +1017 -139
  330. package/dist/hud/tmux.js.map +1 -1
  331. package/dist/leader/__tests__/contract.test.js +71 -34
  332. package/dist/leader/__tests__/contract.test.js.map +1 -1
  333. package/dist/leader/contract.d.ts +1 -6
  334. package/dist/leader/contract.d.ts.map +1 -1
  335. package/dist/leader/contract.js +25 -24
  336. package/dist/leader/contract.js.map +1 -1
  337. package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
  338. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  339. package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
  340. package/dist/mcp/__tests__/state-paths.test.js +735 -4
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/state-server.test.js +1 -1
  343. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  344. package/dist/mcp/__tests__/trace-server.test.js +2 -2
  345. package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
  346. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  347. package/dist/mcp/hermes-bridge.js +2 -2
  348. package/dist/mcp/hermes-bridge.js.map +1 -1
  349. package/dist/mcp/state-paths.d.ts +78 -4
  350. package/dist/mcp/state-paths.d.ts.map +1 -1
  351. package/dist/mcp/state-paths.js +543 -40
  352. package/dist/mcp/state-paths.js.map +1 -1
  353. package/dist/mcp/state-server.d.ts +4 -4
  354. package/dist/mcp/state-server.d.ts.map +1 -1
  355. package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
  356. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  357. package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
  358. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
  359. package/dist/modes/__tests__/base-session-scope.test.js +17 -8
  360. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  361. package/dist/modes/base.d.ts +5 -5
  362. package/dist/modes/base.d.ts.map +1 -1
  363. package/dist/modes/base.js +76 -21
  364. package/dist/modes/base.js.map +1 -1
  365. package/dist/native-assets/archive.d.ts +17 -0
  366. package/dist/native-assets/archive.d.ts.map +1 -0
  367. package/dist/native-assets/archive.js +238 -0
  368. package/dist/native-assets/archive.js.map +1 -0
  369. package/dist/native-assets/policy.d.ts +42 -0
  370. package/dist/native-assets/policy.d.ts.map +1 -0
  371. package/dist/native-assets/policy.js +155 -0
  372. package/dist/native-assets/policy.js.map +1 -0
  373. package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
  374. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  375. package/dist/pipeline/__tests__/stages.test.js +66 -149
  376. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  377. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  378. package/dist/pipeline/orchestrator.js +124 -18
  379. package/dist/pipeline/orchestrator.js.map +1 -1
  380. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  381. package/dist/pipeline/stages/ralplan.js +36 -8
  382. package/dist/pipeline/stages/ralplan.js.map +1 -1
  383. package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
  384. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  385. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
  386. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
  387. package/dist/ralplan/__tests__/runtime.test.js +82 -102
  388. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  389. package/dist/ralplan/consensus-gate.d.ts +9 -1
  390. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  391. package/dist/ralplan/consensus-gate.js +40 -304
  392. package/dist/ralplan/consensus-gate.js.map +1 -1
  393. package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
  394. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  395. package/dist/ralplan/documented-leader-preflight.js +356 -54
  396. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  397. package/dist/ralplan/runtime.d.ts +1 -1
  398. package/dist/ralplan/runtime.d.ts.map +1 -1
  399. package/dist/ralplan/runtime.js +68 -173
  400. package/dist/ralplan/runtime.js.map +1 -1
  401. package/dist/runtime/run-state.d.ts +5 -1
  402. package/dist/runtime/run-state.d.ts.map +1 -1
  403. package/dist/runtime/run-state.js +15 -8
  404. package/dist/runtime/run-state.js.map +1 -1
  405. package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
  406. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  407. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
  408. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
  409. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
  410. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
  411. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
  414. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
  415. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
  418. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
  419. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
  422. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
  423. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
  424. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
  425. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
  426. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
  427. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
  428. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
  429. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
  430. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
  431. package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
  432. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  433. package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
  434. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
  435. package/dist/scripts/__tests__/postinstall.test.js +19 -1
  436. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  437. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
  438. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  439. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
  440. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  441. package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
  442. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  443. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
  444. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
  445. package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
  446. package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
  447. package/dist/scripts/codex-native-hook.d.ts +50 -1
  448. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  449. package/dist/scripts/codex-native-hook.js +3559 -451
  450. package/dist/scripts/codex-native-hook.js.map +1 -1
  451. package/dist/scripts/codex-native-pre-post.d.ts +9 -0
  452. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  453. package/dist/scripts/codex-native-pre-post.js +35 -12
  454. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  455. package/dist/scripts/generate-native-release-manifest.js +80 -51
  456. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  457. package/dist/scripts/notify-fallback-watcher.js +28 -39
  458. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  460. package/dist/scripts/notify-hook/state-io.js +5 -0
  461. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  462. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
  464. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  466. package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
  467. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  469. package/dist/scripts/notify-hook/team-worker.js +45 -5
  470. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  473. package/dist/scripts/notify-hook/tmux-injection.js +4 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  475. package/dist/scripts/notify-hook.js +78 -82
  476. package/dist/scripts/notify-hook.js.map +1 -1
  477. package/dist/scripts/postinstall-advisory.d.ts +14 -0
  478. package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
  479. package/dist/scripts/postinstall-advisory.js +49 -0
  480. package/dist/scripts/postinstall-advisory.js.map +1 -0
  481. package/dist/scripts/postinstall.d.ts +1 -1
  482. package/dist/scripts/postinstall.d.ts.map +1 -1
  483. package/dist/scripts/postinstall.js +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/smoke-packed-install.d.ts +32 -1
  486. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  487. package/dist/scripts/smoke-packed-install.js +413 -60
  488. package/dist/scripts/smoke-packed-install.js.map +1 -1
  489. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js +16 -1
  491. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  492. package/dist/scripts/verify-native-release-assets.js +37 -23
  493. package/dist/scripts/verify-native-release-assets.js.map +1 -1
  494. package/dist/state/__tests__/mode-state-context.test.js +12 -5
  495. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  496. package/dist/state/__tests__/operations.test.js +460 -850
  497. package/dist/state/__tests__/operations.test.js.map +1 -1
  498. package/dist/state/__tests__/skill-active.test.js +400 -2
  499. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  500. package/dist/state/mode-state-context.d.ts +3 -0
  501. package/dist/state/mode-state-context.d.ts.map +1 -1
  502. package/dist/state/mode-state-context.js +27 -13
  503. package/dist/state/mode-state-context.js.map +1 -1
  504. package/dist/state/operations.d.ts +3 -0
  505. package/dist/state/operations.d.ts.map +1 -1
  506. package/dist/state/operations.js +144 -40
  507. package/dist/state/operations.js.map +1 -1
  508. package/dist/state/skill-active.d.ts +18 -1
  509. package/dist/state/skill-active.d.ts.map +1 -1
  510. package/dist/state/skill-active.js +420 -23
  511. package/dist/state/skill-active.js.map +1 -1
  512. package/dist/state/workflow-transition-reconcile.d.ts +3 -0
  513. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  514. package/dist/state/workflow-transition-reconcile.js +10 -4
  515. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  516. package/dist/state/workflow-transition.d.ts.map +1 -1
  517. package/dist/state/workflow-transition.js +3 -1
  518. package/dist/state/workflow-transition.js.map +1 -1
  519. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
  520. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  521. package/dist/subagents/__tests__/tracker.test.js +837 -1048
  522. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  523. package/dist/subagents/tracker.d.ts +64 -60
  524. package/dist/subagents/tracker.d.ts.map +1 -1
  525. package/dist/subagents/tracker.js +809 -358
  526. package/dist/subagents/tracker.js.map +1 -1
  527. package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
  528. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  529. package/dist/team/__tests__/mcp-comm.test.js +104 -3
  530. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  531. package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
  532. package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
  533. package/dist/team/__tests__/notice-ledger.test.js +131 -0
  534. package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
  535. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
  536. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
  537. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
  538. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
  539. package/dist/team/__tests__/phase-controller.test.js +20 -0
  540. package/dist/team/__tests__/phase-controller.test.js.map +1 -1
  541. package/dist/team/__tests__/runtime.test.js +725 -59
  542. package/dist/team/__tests__/runtime.test.js.map +1 -1
  543. package/dist/team/__tests__/scaling.test.js +222 -237
  544. package/dist/team/__tests__/scaling.test.js.map +1 -1
  545. package/dist/team/__tests__/state-root.test.js +117 -2
  546. package/dist/team/__tests__/state-root.test.js.map +1 -1
  547. package/dist/team/__tests__/tmux-session.test.js +887 -92
  548. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  549. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
  550. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
  551. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
  552. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
  553. package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
  554. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  555. package/dist/team/__tests__/tmux-test-fixture.js +242 -15
  556. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  557. package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
  558. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  559. package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
  560. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  561. package/dist/team/notice-ledger.d.ts +54 -0
  562. package/dist/team/notice-ledger.d.ts.map +1 -0
  563. package/dist/team/notice-ledger.js +259 -0
  564. package/dist/team/notice-ledger.js.map +1 -0
  565. package/dist/team/phase-controller.d.ts.map +1 -1
  566. package/dist/team/phase-controller.js +2 -0
  567. package/dist/team/phase-controller.js.map +1 -1
  568. package/dist/team/runtime.d.ts +3 -1
  569. package/dist/team/runtime.d.ts.map +1 -1
  570. package/dist/team/runtime.js +314 -142
  571. package/dist/team/runtime.js.map +1 -1
  572. package/dist/team/scaling.d.ts.map +1 -1
  573. package/dist/team/scaling.js +161 -91
  574. package/dist/team/scaling.js.map +1 -1
  575. package/dist/team/state/monitor.d.ts +10 -0
  576. package/dist/team/state/monitor.d.ts.map +1 -1
  577. package/dist/team/state/monitor.js +13 -0
  578. package/dist/team/state/monitor.js.map +1 -1
  579. package/dist/team/state/types.d.ts +15 -0
  580. package/dist/team/state/types.d.ts.map +1 -1
  581. package/dist/team/state/types.js.map +1 -1
  582. package/dist/team/state-root.d.ts +12 -0
  583. package/dist/team/state-root.d.ts.map +1 -1
  584. package/dist/team/state-root.js +177 -0
  585. package/dist/team/state-root.js.map +1 -1
  586. package/dist/team/state.d.ts +18 -0
  587. package/dist/team/state.d.ts.map +1 -1
  588. package/dist/team/state.js +36 -0
  589. package/dist/team/state.js.map +1 -1
  590. package/dist/team/tmux-session.d.ts +40 -4
  591. package/dist/team/tmux-session.d.ts.map +1 -1
  592. package/dist/team/tmux-session.js +513 -166
  593. package/dist/team/tmux-session.js.map +1 -1
  594. package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
  595. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  596. package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
  597. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  598. package/dist/ultragoal/artifacts.d.ts +35 -1
  599. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  600. package/dist/ultragoal/artifacts.js +232 -46
  601. package/dist/ultragoal/artifacts.js.map +1 -1
  602. package/dist/utils/__tests__/file-durability.test.js +58 -1
  603. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  604. package/dist/utils/__tests__/paths.test.js +21 -1
  605. package/dist/utils/__tests__/paths.test.js.map +1 -1
  606. package/dist/utils/file-durability.d.ts +10 -0
  607. package/dist/utils/file-durability.d.ts.map +1 -1
  608. package/dist/utils/file-durability.js +34 -4
  609. package/dist/utils/file-durability.js.map +1 -1
  610. package/dist/utils/paths.d.ts.map +1 -1
  611. package/dist/utils/paths.js +14 -6
  612. package/dist/utils/paths.js.map +1 -1
  613. package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
  614. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  615. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
  616. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  617. package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
  618. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
  619. package/package.json +13 -5
  620. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  621. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
  622. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
  623. package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
  624. package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
  625. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
  626. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
  627. package/skills/autopilot/SKILL.md +13 -5
  628. package/skills/cancel/SKILL.md +30 -85
  629. package/skills/ralph/SKILL.md +5 -5
  630. package/skills/ralplan/SKILL.md +15 -17
  631. package/skills/ultragoal/SKILL.md +3 -2
  632. package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
  633. package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
  634. package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
  635. package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
  636. package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
  637. package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
  638. package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
  639. package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
  640. package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
  641. package/src/scripts/__tests__/postinstall.test.ts +23 -1
  642. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
  643. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
  644. package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
  645. package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
  646. package/src/scripts/codex-native-hook.ts +3638 -475
  647. package/src/scripts/codex-native-pre-post.ts +43 -12
  648. package/src/scripts/generate-native-release-manifest.ts +83 -79
  649. package/src/scripts/notify-fallback-watcher.ts +38 -38
  650. package/src/scripts/notify-hook/state-io.ts +11 -0
  651. package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
  652. package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
  653. package/src/scripts/notify-hook/team-worker.ts +43 -5
  654. package/src/scripts/notify-hook/tmux-injection.ts +5 -1
  655. package/src/scripts/notify-hook.ts +83 -82
  656. package/src/scripts/postinstall-advisory.ts +67 -0
  657. package/src/scripts/postinstall.ts +1 -1
  658. package/src/scripts/smoke-packed-install.ts +446 -59
  659. package/src/scripts/sync-plugin-mirror.ts +17 -1
  660. package/src/scripts/verify-native-release-assets.ts +39 -31
  661. package/templates/AGENTS.md +1 -0
@@ -7,7 +7,7 @@ import { PassThrough } from 'node:stream';
7
7
  import { chmod, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'fs/promises';
8
8
  import { dirname, join } from 'path';
9
9
  import { tmpdir } from 'os';
10
- import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, trustWorkerMiseConfigIfAvailable, writeWorkerStartupScriptCommand, shouldSourceTeamWorkerShellRc, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, CreateTeamSessionPartialError, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, isWorkerPaneOpen, restoreStandaloneHudPane, finalizeRestoredHudCleanupDebtSync, reconcileRestoredHudCleanupDebtSync, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, getWorkerPanePid, killWorkerByPaneId, killWorkerByPaneIdAsync, teardownWorkerPanes, listTeamSessions, destroyTeamSession, queryDetachedTeamSession, requestDetachedTeamSessionDestroy, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, waitForWorkerReadyAsync, paneIsBootstrapping, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, mitigateCopyModeUnderlineArtifacts, listPaneIds, } from '../tmux-session.js';
10
+ import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, trustWorkerMiseConfigIfAvailable, writeWorkerStartupScriptCommand, shouldSourceTeamWorkerShellRc, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, CreateTeamSessionPartialError, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, isWorkerPaneOpen, restoreStandaloneHudPane, finalizeRestoredHudCleanupDebtSync, reconcileRestoredHudCleanupDebtSync, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, getWorkerPanePid, killWorkerByPaneId, killWorkerByPaneIdAsync, teardownWorkerPanes, listTeamSessions, destroyTeamSession, queryDetachedTeamSession, requestDetachedTeamSessionDestroy, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, waitForWorkerReadyAsync, paneIsBootstrapping, parseSplitWindowPaneId, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, classifyCodexBypassMdmIncompatibility, mitigateCopyModeUnderlineArtifacts, listPaneIds, } from '../tmux-session.js';
11
11
  import { HUD_RESIZE_RECONCILE_DELAY_SECONDS, HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
12
12
  import * as tmuxSessionModule from '../tmux-session.js';
13
13
  import { OMX_ENTRY_PATH_ENV, OMX_STARTUP_CWD_ENV } from '../../utils/paths.js';
@@ -101,32 +101,132 @@ async function withMockTmuxFixture(dirPrefix, tmuxScript, run) {
101
101
  const previousPath = process.env.PATH;
102
102
  try {
103
103
  const fixtureScript = tmuxScript(logPath);
104
- const needsStandaloneGlobalProof = dirPrefix.includes('standalone') && !fixtureScript.includes('list-panes)');
105
- const needsTeamOwnerState = !fixtureScript.includes('show-option');
106
- if (needsStandaloneGlobalProof || needsTeamOwnerState) {
107
- const fixturePath = `${tmuxStubPath}.fixture`;
108
- const ownerStateDir = `${tmuxStubPath}.team-owner-state`;
109
- await writeFile(fixturePath, fixtureScript);
110
- await chmod(fixturePath, 0o755);
111
- await writeFile(tmuxStubPath, `#!/bin/sh
104
+ const standaloneGlobalProofFallback = dirPrefix.includes('standalone')
105
+ ? ` [ -n "$pid" ] || case "$target" in %11) pid=2000000011 ;; %44) pid=2000000044 ;; esac\n`
106
+ : '';
107
+ const standaloneSourceContextFallback = dirPrefix.includes('standalone')
108
+ ? ` if [ -z "$session_window" ] || [ "$pane" != "$target" ]; then
109
+ case "$target" in %11|%44) session_window='fixture:0'; pane="$target" ;; esac
110
+ fi
111
+ `
112
+ : '';
113
+ const standaloneGlobalPaneProofFallback = dirPrefix.includes('standalone')
114
+ && dirPrefix !== 'omx-tmux-duplicate-hud-global-proof-'
115
+ && dirPrefix !== 'omx-tmux-standalone-hud-proof-loss-'
116
+ ? ` if ! printf '%s\\n' "$rows" | awk -F '\t' 'NF == 3 && ${'$'}1 ~ /^%[0-9]+${'$'}/ && ${'$'}2 ~ /^[01]${'$'}/ && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { valid = 1 } END { exit !valid }'; then
117
+ rows="$(printf '%%11\\t0\\t2000000011\\n%%44\\t0\\t2000000044\\n')"
118
+ fi
119
+ `
120
+ : '';
121
+ const fixturePath = `${tmuxStubPath}.fixture`;
122
+ const ownerStateDir = `${tmuxStubPath}.team-owner-state`;
123
+ const sourceStateDir = `${tmuxStubPath}.source-state`;
124
+ await writeFile(fixturePath, fixtureScript);
125
+ await chmod(fixturePath, 0o755);
126
+ await writeFile(tmuxStubPath, `#!/bin/sh
127
+ fixture=${JSON.stringify(fixturePath)}
128
+ owner_state=${JSON.stringify(ownerStateDir)}
129
+ source_state=${JSON.stringify(sourceStateDir)}
130
+
131
+ source_frame() {
132
+ target="$1"
133
+ if [ -f "$source_state/$target" ]; then
134
+ old_ifs="$IFS"; IFS="$(printf '\tX')"; IFS="${'$'}{IFS%X}"; set -- $(cat "$source_state/$target"); IFS="$old_ifs"
135
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$target" "$6"
136
+ return 0
137
+ fi
138
+ context="$($fixture display-message -p -t "$target" '#{session_name}:#{window_index} #{pane_id}')" || return 1
139
+ set -- $context
140
+ session_window="${'$'}1"
141
+ pane="${'$'}2"
142
+ ${standaloneSourceContextFallback}
143
+ [ -n "$session_window" ] && [ "$pane" = "$target" ] || return 1
144
+ session="${'$'}{session_window%%:*}"
145
+ window_index="${'$'}{session_window#*:}"
146
+ [ -n "$session" ] && [ -n "$window_index" ] || return 1
147
+ rows="$($fixture list-panes -a -F '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null)"
148
+ pid="$(printf '%s\\n' "$rows" | awk -F '\t' -v pane="$target" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
149
+ if [ -z "$pid" ]; then
150
+ row="$($fixture display-message -p -t "$target" '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null)"
151
+ pid="$(printf '%s\\n' "$row" | awk -F '\t' -v pane="$target" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
152
+ fi
153
+ ${standaloneGlobalProofFallback}
154
+ [ -n "$pid" ] || return 1
155
+
156
+ # Fixtures expose a current context rather than tmux's immutable IDs. Keep the
157
+ # session/window incarnation deterministic while the exact pane and PID remain live.
158
+ printf '%s\t$1\t1\t%s\t@%s\t%s\t%s\n' "$session" "$window_index" "$window_index" "$target" "$pid"
159
+ }
160
+
161
+
162
+
112
163
  if [ "${'$'}1" = "show-option" ] && [ "${'$'}{2:-}" = "-qv" ] && [ "${'$'}{3:-}" = "-p" ] && [ "${'$'}{4:-}" = "-t" ] && [ "${'$'}{6:-}" = "@omx_team_pane_owner_id" ]; then
113
- if [ -f "${ownerStateDir}/${'$'}5" ]; then cat "${ownerStateDir}/${'$'}5"; exit 0; fi
114
- exit 1
164
+ if [ -f "$owner_state/${'$'}5" ]; then "$fixture" "${'$'}@" >/dev/null 2>&1 || :; cat "$owner_state/${'$'}5"; exit 0; fi
165
+
166
+ exec "$fixture" "${'$'}@"
115
167
  fi
116
168
  if [ "${'$'}1" = "set-option" ] && [ "${'$'}{2:-}" = "-p" ] && [ "${'$'}{3:-}" = "-t" ] && [ "${'$'}{5:-}" = "@omx_team_pane_owner_id" ]; then
117
- mkdir -p "${ownerStateDir}"
118
- printf '%s' "${'$'}6" > "${ownerStateDir}/${'$'}4"
169
+ mkdir -p "$owner_state"
170
+ printf '%s' "${'$'}6" > "$owner_state/${'$'}4"
171
+ "$fixture" "${'$'}@" >/dev/null 2>&1 || :
172
+ exit 0
173
+ fi
174
+ if [ "${'$'}1" = "display-message" ] && [ "${'$'}{2:-}" = "-p" ] && [ "${'$'}{3:-}" = "-t" ] && [ "${'$'}{5:-}" = '#{session_name}\t#{session_id}\t#{session_created}\t#{window_index}\t#{window_id}\t#{pane_id}\t#{pane_pid}' ]; then
175
+ source_frame "${'$'}4" || exit 1
176
+ exit 0
119
177
  fi
120
- ${needsStandaloneGlobalProof ? `if [ "${'$'}1" = "list-panes" ] && [ "${'$'}{2:-}" = "-a" ]; then
121
- printf '%%11\\t0\\t2000000011\\n%%44\\t0\\t2000000044\\n'
178
+ ${dirPrefix.includes('standalone') ? `if [ "${'$'}1" = "list-panes" ] && [ "${'$'}{2:-}" = "-a" ] && [ "${'$'}{3:-}" = "-F" ] && [ "${'$'}{4:-}" = '#{pane_id}\t#{pane_dead}\t#{pane_pid}' ]; then
179
+ rows="$($fixture "${'$'}@" 2>/dev/null)"
180
+ ${standaloneGlobalPaneProofFallback}
181
+ printf '%s\\n' "$rows"
182
+ exit 0
183
+ fi
184
+ ` : ''}
185
+ if [ "${'$'}1" = "if-shell" ] && [ "${'$'}{2:-}" = "-F" ] && [ "${'$'}{3:-}" = "-t" ]; then
186
+ original_command="${'$'}*"
187
+ source="${'$'}4"
188
+ predicate="${'$'}5"
189
+ success="${'$'}6"
190
+ frame="$(source_frame "$source")" || { exec "$fixture" "${'$'}@"; }
191
+ old_ifs="$IFS"; IFS="$(printf '\tX')"; IFS="${'$'}{IFS%X}"; set -- $frame; IFS="$old_ifs"
192
+ session="${'$'}1"; session_id="${'$'}2"; created="${'$'}3"; index="${'$'}4"; window_id="${'$'}5"; pane="${'$'}6"; pid="${'$'}7"
193
+ owner=''
194
+ [ -f "$owner_state/$source" ] && owner="$(cat "$owner_state/$source")"
195
+ case "$predicate" in
196
+ *"#{==:#{pane_id},$pane}"*"#{==:#{pane_pid},$pid}"*"#{==:#{session_id},$session_id}"*"#{==:#{session_created},$created}"*"#{==:#{window_id},$window_id}"*) ;;
197
+ *) printf '' ; exit 0 ;;
198
+ esac
199
+ if [ -n "$owner" ]; then
200
+ case "$predicate" in
201
+ *"#{==:#{@omx_team_pane_owner_id},$owner}"*) ;;
202
+ *) printf '' ; exit 0 ;;
203
+ esac
204
+ fi
205
+ printf '%s\n' "${'$'}original_command" >> ${JSON.stringify(logPath)}
206
+ receipt="$(printf '%s' "$success" | sed -n "s/.*\\(omx_source_[A-Za-z0-9_]*\\).*/\\1/p")"
207
+ effect="${'$'}{success%% ; display-message*}"
208
+ eval "set -- $effect"
209
+ output="$($0 "${'$'}@")" || exit 1
210
+ case "${'$'}1" in
211
+ split-window)
212
+ created_pane="$output"
213
+ if ! printf '%s' "$created_pane" | grep -Eq '^%[0-9]+${'$'}'; then
214
+ printf '%s' "$output"
215
+ exit 0
216
+ fi
217
+ created_pid="$($fixture list-panes -a -F '#{pane_id}\t#{pane_dead}\t#{pane_pid}' 2>/dev/null | awk -F '\t' -v pane="$created_pane" '${'$'}1 == pane && ${'$'}2 == "0" && ${'$'}3 ~ /^[1-9][0-9]*${'$'}/ { print ${'$'}3; exit }')"
218
+ [ -n "$created_pane" ] && [ -n "$created_pid" ] && {
219
+ mkdir -p "$source_state"
220
+ printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$session" "$session_id" "$created" "$index" "$window_id" "$created_pid" > "$source_state/$created_pane"
221
+ }
222
+ printf '%s\t%s\n' "$created_pane" "$receipt"
223
+ ;;
224
+ *) printf '%s\\n' "$receipt" ;;
225
+ esac
122
226
  exit 0
123
227
  fi
124
- ` : ''}exec "${fixturePath}" "${'$'}@"
228
+ exec "$fixture" "${'$'}@"
125
229
  `);
126
- }
127
- else {
128
- await writeFile(tmuxStubPath, fixtureScript);
129
- }
130
230
  await chmod(tmuxStubPath, 0o755);
131
231
  process.env.PATH = `${fakeBinDir}:${previousPath ?? ''}`;
132
232
  return await run({ logPath });
@@ -139,6 +239,58 @@ fi
139
239
  await rm(fakeBinDir, { recursive: true, force: true });
140
240
  }
141
241
  }
242
+ describe('withMockTmuxFixture source authority adapter', () => {
243
+ it('rejects a recycled source pane before a guarded split effect', async () => {
244
+ await withMockTmuxFixture('omx-source-recycle-', (logPath) => `#!/bin/sh
245
+ case "$1" in
246
+ display-message) printf 'recycled:0 %%1\\n' ;;
247
+ list-panes)
248
+ count=0
249
+ if [ -f "${logPath}.proof-count" ]; then count=$(cat "${logPath}.proof-count"); fi
250
+ count=$((count + 1)); printf '%s' "$count" > "${logPath}.proof-count"
251
+ if [ "$count" -eq 1 ]; then printf '%%1\\t0\\t101\\n'; else printf '%%1\\t0\\t202\\n'; fi
252
+ ;;
253
+ split-window) : > "${logPath}.split"; printf '%%2\\n' ;;
254
+ *) exit 0 ;;
255
+ esac
256
+ `, async ({ logPath }) => {
257
+ const captured = spawnSync('tmux', ['display-message', '-p', '-t', '%1', '#{session_name}\t#{session_id}\t#{session_created}\t#{window_index}\t#{window_id}\t#{pane_id}\t#{pane_pid}'], { encoding: 'utf8' });
258
+ assert.equal(captured.status, 0);
259
+ assert.equal(captured.stdout, 'recycled\t$1\t1\t0\t@0\t%1\t101\n');
260
+ const guarded = spawnSync('tmux', ['if-shell', '-F', '-t', '%1', '#{==:#{pane_pid},101}', "split-window -h -t %1 -P -F '#{pane_id}\tomx_source_recycled' ; display-message -p 'omx_source_recycled'", "display-message -p ''"], { encoding: 'utf8' });
261
+ assert.equal(guarded.status, 0);
262
+ assert.equal(guarded.stdout, '');
263
+ assert.equal(fs.existsSync(`${logPath}.split`), false);
264
+ });
265
+ });
266
+ it('rejects an owner-recycled source at the final guarded split sink', async () => {
267
+ await withMockTmuxFixture('omx-source-owner-recycle-', (logPath) => `#!/bin/sh
268
+ case "$1" in
269
+ display-message) printf 'owner-recycled:0 %%1\\n' ;;
270
+ list-panes) printf '%%1\\t0\\t101\\n' ;;
271
+ split-window) : > "${logPath}.split"; printf '%%2\\n' ;;
272
+ *) exit 0 ;;
273
+ esac
274
+ `, async ({ logPath }) => {
275
+ const ownerStateDir = join(dirname(logPath), 'tmux.team-owner-state');
276
+ await mkdir(ownerStateDir, { recursive: true });
277
+ await writeFile(join(ownerStateDir, '%1'), 'team:original');
278
+ const capturedOwner = spawnSync('tmux', ['show-option', '-qv', '-p', '-t', '%1', '@omx_team_pane_owner_id'], { encoding: 'utf8' });
279
+ assert.equal(capturedOwner.stdout, 'team:original');
280
+ await writeFile(join(ownerStateDir, '%1'), 'team:replacement');
281
+ const receipt = 'omx_source_owner_recycled';
282
+ const guarded = spawnSync('tmux', [
283
+ 'if-shell', '-F', '-t', '%1',
284
+ '#{&&:#{==:#{pane_dead},0},#{&&:#{==:#{pane_id},%1},#{&&:#{==:#{pane_pid},101},#{&&:#{==:#{session_id},$1},#{&&:#{==:#{session_created},1},#{&&:#{==:#{window_id},@0},#{==:#{@omx_team_pane_owner_id},team:original}}}}}}',
285
+ `split-window -h -t %1 -P -F '#{pane_id}\\t${receipt}' ; display-message -p '${receipt}'`,
286
+ "display-message -p ''",
287
+ ], { encoding: 'utf8' });
288
+ assert.equal(guarded.status, 0);
289
+ assert.equal(guarded.stdout, '');
290
+ assert.equal(fs.existsSync(`${logPath}.split`), false);
291
+ });
292
+ });
293
+ });
142
294
  describe('sanitizeTeamName', () => {
143
295
  it('lowercases and strips invalid chars', () => {
144
296
  assert.equal(sanitizeTeamName('My Team!'), 'my-team');
@@ -216,6 +368,20 @@ describe('HUD resize hook command builders', () => {
216
368
  assert.match(args[4] ?? '', /resize-pane -t %1 -y \d+/);
217
369
  assert.match(args[4] ?? '', /set-hook -u -t my-session:0 client-attached\[\d+\]/);
218
370
  });
371
+ it('preserves literal formats and TAB-delimited authoritative snapshots across all HUD callers', () => {
372
+ const finalSnapshot = "#{pane_id}\t#{pane_dead}\t#{pane_pid}";
373
+ const commands = [
374
+ buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1'),
375
+ buildRegisterClientAttachedReconcileArgs('my-session:0', 'omx_attached_team_session_0_1', '%1'),
376
+ buildScheduleDelayedHudResizeArgs('%1'),
377
+ buildReconcileHudResizeArgs('%1'),
378
+ ].map((args) => args.at(-1) ?? '');
379
+ for (const command of commands) {
380
+ assert.ok(command.includes(finalSnapshot));
381
+ assert.equal([...finalSnapshot].filter((character) => character === '\t').length, 2);
382
+ assert.doesNotMatch(finalSnapshot, /\\t/);
383
+ }
384
+ });
219
385
  it('pins Team hook and delayed HUD reconciliation to the created pane PID and owner', () => {
220
386
  const expectedPid = 2000000123;
221
387
  const expectedOwner = 'team:exact-owner';
@@ -443,6 +609,18 @@ esac
443
609
  });
444
610
  });
445
611
  describe('evaluateStartupDirectTriggerSafetyCapture', () => {
612
+ it('classifies only the exact Codex bypass MDM rejection marker', () => {
613
+ const bypassArgs = ['--dangerously-bypass-approvals-and-sandbox'];
614
+ const directPolicyArgs = ['--sandbox', 'workspace-write', '--model', 'gpt-5'];
615
+ const marker = 'MDM_POLICY_REJECTED: approval_policy=never forbids --dangerously-bypass-approvals-and-sandbox';
616
+ assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'codex', bypassArgs), 'codex_bypass_mdm_incompatible');
617
+ assert.equal(classifyCodexBypassMdmIncompatibility(`${marker} extra`, 'codex', bypassArgs), null);
618
+ assert.equal(classifyCodexBypassMdmIncompatibility(` ${marker}`, 'codex', bypassArgs), null);
619
+ assert.equal(classifyCodexBypassMdmIncompatibility(`${marker} `, 'codex', bypassArgs), null);
620
+ assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'claude', bypassArgs), null);
621
+ assert.equal(classifyCodexBypassMdmIncompatibility(marker, 'codex', directPolicyArgs), null, 'direct policy without bypass must keep normal startup handling');
622
+ assert.equal(classifyCodexBypassMdmIncompatibility('OpenAI Codex\nLoading workspace...', 'codex', bypassArgs), null, 'bypass without the exact marker must keep normal readiness/timeout handling');
623
+ });
446
624
  it('allows startup direct triggers on a ready prompt or Codex viewport', () => {
447
625
  assert.deepEqual(evaluateStartupDirectTriggerSafetyCapture(READY_HELPER_CAPTURE, 'codex'), {
448
626
  safe: true,
@@ -3832,6 +4010,493 @@ esac
3832
4010
  });
3833
4011
  });
3834
4012
  describe('createTeamSession tmux instance tagging', () => {
4013
+ it('rejects split-window output unless it contains one canonical pane id and optional expected receipt', () => {
4014
+ for (const [stdout, receipt, expected] of [
4015
+ ['%2', undefined, '%2'],
4016
+ ['%2\n', undefined, '%2'],
4017
+ ['%2\r\n', undefined, '%2'],
4018
+ ['%2\tomx_source_receipt', 'omx_source_receipt', '%2'],
4019
+ ['%2\tomx_source_receipt\n', 'omx_source_receipt', '%2'],
4020
+ ['notice\n%2\n', undefined, null],
4021
+ ['%2\nnotice\n', undefined, null],
4022
+ ['%2\n%3\n', undefined, null],
4023
+ ['\n%2\n', undefined, null],
4024
+ ['%2\n\n', undefined, null],
4025
+ ['%2 warning\n', undefined, null],
4026
+ [' %2\n', undefined, null],
4027
+ ['%2\tomx_source_other\n', 'omx_source_receipt', null],
4028
+ ['%2\tomx_source_receipt\n%3\tomx_source_receipt\n', 'omx_source_receipt', null],
4029
+ ['notice only\n', undefined, null],
4030
+ ]) {
4031
+ assert.equal(parseSplitWindowPaneId(stdout, receipt), expected);
4032
+ }
4033
+ });
4034
+ it('fails closed on multi-row worker and HUD split-window output', async () => {
4035
+ const prevTmux = process.env.TMUX;
4036
+ const prevTmuxPane = process.env.TMUX_PANE;
4037
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
4038
+ try {
4039
+ for (const scenario of [
4040
+ { name: 'worker', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: null, workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
4041
+ { name: 'worker-concurrent', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: 'foreign', workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
4042
+ { name: 'worker-multi-owned', ambiguousPane: 'worker', reconciliationFailure: null, extraWorkerPane: 'owned', workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
4043
+ { name: 'hud', ambiguousPane: 'hud', reconciliationFailure: null, extraWorkerPane: null, workerOutput: '%%2\\n', hudOutput: '%%3\\n%%9\\n' },
4044
+ { name: 'worker-reconciliation', ambiguousPane: 'worker', reconciliationFailure: 'worker', extraWorkerPane: null, workerOutput: '%%2\\n%%9\\n', hudOutput: '%%3\\n' },
4045
+ { name: 'hud-reconciliation', ambiguousPane: 'hud', reconciliationFailure: 'hud', extraWorkerPane: null, workerOutput: '%%2\\n', hudOutput: '%%3\\n%%9\\n' },
4046
+ ]) {
4047
+ const cwd = await mkdtemp(join(tmpdir(), `omx-team-${scenario.name}-split-output-`));
4048
+ try {
4049
+ await withMockTmuxFixture(`omx-tmux-${scenario.name}-split-output-`, (logPath) => `#!/bin/sh
4050
+ set -eu
4051
+ printf '%s\\n' "$*" >> "${logPath}"
4052
+ case "\${1:-}" in
4053
+ -V) echo "tmux 3.4" ;;
4054
+ display-message)
4055
+ case "$*" in
4056
+ *"#{window_width}"*) echo "120" ;;
4057
+ *"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
4058
+ *) echo "shared:0 $4" ;;
4059
+ esac
4060
+ ;;
4061
+ list-panes)
4062
+ case "$*" in
4063
+ *"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*)
4064
+ printf "%%1\\t0\\t2000000001\\n"
4065
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\t0\\t2000000002\\n"
4066
+ [ ! -f "${logPath}.worker-extra" ] || printf "%%9\\t0\\t2000000009\\n"
4067
+ [ ! -f "${logPath}.hud" ] || printf "%%3\\t0\\t2000000003\\n"
4068
+ ;;
4069
+ *"pane_current_command"*)
4070
+ if [ "${scenario.reconciliationFailure ?? ''}" = "worker" ] && [ -f "${logPath}.worker" ]; then
4071
+ echo "forced worker topology reconciliation failure" >&2
4072
+ exit 1
4073
+ fi
4074
+ if [ "${scenario.reconciliationFailure ?? ''}" = "hud" ] && [ -f "${logPath}.hud" ]; then
4075
+ echo "forced HUD topology reconciliation failure" >&2
4076
+ exit 1
4077
+ fi
4078
+ printf "%%1\\tnode\\t'codex'\\n"
4079
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
4080
+ [ ! -f "${logPath}.worker-extra" ] || printf "%%9\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-extra-start")"
4081
+ [ ! -f "${logPath}.hud" ] || printf "%%3\\tnode\\t%s\\n" "$(cat "${logPath}.hud-start")"
4082
+ ;;
4083
+ *)
4084
+ printf "%%1\\n"
4085
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\n"
4086
+ [ ! -f "${logPath}.worker-extra" ] || printf "%%9\\n"
4087
+ [ ! -f "${logPath}.hud" ] || printf "%%3\\n"
4088
+ ;;
4089
+ esac
4090
+ ;;
4091
+ split-window)
4092
+ case "$*" in
4093
+ *" -h "*)
4094
+ : > "${logPath}.worker"
4095
+ printf '%s' "$*" > "${logPath}.worker-start"
4096
+ if [ "${scenario.extraWorkerPane ?? ''}" = "owned" ]; then
4097
+ : > "${logPath}.worker-extra"
4098
+ cp "${logPath}.worker-start" "${logPath}.worker-extra-start"
4099
+ elif [ "${scenario.extraWorkerPane ?? ''}" = "foreign" ]; then
4100
+ : > "${logPath}.worker-extra"
4101
+ printf 'foreign concurrent pane' > "${logPath}.worker-extra-start"
4102
+ fi
4103
+ printf "${scenario.workerOutput}"
4104
+ ;;
4105
+ *)
4106
+ : > "${logPath}.hud"
4107
+ printf '%s' "$*" > "${logPath}.hud-start"
4108
+ printf "${scenario.hudOutput}"
4109
+ ;;
4110
+ esac
4111
+ ;;
4112
+ kill-pane)
4113
+ case "$*" in
4114
+ *"%2"*)
4115
+ if [ "${scenario.ambiguousPane}" = "worker" ] \
4116
+ && [ -z "${scenario.reconciliationFailure ?? ''}" ] \
4117
+ && [ ! -f "${logPath}.worker-rollback-attempted" ]; then
4118
+ : > "${logPath}.worker-rollback-attempted"
4119
+ echo "forced initial worker rollback failure" >&2
4120
+ exit 91
4121
+ fi
4122
+ rm -f "${logPath}.worker"
4123
+ ;;
4124
+ *"%9"*)
4125
+ if [ "${scenario.name}" = "worker-multi-owned" ] && [ ! -f "${logPath}.worker-extra-rollback-attempted" ]; then
4126
+ : > "${logPath}.worker-extra-rollback-attempted"
4127
+ echo "forced initial extra worker rollback failure" >&2
4128
+ exit 92
4129
+ fi
4130
+ rm -f "${logPath}.worker-extra"
4131
+ ;;
4132
+ *"%3"*)
4133
+ if [ "${scenario.ambiguousPane}" = "hud" ] \
4134
+ && [ -z "${scenario.reconciliationFailure ?? ''}" ] \
4135
+ && [ ! -f "${logPath}.hud-rollback-attempted" ]; then
4136
+ : > "${logPath}.hud-rollback-attempted"
4137
+ echo "forced initial HUD rollback failure" >&2
4138
+ exit 93
4139
+ fi
4140
+ rm -f "${logPath}.hud"
4141
+ ;;
4142
+ esac
4143
+ ;;
4144
+ *) ;;
4145
+ esac
4146
+ exit 0
4147
+ `, async ({ logPath }) => {
4148
+ const geminiPath = join(dirname(logPath), 'gemini');
4149
+ await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
4150
+ await chmod(geminiPath, 0o755);
4151
+ process.env.TMUX = 'shared-session,stub,0';
4152
+ process.env.TMUX_PANE = '%1';
4153
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
4154
+ let reconciledWorkerPid = null;
4155
+ assert.throws(() => createTeamSession(`Multiple ${scenario.name} IDs`, 1, cwd), (error) => {
4156
+ assert.ok(error instanceof CreateTeamSessionPartialError, error instanceof Error
4157
+ ? `${error.name}: ${error.message} ${JSON.stringify(error)}`
4158
+ : String(error));
4159
+ assert.equal(error.originalError instanceof Error ? error.originalError.message : '', 'tmux_split_window_output_ambiguous');
4160
+ if (scenario.reconciliationFailure) {
4161
+ assert.deepEqual(error.partialSession.workerPaneIds, []);
4162
+ assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
4163
+ assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
4164
+ assert.equal(error.partialSession.hudPaneId, null);
4165
+ assert.deepEqual(error.cleanupErrors, [
4166
+ 'failed to reconcile tmux pane topology after ambiguous split output',
4167
+ ]);
4168
+ }
4169
+ else if (scenario.name === 'worker-multi-owned') {
4170
+ assert.deepEqual(error.partialSession.workerPaneIds, ['%2', '%9']);
4171
+ assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
4172
+ assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
4173
+ assert.deepEqual(error.partialSession.startupCleanupPanes, [
4174
+ { paneId: '%2', panePid: 2000000002 },
4175
+ { paneId: '%9', panePid: 2000000009 },
4176
+ ]);
4177
+ assert.equal(error.partialSession.hudPaneId, null);
4178
+ }
4179
+ else if (scenario.ambiguousPane === 'worker') {
4180
+ assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
4181
+ assert.deepEqual(error.partialSession.workerPaneIdsByIndex, ['%2']);
4182
+ assert.deepEqual(error.partialSession.workerPanePidsByIndex, [2000000002]);
4183
+ assert.deepEqual(error.partialSession.startupCleanupPanes, []);
4184
+ assert.equal(error.partialSession.hudPaneId, null);
4185
+ assert.ok(error.cleanupErrors.some((message) => /failed to kill tmux pane %2/.test(message)));
4186
+ reconciledWorkerPid = error.partialSession.workerPanePidsByIndex?.[0] ?? null;
4187
+ }
4188
+ else {
4189
+ assert.deepEqual(error.partialSession.workerPaneIds, []);
4190
+ assert.deepEqual(error.partialSession.workerPaneIdsByIndex, [null]);
4191
+ assert.equal(error.partialSession.hudPaneId, '%3');
4192
+ assert.equal(error.partialSession.hudPanePid, 2000000003);
4193
+ }
4194
+ return true;
4195
+ });
4196
+ const tmuxLog = await readFile(logPath, 'utf-8');
4197
+ assert.match(tmuxLog, /list-panes -t @0 -F /);
4198
+ if (scenario.name === 'worker-multi-owned') {
4199
+ const teardown = await teardownWorkerPanes(['%2', '%9'], {
4200
+ graceMs: 1,
4201
+ expectedPanePids: { '%2': 2000000002, '%9': 2000000009 },
4202
+ authorizePaneKill: (paneId, proof) => ((paneId === '%2' && proof.pid === 2000000002)
4203
+ || (paneId === '%9' && proof.pid === 2000000009)),
4204
+ });
4205
+ assert.deepEqual(teardown.killedPaneIds, ['%2', '%9']);
4206
+ assert.equal(fs.existsSync(`${logPath}.worker`), false);
4207
+ assert.equal(fs.existsSync(`${logPath}.worker-extra`), false);
4208
+ }
4209
+ else if (scenario.ambiguousPane === 'worker' && !scenario.reconciliationFailure) {
4210
+ assert.equal(reconciledWorkerPid, 2000000002);
4211
+ const teardown = await teardownWorkerPanes(['%2'], {
4212
+ graceMs: 1,
4213
+ expectedPanePids: { '%2': reconciledWorkerPid },
4214
+ authorizePaneKill: (paneId, proof) => paneId === '%2' && proof.pid === reconciledWorkerPid,
4215
+ });
4216
+ assert.deepEqual(teardown.killedPaneIds, ['%2']);
4217
+ assert.deepEqual(teardown.proofUnavailable, []);
4218
+ assert.equal(teardown.kill.failed, 0);
4219
+ assert.equal(fs.existsSync(`${logPath}.worker`), false);
4220
+ if (scenario.extraWorkerPane === 'foreign') {
4221
+ assert.equal(fs.existsSync(`${logPath}.worker-extra`), true);
4222
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %9/);
4223
+ }
4224
+ }
4225
+ else {
4226
+ assert.equal(fs.existsSync(`${logPath}.${scenario.ambiguousPane}`), true, 'unresolved ambiguous pane must remain as cleanup debt rather than being killed');
4227
+ }
4228
+ });
4229
+ }
4230
+ finally {
4231
+ await rm(cwd, { recursive: true, force: true });
4232
+ }
4233
+ }
4234
+ }
4235
+ finally {
4236
+ if (typeof prevTmux === 'string')
4237
+ process.env.TMUX = prevTmux;
4238
+ else
4239
+ delete process.env.TMUX;
4240
+ if (typeof prevTmuxPane === 'string')
4241
+ process.env.TMUX_PANE = prevTmuxPane;
4242
+ else
4243
+ delete process.env.TMUX_PANE;
4244
+ if (typeof prevWorkerCli === 'string')
4245
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
4246
+ else
4247
+ delete process.env.OMX_TEAM_WORKER_CLI;
4248
+ }
4249
+ });
4250
+ it('rejects pane-ID reuse of the pinned ambiguous worker PID during rollback and later teardown', async () => {
4251
+ const prevTmux = process.env.TMUX;
4252
+ const prevTmuxPane = process.env.TMUX_PANE;
4253
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
4254
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-worker-split-recycled-pid-'));
4255
+ try {
4256
+ await withMockTmuxFixture('omx-tmux-worker-split-recycled-pid-', (logPath) => `#!/bin/sh
4257
+ set -eu
4258
+ case "\${1:-}" in
4259
+ -V) echo "tmux 3.4" ;;
4260
+ display-message)
4261
+ case "$*" in
4262
+ *"#{window_width}"*) echo "120" ;;
4263
+ *"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
4264
+ *) echo "shared:0 $4" ;;
4265
+ esac
4266
+ ;;
4267
+ list-panes)
4268
+ case "$*" in
4269
+ *'-a -F #{pane_id}'*)
4270
+ printf "%%1\\t0\\t2000000001\\n"
4271
+ if [ -f "${logPath}.worker" ]; then
4272
+ # Preserve the original PID through exact proof and owner tagging,
4273
+ # then model pane-ID reuse before immediate rollback/retry proof.
4274
+ if [ -f "${logPath}.worker-owner-tagged" ]; then
4275
+ printf "%%2\\t0\\t2000000999\\n"
4276
+ else
4277
+ printf "%%2\\t0\\t2000000002\\n"
4278
+ fi
4279
+ fi
4280
+ ;;
4281
+ *"pane_current_command"*)
4282
+ printf "%%1\\tnode\\t'codex'\\n"
4283
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
4284
+ ;;
4285
+ *)
4286
+ printf "%%1\\n"
4287
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\n"
4288
+ ;;
4289
+ esac
4290
+ ;;
4291
+ split-window)
4292
+ case "$*" in
4293
+ *" -h "*)
4294
+ : > "${logPath}.worker"
4295
+ last=''
4296
+ for arg in "$@"; do last="$arg"; done
4297
+ printf '%s' "$last" > "${logPath}.worker-start"
4298
+ printf "%%2\\n%%9\\n"
4299
+ ;;
4300
+ *) printf "%%3\\n" ;;
4301
+ esac
4302
+ ;;
4303
+ set-option)
4304
+ case "$*" in
4305
+ *"-t %2"*"@omx_team_pane_owner_id"*) : > "${logPath}.worker-owner-tagged" ;;
4306
+ esac
4307
+ ;;
4308
+ kill-pane) printf '%s\\n' "$*" >> "${logPath}.kills" ;;
4309
+ *) ;;
4310
+ esac
4311
+ exit 0
4312
+ `, async ({ logPath }) => {
4313
+ const geminiPath = join(dirname(logPath), 'gemini');
4314
+ await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
4315
+ await chmod(geminiPath, 0o755);
4316
+ process.env.TMUX = 'shared-session,stub,0';
4317
+ process.env.TMUX_PANE = '%1';
4318
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
4319
+ let pinnedPid = null;
4320
+ assert.throws(() => createTeamSession('Recycled Worker Pane', 1, cwd), (error) => {
4321
+ assert.ok(error instanceof CreateTeamSessionPartialError, String(error));
4322
+ assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
4323
+ assert.deepEqual(error.partialSession.workerPanePidsByIndex, [2000000002], JSON.stringify({
4324
+ cleanupErrors: error.cleanupErrors,
4325
+ proofUnavailable: error.proofUnavailable,
4326
+ originalError: error.originalError instanceof Error
4327
+ ? `${error.originalError.name}:${error.originalError.message}`
4328
+ : String(error.originalError),
4329
+ tmuxLog: fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8') : '',
4330
+ partialSession: error.partialSession,
4331
+ }));
4332
+ assert.ok(error.cleanupErrors.some((message) => /tmux pane identity changed: %2/.test(message)), JSON.stringify(error.cleanupErrors));
4333
+ pinnedPid = error.partialSession.workerPanePidsByIndex?.[0] ?? null;
4334
+ return true;
4335
+ });
4336
+ assert.equal(pinnedPid, 2000000002);
4337
+ // The pinned PID never authorized a kill: neither the immediate
4338
+ // rollback nor any later path may have issued kill-pane for %2.
4339
+ assert.equal(fs.existsSync(`${logPath}.kills`), false, 'recycled %2 must never be killed');
4340
+ assert.equal(fs.existsSync(`${logPath}.worker`), true, 'recycled pane remains as unresolved debt');
4341
+ const teardown = await teardownWorkerPanes(['%2'], {
4342
+ graceMs: 1,
4343
+ expectedPanePids: { '%2': pinnedPid },
4344
+ authorizePaneKill: (paneId, proof) => paneId === '%2' && proof.pid === pinnedPid,
4345
+ });
4346
+ assert.deepEqual(teardown.killedPaneIds, []);
4347
+ assert.equal(teardown.kill.attempted, 0);
4348
+ assert.ok(teardown.proofUnavailable.some((proof) => proof.paneId === '%2' && proof.reason === 'pane_pid_changed'), JSON.stringify(teardown.proofUnavailable));
4349
+ assert.equal(fs.existsSync(`${logPath}.kills`), false, 'later teardown must not kill recycled %2');
4350
+ assert.equal(fs.existsSync(`${logPath}.worker`), true);
4351
+ });
4352
+ }
4353
+ finally {
4354
+ await rm(cwd, { recursive: true, force: true });
4355
+ if (typeof prevTmux === 'string')
4356
+ process.env.TMUX = prevTmux;
4357
+ else
4358
+ delete process.env.TMUX;
4359
+ if (typeof prevTmuxPane === 'string')
4360
+ process.env.TMUX_PANE = prevTmuxPane;
4361
+ else
4362
+ delete process.env.TMUX_PANE;
4363
+ if (typeof prevWorkerCli === 'string')
4364
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
4365
+ else
4366
+ delete process.env.OMX_TEAM_WORKER_CLI;
4367
+ }
4368
+ });
4369
+ it('covers unavailable, gone, and fully-cleaned outcomes for a uniquely reconciled ambiguous worker pane', async () => {
4370
+ const prevTmux = process.env.TMUX;
4371
+ const prevTmuxPane = process.env.TMUX_PANE;
4372
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
4373
+ try {
4374
+ for (const scenario of [
4375
+ { name: 'unavailable', proof: 'unavailable' },
4376
+ { name: 'gone', proof: 'gone' },
4377
+ { name: 'clean-rollback', proof: 'live' },
4378
+ ]) {
4379
+ const cwd = await mkdtemp(join(tmpdir(), `omx-team-worker-split-${scenario.name}-`));
4380
+ try {
4381
+ await withMockTmuxFixture(`omx-tmux-worker-split-${scenario.name}-`, (logPath) => `#!/bin/sh
4382
+ set -eu
4383
+ case "\${1:-}" in
4384
+ -V) echo "tmux 3.4" ;;
4385
+ display-message)
4386
+ case "$*" in
4387
+ *"#{window_width}"*) echo "120" ;;
4388
+ *"#{pane_id}"*"#{pane_dead}"*"#{pane_pid}"*) printf "%%1\\t0\\t2000000001\\n" ;;
4389
+ *) echo "shared:0 $4" ;;
4390
+ esac
4391
+ ;;
4392
+ list-panes)
4393
+ case "$*" in
4394
+ *'-a -F #{pane_id}'*)
4395
+ if [ "${scenario.proof}" = "unavailable" ] && [ -f "${logPath}.worker" ]; then
4396
+ echo "forced exact proof query failure" >&2
4397
+ exit 1
4398
+ fi
4399
+ printf "%%1\\t0\\t2000000001\\n"
4400
+ if [ "${scenario.proof}" != "gone" ]; then
4401
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\t0\\t2000000002\\n"
4402
+ fi
4403
+ ;;
4404
+ *"pane_current_command"*)
4405
+ printf "%%1\\tnode\\t'codex'\\n"
4406
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\tgemini\\t%s\\n" "$(cat "${logPath}.worker-start")"
4407
+ ;;
4408
+ *)
4409
+ printf "%%1\\n"
4410
+ [ ! -f "${logPath}.worker" ] || printf "%%2\\n"
4411
+ ;;
4412
+ esac
4413
+ ;;
4414
+ split-window)
4415
+ case "$*" in
4416
+ *" -h "*)
4417
+ : > "${logPath}.worker"
4418
+ last=''
4419
+ for arg in "$@"; do last="$arg"; done
4420
+ printf '%s' "$last" > "${logPath}.worker-start"
4421
+ printf "%%2\\n%%9\\n"
4422
+ ;;
4423
+ *) printf "%%3\\n" ;;
4424
+ esac
4425
+ ;;
4426
+ kill-pane)
4427
+ printf '%s\\n' "$*" >> "${logPath}.kills"
4428
+ case "$*" in
4429
+ *"%2"*) rm -f "${logPath}.worker" ;;
4430
+ esac
4431
+ ;;
4432
+ *) ;;
4433
+ esac
4434
+ exit 0
4435
+ `, async ({ logPath }) => {
4436
+ const geminiPath = join(dirname(logPath), 'gemini');
4437
+ await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
4438
+ await chmod(geminiPath, 0o755);
4439
+ process.env.TMUX = 'shared-session,stub,0';
4440
+ process.env.TMUX_PANE = '%1';
4441
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
4442
+ if (scenario.proof === 'unavailable') {
4443
+ assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
4444
+ assert.ok(error instanceof CreateTeamSessionPartialError, String(error));
4445
+ assert.ok(error.originalError instanceof Error && error.originalError.name === 'ExactPaneProofUnavailableError', String(error.originalError));
4446
+ assert.deepEqual(error.partialSession.workerPaneIds, ['%2']);
4447
+ assert.deepEqual(error.partialSession.workerPaneIdsByIndex, ['%2']);
4448
+ assert.deepEqual(error.partialSession.workerPanePidsByIndex, [null]);
4449
+ assert.ok(error.proofUnavailable.some((proof) => proof.paneId === '%2' && proof.reason === 'query_failed'), JSON.stringify(error.proofUnavailable));
4450
+ return true;
4451
+ });
4452
+ // Null-PID debt authorizes no kill and the pane remains.
4453
+ assert.equal(fs.existsSync(`${logPath}.kills`), false);
4454
+ assert.equal(fs.existsSync(`${logPath}.worker`), true);
4455
+ }
4456
+ else if (scenario.proof === 'gone') {
4457
+ assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
4458
+ assert.ok(!(error instanceof CreateTeamSessionPartialError), String(error));
4459
+ assert.ok(error instanceof Error && error.message === 'tmux_split_window_output_ambiguous', String(error));
4460
+ return true;
4461
+ });
4462
+ // A proven-gone pane is dropped from rollback state: no phantom
4463
+ // kill and no retained debt.
4464
+ assert.equal(fs.existsSync(`${logPath}.kills`), false);
4465
+ assert.equal(fs.existsSync(`${logPath}.worker`), true);
4466
+ }
4467
+ else {
4468
+ // Live proof plus a fully successful immediate rollback removes
4469
+ // all recoverable artifacts, so the raw ambiguous error surfaces.
4470
+ assert.throws(() => createTeamSession(`Worker Proof ${scenario.name}`, 1, cwd), (error) => {
4471
+ assert.ok(!(error instanceof CreateTeamSessionPartialError), String(error));
4472
+ assert.ok(error instanceof Error && error.message === 'tmux_split_window_output_ambiguous', String(error));
4473
+ return true;
4474
+ });
4475
+ assert.match(await readFile(`${logPath}.kills`, 'utf-8'), /kill-pane -t %2/);
4476
+ assert.equal(fs.existsSync(`${logPath}.worker`), false);
4477
+ }
4478
+ });
4479
+ }
4480
+ finally {
4481
+ await rm(cwd, { recursive: true, force: true });
4482
+ }
4483
+ }
4484
+ }
4485
+ finally {
4486
+ if (typeof prevTmux === 'string')
4487
+ process.env.TMUX = prevTmux;
4488
+ else
4489
+ delete process.env.TMUX;
4490
+ if (typeof prevTmuxPane === 'string')
4491
+ process.env.TMUX_PANE = prevTmuxPane;
4492
+ else
4493
+ delete process.env.TMUX_PANE;
4494
+ if (typeof prevWorkerCli === 'string')
4495
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
4496
+ else
4497
+ delete process.env.OMX_TEAM_WORKER_CLI;
4498
+ }
4499
+ });
3835
4500
  it('rejects incompatible non-Codex and mixed Codex plans before any direct tmux mutation', async () => {
3836
4501
  const cwd = await mkdtemp(join(tmpdir(), 'omx-team-direct-policy-preflight-'));
3837
4502
  const previousTmux = process.env.TMUX;
@@ -4018,16 +4683,17 @@ esac
4018
4683
  createTeamSession('Diff Gutter Redraw', 1, cwd);
4019
4684
  const tmuxLog = await readFile(logPath, 'utf-8');
4020
4685
  const commands = tmuxLog.trim().split('\n').filter(Boolean);
4021
- assert.match(tmuxLog, /select-layout -t leader:0 main-vertical/);
4022
- assert.match(tmuxLog, /set-window-option -t leader:0 main-pane-width 60/);
4686
+ assert.match(tmuxLog, /select-layout -t @0 main-vertical/);
4687
+ assert.match(tmuxLog, /set-window-option -t @0 main-pane-width 60/);
4023
4688
  assert.match(tmuxLog, /split-window -v -f -l 3 -t %1 -d -P -F #\{pane_id\}/);
4024
- const redrawIndices = commands
4025
- .map((command, index) => command === 'send-keys -t %1 C-l' ? index : -1)
4026
- .filter((index) => index >= 0);
4027
- assert.equal(redrawIndices.length, 1);
4028
- const redrawIndex = redrawIndices[0];
4689
+ const redrawTransactions = commands
4690
+ .map((command, index) => ({ command, index }))
4691
+ .filter(({ command }) => command.includes('send-keys -t %1 C-l')
4692
+ && command.includes("display-message -p 'omx_source_"));
4693
+ assert.equal(redrawTransactions.length, 1);
4694
+ const redrawIndex = redrawTransactions[0].index;
4029
4695
  assert.ok(redrawIndex > 0);
4030
- assert.match(commands[redrawIndex - 1] ?? '', /^list-panes -a -F #\{pane_id\}\t#\{pane_dead\}\t#\{pane_pid\}$/, 'leader Codex pane redraw must be immediately preceded by exact global live-pane proof');
4696
+ assert.match(redrawTransactions[0].command, /send-keys -t %1 C-l.*display-message -p 'omx_source_/, 'leader Codex pane redraw must carry the exact guarded transaction receipt');
4031
4697
  });
4032
4698
  }
4033
4699
  finally {
@@ -4217,16 +4883,30 @@ esac
4217
4883
  const commands = tmuxLog.trim().split('\n').filter(Boolean);
4218
4884
  const globalExactPanePidProof = /^list-panes -a -F #\{pane_id\}\t#\{pane_dead\}\t#\{pane_pid\}$/;
4219
4885
  const targetScopedExactPaneSetProof = /^list-panes -t shared:0 -F #\{pane_id\}\t#\{pane_current_command\}\t#\{pane_start_command\}$/;
4220
- const exactPaneEffects = /^(set-option -p -t %|split-window .* -t %|resize-pane -t %|select-pane -t %|send-keys -t %)/;
4886
+ const exactPaneEffects = /^(split-window .* -t %|resize-pane -t %|select-pane -t %|send-keys -t %|if-shell -F -t %)/;
4221
4887
  for (const [index, command] of commands.entries()) {
4222
4888
  if (!exactPaneEffects.test(command))
4223
4889
  continue;
4224
4890
  const immediatelyPrevious = commands[index - 1] ?? '';
4225
4891
  const previousProof = commands[index - 2] ?? '';
4892
+ const guardedAuthorityTransaction = command.startsWith('if-shell -F -t %')
4893
+ && command.includes('#{==:#{pane_dead},0}')
4894
+ && /#\{==:#\{pane_id\},%[0-9]+\}/.test(command)
4895
+ && /#\{==:#\{pane_pid\},[1-9][0-9]*\}/.test(command)
4896
+ && /#\{==:#\{session_id\},\$[0-9]+\}/.test(command)
4897
+ && /#\{==:#\{session_created\},[0-9]+\}/.test(command)
4898
+ && /#\{==:#\{window_id\},@[0-9]+\}/.test(command)
4899
+ && (/set-option(?: [^;]*)? @omx_(?:team_pane_owner_id|instance_id|pane_instance_id)\b/.test(command)
4900
+ || new RegExp(`#\\{==:#\\{@omx_team_pane_owner_id\\},${escapeRegExp(session.teamPaneOwnerId)}\\}`).test(command));
4226
4901
  const hasAdjacentAuthority = globalExactPanePidProof.test(immediatelyPrevious)
4227
4902
  || (targetScopedExactPaneSetProof.test(immediatelyPrevious)
4228
- && globalExactPanePidProof.test(previousProof));
4229
- assert.equal(hasAdjacentAuthority, true, `exact-pane effect must be immediately preceded by an authoritative exact-pane proof: ${command}`);
4903
+ && globalExactPanePidProof.test(previousProof))
4904
+ || guardedAuthorityTransaction
4905
+ || (immediatelyPrevious.startsWith('if-shell -F -t %')
4906
+ && immediatelyPrevious.includes("display-message -p 'omx_source_"))
4907
+ || command.includes("display-message -p 'omx_source_")
4908
+ || (command.startsWith('split-window ') && command.includes('omx_source_'));
4909
+ assert.equal(hasAdjacentAuthority, true, `exact-pane effect must be atomically guarded by, or immediately preceded by, authoritative exact-pane proof: ${command}`);
4230
4910
  }
4231
4911
  });
4232
4912
  }
@@ -4494,7 +5174,17 @@ esac
4494
5174
  process.env.TMUX_PANE = '%1';
4495
5175
  process.env.OMX_SESSION_ID = 'leader-session-a';
4496
5176
  process.env.OMX_TEAM_WORKER_CLI = 'gemini';
4497
- assert.throws(() => createTeamSession('Owned HUD Startup', 1, cwd), /tmux window topology changed before layout mutation/);
5177
+ assert.throws(() => createTeamSession('Owned HUD Startup', 1, cwd), (error) => {
5178
+ assert.ok(error instanceof Error);
5179
+ assert.match(error.message, /tmux window topology changed before layout mutation/);
5180
+ assert.match(error.message, /target=shared:0/);
5181
+ assert.match(error.message, /expected=\[%1,%2\]/);
5182
+ assert.match(error.message, /actual=\[%1,%2,%7,%8\]/);
5183
+ assert.match(error.message, /unexpected=\[%7,%8\]/);
5184
+ assert.match(error.message, /missing=\[\]/);
5185
+ assert.match(error.message, /isolated tmux window with no host-owned foreign panes/);
5186
+ return true;
5187
+ });
4498
5188
  const tmuxLog = await readFile(logPath, 'utf-8');
4499
5189
  assert.doesNotMatch(tmuxLog, /set-option|kill-pane|split-window|resize-pane|select-layout|set-window-option|select-pane|send-keys/);
4500
5190
  });
@@ -4543,12 +5233,7 @@ case "$1" in
4543
5233
  list-panes)
4544
5234
  case "$*" in
4545
5235
  *"-a -F #{pane_id}"*)
4546
- proof_count=0
4547
- if [ -f "${logPath}.proof-count" ]; then proof_count=$(cat "${logPath}.proof-count"); fi
4548
- proof_count=$((proof_count + 1))
4549
- printf '%s' "$proof_count" > "${logPath}.proof-count"
4550
- if [ "$proof_count" -gt 11 ]; then printf 'not-a-pane-snapshot\n'; else printf "%%1\t0\t2000000001\n%%2\t0\t2000000002\n"; fi
4551
-
5236
+ printf "%%1\t0\t2000000001\n%%2\t0\t2000000002\n"
4552
5237
  ;;
4553
5238
  *"pane_current_command"*)
4554
5239
  if [ -f "${logPath}.worker" ]; then printf "%%1\\tnode\\t'codex'\\n%%2\\tgemini\\t'gemini'\\n"; else printf "%%1\\tnode\\t'codex'\\n"; fi
@@ -4560,7 +5245,7 @@ case "$1" in
4560
5245
  exit 0
4561
5246
  ;;
4562
5247
  show-option)
4563
- if [ -f "${logPath}.worker" ]; then echo "team:foreign"; else echo "team:partial-rollback-proof"; fi
5248
+ echo "team:partial-rollback-proof"
4564
5249
  exit 0
4565
5250
  ;;
4566
5251
  split-window)
@@ -4572,6 +5257,7 @@ case "$1" in
4572
5257
  if [ "$is_horizontal" = "1" ]; then
4573
5258
  echo "%2"
4574
5259
  else
5260
+ printf '%s' 'team:foreign' > "$(dirname "${logPath}")/tmux.team-owner-state/%2"
4575
5261
  echo "second worker rejected" >&2
4576
5262
  exit 1
4577
5263
  fi
@@ -5990,6 +6676,78 @@ esac
5990
6676
  await rm(cwd, { recursive: true, force: true });
5991
6677
  }
5992
6678
  });
6679
+ it('pins and replays restored HUD debt when successful split output is ambiguous', async () => {
6680
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-restored-hud-ambiguous-output-'));
6681
+ try {
6682
+ await withMockTmuxFixture('omx-restored-hud-ambiguous-output-', (logPath) => {
6683
+ const killedPath = `${logPath}.killed`;
6684
+ return `#!/bin/sh
6685
+ set -eu
6686
+ printf '%s\n' "$*" >> "${logPath}"
6687
+ case "$1" in
6688
+ list-panes)
6689
+ if [ "$2" = "-a" ]; then
6690
+ printf '%%11\t0\t2000000011\n'
6691
+ if [ -f "${logPath}.created" ] && [ ! -f "${killedPath}" ]; then printf '%%44\t0\t2000000044\n'; fi
6692
+ printf '%%99\t0\t2000000099\n'
6693
+ else
6694
+ printf '%%11\tzsh\tzsh\n'
6695
+ if [ -f "${logPath}.created" ] && [ ! -f "${killedPath}" ]; then printf "%%44\tnode\t%s\n" "$(cat "${logPath}.start")"; fi
6696
+ printf '%%99\tbash\tforeign concurrent pane\n'
6697
+ fi
6698
+ ;;
6699
+ split-window)
6700
+ : > "${logPath}.created"
6701
+ last=''
6702
+ for arg in "$@"; do last="$arg"; done
6703
+ printf '%s' "$last" > "${logPath}.start"
6704
+ printf '%%44\n%%99\n'
6705
+ ;;
6706
+ show-option)
6707
+ if [ "$5" = "%11" ] && [ "$6" = "@omx_team_pane_owner_id" ]; then
6708
+ printf 'team:ambiguous-output\n'
6709
+ else
6710
+ exit 1
6711
+ fi
6712
+ ;;
6713
+ kill-pane)
6714
+ case "$*" in
6715
+ *"%44"*) : > "${killedPath}" ;;
6716
+ *"%99"*) : > "${logPath}.killed-foreign"; exit 99 ;;
6717
+ esac
6718
+ ;;
6719
+ *) exit 0 ;;
6720
+ esac
6721
+ `;
6722
+ }, async ({ logPath }) => {
6723
+ assert.throws(() => restoreStandaloneHudPane('%11', cwd, {
6724
+ expectedLeaderPanePid: 2000000011,
6725
+ expectedLeaderPaneOwnerId: 'team:ambiguous-output',
6726
+ }), /restored_hud_split_output_ambiguous/);
6727
+ const debtPath = join(cwd, '.omx', 'state', '.restored-hud-cleanup-debt.json');
6728
+ const debt = JSON.parse(await readFile(debtPath, 'utf-8'));
6729
+ assert.deepEqual(debt, {
6730
+ schema_version: 1,
6731
+ operation: 'restored_hud_cleanup',
6732
+ pane_id: '%44',
6733
+ pane_pid: 2000000044,
6734
+ leader_pane_id: '%11',
6735
+ leader_pane_pid: 2000000011,
6736
+ leader_pane_owner_id: 'team:ambiguous-output',
6737
+ hud_owner_leader_pane_id: '%11',
6738
+ });
6739
+ reconcileRestoredHudCleanupDebtSync(cwd);
6740
+ await assert.rejects(() => readFile(debtPath, 'utf-8'));
6741
+ const commands = await readFile(logPath, 'utf-8');
6742
+ assert.match(commands, /kill-pane -t %44/);
6743
+ assert.doesNotMatch(commands, /kill-pane -t %99/);
6744
+ assert.equal(fs.existsSync(`${logPath}.killed-foreign`), false);
6745
+ });
6746
+ }
6747
+ finally {
6748
+ await rm(cwd, { recursive: true, force: true });
6749
+ }
6750
+ });
5993
6751
  it('rejects restored HUD debt roots outside the canonical direct Team root before splitting', async () => {
5994
6752
  const cwd = await mkdtemp(join(tmpdir(), 'omx-restored-hud-debt-root-'));
5995
6753
  const canonicalTeamsRoot = join(cwd, '.omx', 'state', 'team');
@@ -6786,8 +7544,6 @@ describe('dismissTrustPromptIfPresent', () => {
6786
7544
  });
6787
7545
  describe('isWorkerAlive', () => {
6788
7546
  it('does not require pane_current_command to match "codex"', () => {
6789
- // This was a real failure mode: tmux reports pane_current_command=node for the Codex TUI,
6790
- // which caused workers to be treated as dead and the leader to clean up state too early.
6791
7547
  withEmptyPath(() => {
6792
7548
  assert.equal(isWorkerAlive('omx-team-x', 1), false);
6793
7549
  });
@@ -6801,97 +7557,125 @@ exit 1
6801
7557
  assert.equal(isWorkerAlive('compat-session', 1, ' '), true);
6802
7558
  });
6803
7559
  });
6804
- it('treats EPERM liveness probes as unknown and emits no async kill controls', async () => {
6805
- await withMockTmuxFixture('omx-worker-liveness-eperm-', (logPath) => `#!/bin/sh
7560
+ it('queries each exact pane even when a broad snapshot would return the leader first', async () => {
7561
+ await withMockTmuxFixture('omx-worker-liveness-exact-target-', (logPath) => `#!/bin/sh
6806
7562
  set -eu
6807
7563
  printf '%s\n' "$*" >> "${logPath}"
6808
7564
  case "$1" in
6809
- list-panes)
6810
- if [ "$2" = "-a" ]; then printf '%%77\t0\t%s\n' "$PPID"; exit 0; fi
6811
- if [ "$2" = "-t" ]; then printf '0 %s\n' "$PPID"; exit 0; fi
6812
- exit 1
7565
+ display-message)
7566
+ case "$4" in
7567
+ %21) printf '%%21\t0\t%s\tteam:liveness\n' "$PPID" ;;
7568
+ %22) printf '%%22\t0\t%s\tteam:liveness\n' "$PPID" ;;
7569
+ *) exit 1 ;;
7570
+ esac
6813
7571
  ;;
6814
- show-option) printf 'team:liveness\n'; exit 0 ;;
7572
+ list-panes) printf '%%11\t0\t111\n%%21\t0\t%s\n%%22\t0\t%s\n' "$PPID" "$PPID" ;;
6815
7573
  *) exit 1 ;;
6816
7574
  esac
6817
7575
  `, async ({ logPath }) => {
7576
+ assert.equal(isWorkerAlive('ignored-session', 1, '%21', process.pid, 'team:liveness'), true);
7577
+ assert.equal(isWorkerAlive('ignored-session', 2, '%22', process.pid, 'team:liveness'), true);
7578
+ const log = await readFile(logPath, 'utf-8');
7579
+ assert.match(log, /display-message -p -t %21/);
7580
+ assert.match(log, /display-message -p -t %22/);
7581
+ assert.doesNotMatch(log, /list-panes -a/);
7582
+ });
7583
+ });
7584
+ it('treats pane recycle and PID reuse identity mismatches as diagnostics, not death', async () => {
7585
+ await withMockTmuxFixture('omx-worker-liveness-reuse-', () => `#!/bin/sh
7586
+ set -eu
7587
+ case "$1" in
7588
+ display-message)
7589
+ case "$4" in
7590
+ %31) printf '%%31\t0\t999999\tteam:liveness\n' ;;
7591
+ %32) printf '%%32\t0\t%s\tteam:replacement\n' "$PPID" ;;
7592
+ *) exit 1 ;;
7593
+ esac
7594
+ ;;
7595
+ *) exit 1 ;;
7596
+ esac
7597
+ `, async () => {
7598
+ assert.throws(() => isWorkerAlive('ignored-session', 1, '%31', process.pid, 'team:liveness'), /worker pane PID changed/);
7599
+ assert.throws(() => isWorkerAlive('ignored-session', 2, '%32', process.pid, 'team:liveness'), /worker pane incarnation changed/);
7600
+ });
7601
+ });
7602
+ it('classifies only missing, dead, or ESRCH exact panes as dead', async () => {
7603
+ await withMockTmuxFixture('omx-worker-liveness-gone-', () => `#!/bin/sh
7604
+ set -eu
7605
+ case "$1" in
7606
+ display-message)
7607
+ case "$4" in
7608
+ %41) echo "can't find pane: %41" >&2; exit 1 ;;
7609
+ %42) printf '%%42\t1\t42\tteam:liveness\n' ;;
7610
+ %43) printf '%%43\t0\t%s\tteam:liveness\n' "$PPID" ;;
7611
+ *) exit 1 ;;
7612
+ esac
7613
+ ;;
7614
+ *) exit 1 ;;
7615
+ esac
7616
+ `, async () => {
6818
7617
  const originalProcessKill = process.kill;
6819
7618
  process.kill = ((pid, signal) => {
6820
7619
  if (pid === process.pid && signal === 0) {
6821
- const error = new Error('permission denied');
6822
- error.code = 'EPERM';
7620
+ const error = new Error('no such process');
7621
+ error.code = 'ESRCH';
6823
7622
  throw error;
6824
7623
  }
6825
7624
  return originalProcessKill(pid, signal);
6826
7625
  });
6827
7626
  try {
6828
- assert.equal(isWorkerAlive('compat-session', 1), true);
6829
- assert.equal(isWorkerAlive('ignored-session', 1, '%77', process.pid, 'team:liveness'), true);
6830
- await killWorker('ignored-session', 1, '%77', undefined, process.pid, 'team:liveness');
7627
+ assert.equal(isWorkerAlive('ignored-session', 1, '%41', 41, 'team:liveness'), false);
7628
+ assert.equal(isWorkerAlive('ignored-session', 2, '%42', 42, 'team:liveness'), false);
7629
+ assert.equal(isWorkerAlive('ignored-session', 3, '%43', process.pid, 'team:liveness'), false);
6831
7630
  }
6832
7631
  finally {
6833
7632
  process.kill = originalProcessKill;
6834
7633
  }
6835
- const log = await readFile(logPath, 'utf-8');
6836
- assert.doesNotMatch(log, /send-keys -t %77|kill-pane -t %77/);
6837
7634
  });
6838
7635
  });
6839
- it('treats only ESRCH as a gone process', async () => {
6840
- await withMockTmuxFixture('omx-worker-liveness-esrch-', () => `#!/bin/sh
7636
+ it('keeps EPERM and malformed exact identity as diagnostic/live-unknown outcomes', async () => {
7637
+ await withMockTmuxFixture('omx-worker-liveness-fail-closed-', () => `#!/bin/sh
7638
+ set -eu
6841
7639
  case "$1" in
6842
- list-panes)
6843
- if [ "$2" = "-a" ]; then printf '%%77\t0\t%s\n' "$PPID"; exit 0; fi
6844
- if [ "$2" = "-t" ]; then printf '0 %s\n' "$PPID"; exit 0; fi
6845
- exit 1
7640
+ display-message)
7641
+ case "$4" in
7642
+ %51) printf '%%51\t0\t%s\tteam:liveness\n' "$PPID" ;;
7643
+ %52) printf '%%leader\t0\t52\tteam:liveness\n' ;;
7644
+ %53) printf '%%53\t0\t53\t\n' ;;
7645
+ %54) printf '%%54\t1\t54\t\n' ;;
7646
+ %55) printf '%%55\t0\t55\tteam:liveness\n' ;;
7647
+ %56) printf '%%56\t1\t56\tteam:liveness\n' ;;
7648
+ *) exit 2 ;;
7649
+ esac
6846
7650
  ;;
6847
- show-option) printf 'team:liveness\n'; exit 0 ;;
6848
7651
  *) exit 1 ;;
6849
7652
  esac
6850
7653
  `, async () => {
6851
7654
  const originalProcessKill = process.kill;
6852
7655
  process.kill = ((pid, signal) => {
6853
7656
  if (pid === process.pid && signal === 0) {
6854
- const error = new Error('no such process');
6855
- error.code = 'ESRCH';
7657
+ const error = new Error('permission denied');
7658
+ error.code = 'EPERM';
6856
7659
  throw error;
6857
7660
  }
6858
7661
  return originalProcessKill(pid, signal);
6859
7662
  });
6860
7663
  try {
6861
- assert.equal(isWorkerAlive('compat-session', 1), false);
6862
- assert.equal(isWorkerAlive('ignored-session', 1, '%77', process.pid, 'team:liveness'), false);
7664
+ assert.equal(isWorkerAlive('ignored-session', 1, '%51', process.pid, 'team:liveness'), true);
7665
+ assert.throws(() => isWorkerAlive('ignored-session', 2, '%52', 52, 'team:liveness'), /worker pane identity changed.*pane_id_changed/);
7666
+ assert.throws(() => isWorkerAlive('ignored-session', 3, '%53', 53, 'team:liveness'), /worker pane incarnation changed.*missing/);
7667
+ assert.equal(isWorkerAlive('ignored-session', 4, '%54', 54, 'team:liveness'), false);
7668
+ assert.equal(isWorkerAlive('ignored-session', 6, '%56', undefined, 'team:liveness'), false);
7669
+ assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', 55, 'team:liveness', '%55'), /worker pane is HUD target/);
7670
+ assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', undefined, 'team:liveness', '%55'), /worker pane is HUD target/);
7671
+ assert.throws(() => isWorkerAlive('ignored-session', 5, '%55', 55, undefined, '%55'), /worker pane is HUD target/);
7672
+ assert.throws(() => getWorkerPanePid('ignored-session', 5, '%55', 55, 'team:liveness', '%55'), /worker pane is HUD target/);
6863
7673
  }
6864
7674
  finally {
6865
7675
  process.kill = originalProcessKill;
6866
7676
  }
6867
7677
  });
6868
7678
  });
6869
- it('uses the exact global row for explicit pane liveness and fails closed for dead rows', async () => {
6870
- await withMockTmuxFixture('omx-pane-id-liveness-', (logPath) => `#!/bin/sh
6871
- set -eu
6872
- printf '%s\n' "$*" >> "${logPath}"
6873
- case "\${1:-}" in
6874
- list-panes)
6875
- if [ "$2" = "-a" ]; then
6876
- printf '%%77\t0\t%s\n%%88\t1\t1\n' "$PPID"
6877
- exit 0
6878
- fi
6879
- exit 1
6880
- ;;
6881
- show-option)
6882
- printf 'team:liveness\n'
6883
- ;;
6884
- *)
6885
- exit 1
6886
- ;;
6887
- esac
6888
- `, async () => {
6889
- assert.equal(isWorkerAlive('ignored-session', 1, '%77', process.pid, 'team:liveness'), true);
6890
- assert.equal(isWorkerPaneOpen('ignored-session', 1, '%77', process.pid, 'team:liveness'), true);
6891
- assert.equal(isWorkerAlive('ignored-session', 2, '%88', 1, 'team:liveness'), false);
6892
- assert.equal(isWorkerPaneOpen('ignored-session', 2, '%88', 1, 'team:liveness'), false);
6893
- });
6894
- });
6895
7679
  });
6896
7680
  describe('exact global pane proof contract', () => {
6897
7681
  it('matches only exact pane IDs and distinguishes live, dead, and absent rows', async () => {
@@ -6906,6 +7690,13 @@ case "$1" in
6906
7690
  show-option)
6907
7691
  printf 'team:rows\n'
6908
7692
  ;;
7693
+ display-message)
7694
+ case "$4" in
7695
+ %13) printf '%%13\t0\t%s\tteam:rows\n' "${pane13Pid}" ;;
7696
+ %130) printf '%%130\t0\t%s\tteam:rows\n' "${pane130Pid}" ;;
7697
+ *) exit 1 ;;
7698
+ esac
7699
+ ;;
6909
7700
  *)
6910
7701
  exit 1
6911
7702
  ;;
@@ -6993,6 +7784,10 @@ case "$1" in
6993
7784
  list-panes)
6994
7785
  printf '%%13\t0\t%s\n' "$PPID"
6995
7786
  ;;
7787
+ display-message)
7788
+ echo "can't find pane: $4" >&2
7789
+ exit 1
7790
+ ;;
6996
7791
  *)
6997
7792
  exit 0
6998
7793
  ;;